diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/third_party/blink/renderer/core/layout | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout')
320 files changed, 11430 insertions, 4453 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/BUILD.gn b/chromium/third_party/blink/renderer/core/layout/BUILD.gn index 0f7341938a0..fdc18664d57 100644 --- a/chromium/third_party/blink/renderer/core/layout/BUILD.gn +++ b/chromium/third_party/blink/renderer/core/layout/BUILD.gn @@ -63,6 +63,8 @@ blink_core_sources("layout") { "geometry/physical_size.h", "geometry/transform_state.cc", "geometry/transform_state.h", + "geometry/writing_mode_converter.cc", + "geometry/writing_mode_converter.h", "grid.cc", "grid.h", "grid_baseline_alignment.cc", @@ -145,6 +147,8 @@ blink_core_sources("layout") { "layout_list_item.h", "layout_list_marker.cc", "layout_list_marker.h", + "layout_list_marker_image.cc", + "layout_list_marker_image.h", "layout_media.cc", "layout_media.h", "layout_multi_column_flow_thread.cc", @@ -262,6 +266,8 @@ blink_core_sources("layout") { "line/trailing_objects.cc", "line/trailing_objects.h", "line/word_measurement.h", + "list_marker.cc", + "list_marker.h", "list_marker_text.cc", "list_marker_text.h", "map_coordinates_flags.h", @@ -324,6 +330,14 @@ blink_core_sources("layout") { "ng/geometry/ng_margin_strut.cc", "ng/geometry/ng_margin_strut.h", "ng/geometry/ng_static_position.h", + "ng/grid/layout_ng_grid.cc", + "ng/grid/layout_ng_grid.h", + "ng/grid/ng_grid_child_iterator.cc", + "ng/grid/ng_grid_child_iterator.h", + "ng/grid/ng_grid_layout_algorithm.cc", + "ng/grid/ng_grid_layout_algorithm.h", + "ng/grid/ng_grid_track_collection.cc", + "ng/grid/ng_grid_track_collection.h", "ng/inline/empty_offset_mapping_builder.h", "ng/inline/layout_ng_text.h", "ng/inline/layout_ng_text_fragment.h", @@ -377,6 +391,8 @@ blink_core_sources("layout") { "ng/inline/ng_line_truncator.h", "ng/inline/ng_line_utils.cc", "ng/inline/ng_line_utils.h", + "ng/inline/ng_logical_line_item.cc", + "ng/inline/ng_logical_line_item.h", "ng/inline/ng_offset_mapping.cc", "ng/inline/ng_offset_mapping.h", "ng/inline/ng_offset_mapping_builder.cc", @@ -385,6 +401,8 @@ blink_core_sources("layout") { "ng/inline/ng_physical_line_box_fragment.h", "ng/inline/ng_physical_text_fragment.cc", "ng/inline/ng_physical_text_fragment.h", + "ng/inline/ng_ruby_utils.cc", + "ng/inline/ng_ruby_utils.h", "ng/inline/ng_text_fragment.cc", "ng/inline/ng_text_fragment.h", "ng/inline/ng_text_fragment_builder.cc", @@ -399,8 +417,6 @@ blink_core_sources("layout") { "ng/layout_ng_block_flow_mixin.h", "ng/layout_ng_fieldset.cc", "ng/layout_ng_fieldset.h", - "ng/layout_ng_grid.cc", - "ng/layout_ng_grid.h", "ng/layout_ng_mixin.cc", "ng/layout_ng_mixin.h", "ng/layout_ng_progress.cc", @@ -418,12 +434,8 @@ blink_core_sources("layout") { "ng/list/layout_ng_inside_list_marker.h", "ng/list/layout_ng_list_item.cc", "ng/list/layout_ng_list_item.h", - "ng/list/layout_ng_list_marker_image.cc", - "ng/list/layout_ng_list_marker_image.h", "ng/list/layout_ng_outside_list_marker.cc", "ng/list/layout_ng_outside_list_marker.h", - "ng/list/list_marker.cc", - "ng/list/list_marker.h", "ng/list/ng_unpositioned_list_marker.cc", "ng/list/ng_unpositioned_list_marker.h", "ng/mathml/layout_ng_mathml_block.cc", @@ -440,6 +452,7 @@ blink_core_sources("layout") { "ng/mathml/ng_math_space_layout_algorithm.h", "ng/mathml/ng_math_under_over_layout_algorithm.cc", "ng/mathml/ng_math_under_over_layout_algorithm.h", + "ng/mathml/ng_mathml_paint_info.h", "ng/ng_absolute_utils.cc", "ng/ng_absolute_utils.h", "ng/ng_block_break_token.cc", @@ -478,8 +491,6 @@ blink_core_sources("layout") { "ng/ng_fragment_child_iterator.h", "ng/ng_fragmentation_utils.cc", "ng/ng_fragmentation_utils.h", - "ng/ng_grid_layout_algorithm.cc", - "ng/ng_grid_layout_algorithm.h", "ng/ng_ink_overflow.cc", "ng/ng_ink_overflow.h", "ng/ng_layout_algorithm.h", @@ -535,6 +546,10 @@ blink_core_sources("layout") { "ng/table/layout_ng_table_section.cc", "ng/table/layout_ng_table_section.h", "ng/table/layout_ng_table_section_interface.h", + "ng/table/ng_table_layout_algorithm_helpers.cc", + "ng/table/ng_table_layout_algorithm_helpers.h", + "ng/table/ng_table_layout_algorithm_types.cc", + "ng/table/ng_table_layout_algorithm_types.h", "order_iterator.cc", "order_iterator.h", "overflow_model.h", diff --git a/chromium/third_party/blink/renderer/core/layout/api/line_layout_item.h b/chromium/third_party/blink/renderer/core/layout/api/line_layout_item.h index f3c8d5a489e..54d5aeab8c3 100644 --- a/chromium/third_party/blink/renderer/core/layout/api/line_layout_item.h +++ b/chromium/third_party/blink/renderer/core/layout/api/line_layout_item.h @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_object_inlines.h" #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/paint/object_paint_invalidator.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" @@ -64,6 +65,14 @@ class LineLayoutItem { Node* NonPseudoNode() const { return layout_object_->NonPseudoNode(); } + Node* GetNodeForOwnerNodeId() const { + LayoutTextFragment* layout_text_fragment = + ToLayoutTextFragmentOrNull(layout_object_); + if (layout_text_fragment) + return layout_text_fragment->AssociatedTextNode(); + return layout_object_->GetNode(); + } + LineLayoutItem Parent() const { return LineLayoutItem(layout_object_->Parent()); } diff --git a/chromium/third_party/blink/renderer/core/layout/api/line_layout_list_marker.h b/chromium/third_party/blink/renderer/core/layout/api/line_layout_list_marker.h index f5cc4ed4fcd..30e6869bb0a 100644 --- a/chromium/third_party/blink/renderer/core/layout/api/line_layout_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/api/line_layout_list_marker.h @@ -5,22 +5,22 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_API_LINE_LAYOUT_LIST_MARKER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_API_LINE_LAYOUT_LIST_MARKER_H_ -#include "third_party/blink/renderer/core/layout/api/line_layout_box.h" -#include "third_party/blink/renderer/core/layout/layout_list_marker.h" +#include "third_party/blink/renderer/core/layout/api/line_layout_item.h" namespace blink { -class LineLayoutListMarker : public LineLayoutBox { +class LineLayoutListMarker : public LineLayoutItem { public: - explicit LineLayoutListMarker(LayoutListMarker* layout_list_marker) - : LineLayoutBox(layout_list_marker) {} + explicit LineLayoutListMarker(LayoutObject* object) : LineLayoutItem(object) { + SECURITY_DCHECK(!object || object->IsListMarker()); + } explicit LineLayoutListMarker(const LineLayoutItem& item) - : LineLayoutBox(item) { + : LineLayoutItem(item) { SECURITY_DCHECK(!item || item.IsListMarker()); } - explicit LineLayoutListMarker(std::nullptr_t) : LineLayoutBox(nullptr) {} + explicit LineLayoutListMarker(std::nullptr_t) : LineLayoutItem(nullptr) {} LineLayoutListMarker() = default; diff --git a/chromium/third_party/blink/renderer/core/layout/api/selection_state.cc b/chromium/third_party/blink/renderer/core/layout/api/selection_state.cc index 86c66c603dc..e2ba364136e 100644 --- a/chromium/third_party/blink/renderer/core/layout/api/selection_state.cc +++ b/chromium/third_party/blink/renderer/core/layout/api/selection_state.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/layout/api/selection_state.h" +#include "base/notreached.h" #include "third_party/blink/renderer/platform/wtf/assertions.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/layout/counter_node.cc b/chromium/third_party/blink/renderer/core/layout/counter_node.cc index 4e6b3ceb3b9..d51138fcb02 100644 --- a/chromium/third_party/blink/renderer/core/layout/counter_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/counter_node.cc @@ -30,8 +30,8 @@ namespace blink { -CounterNode::CounterNode(LayoutObject& o, bool has_reset_type, int value) - : has_reset_type_(has_reset_type), +CounterNode::CounterNode(LayoutObject& o, unsigned type_mask, int value) + : type_mask_(type_mask), value_(value), count_in_parent_(0), owner_(o), @@ -93,9 +93,9 @@ CounterNode::~CounterNode() { } scoped_refptr<CounterNode> CounterNode::Create(LayoutObject& owner, - bool has_reset_type, + unsigned type_mask, int value) { - return base::AdoptRef(new CounterNode(owner, has_reset_type, value)); + return base::AdoptRef(new CounterNode(owner, type_mask, value)); } CounterNode* CounterNode::NextInPreOrderAfterChildren( @@ -146,6 +146,14 @@ int CounterNode::ComputeCountInParent() const { // According to the spec, if an increment would overflow or underflow the // counter, we are allowed to ignore the increment. // https://drafts.csswg.org/css-lists-3/#valdef-counter-reset-custom-ident-integer + + // If we have a set type, then we override parent value altogether, so the + // result is just our value. + if (HasSetType()) + return value_; + + // If we act as a reset, then we don't add anything on top of the parent count + // (and we don't override it as we would with a set type). int increment = ActsAsReset() ? 0 : value_; if (previous_sibling_) { return base::CheckAdd(previous_sibling_->count_in_parent_, increment) @@ -218,6 +226,31 @@ void CounterNode::ResetLayoutObjects() { } } +// static +CounterNode* CounterNode::AncestorNodeAcrossStyleContainment( + const LayoutObject& starting_object, + const AtomicString& identifier) { + bool crossed_style_containment = false; + for (auto* ancestor = starting_object.Parent(); ancestor; + ancestor = ancestor->Parent()) { + crossed_style_containment |= ancestor->ShouldApplyStyleContainment(); + if (!crossed_style_containment) + continue; + if (CounterMap* node_map = LayoutCounter::GetCounterMap(ancestor)) { + if (CounterNode* node = node_map->at(identifier)) + return node; + } + } + return nullptr; +} + +CounterNode* CounterNode::ParentCrossingStyleContainment( + const AtomicString& identifier) const { + if (parent_) + return parent_; + return AncestorNodeAcrossStyleContainment(Owner(), identifier); +} + void CounterNode::ResetThisAndDescendantsLayoutObjects() { CounterNode* node = this; do { @@ -251,7 +284,7 @@ void CounterNode::InsertAfter(CounterNode* new_child, if (ref_child && ref_child->parent_ != this) return; - if (new_child->has_reset_type_) { + if (new_child->HasResetType()) { while (last_child_ != ref_child) LayoutCounter::DestroyCounterNode(last_child_->Owner(), identifier); } @@ -278,7 +311,7 @@ void CounterNode::InsertAfter(CounterNode* new_child, last_child_ = new_child; } - if (!new_child->first_child_ || new_child->has_reset_type_) { + if (!new_child->first_child_ || new_child->HasResetType()) { new_child->count_in_parent_ = new_child->ComputeCountInParent(); new_child->ResetThisAndDescendantsLayoutObjects(); if (next) diff --git a/chromium/third_party/blink/renderer/core/layout/counter_node.h b/chromium/third_party/blink/renderer/core/layout/counter_node.h index 5388c8bf27b..1ba00809897 100644 --- a/chromium/third_party/blink/renderer/core/layout/counter_node.h +++ b/chromium/third_party/blink/renderer/core/layout/counter_node.h @@ -47,12 +47,15 @@ class CounterNode : public RefCounted<CounterNode> { USING_FAST_MALLOC(CounterNode); public: + enum Type { kIncrementType = 1 << 0, kResetType = 1 << 1, kSetType = 1 << 2 }; + static scoped_refptr<CounterNode> Create(LayoutObject&, - bool is_reset, + unsigned type_mask, int value); ~CounterNode(); - bool ActsAsReset() const { return has_reset_type_ || !parent_; } - bool HasResetType() const { return has_reset_type_; } + bool ActsAsReset() const { return HasResetType() || !parent_; } + bool HasResetType() const { return type_mask_ & kResetType; } + bool HasSetType() const { return type_mask_ & kSetType; } int Value() const { return value_; } int CountInParent() const { return count_in_parent_; } LayoutObject& Owner() const { return owner_; } @@ -62,6 +65,21 @@ class CounterNode : public RefCounted<CounterNode> { // Invalidates the text in the layoutObjects of this counter, if any. void ResetLayoutObjects(); + // This finds a closest ancestor style containment boundary, crosses it, and + // then returns the closest ancestor CounterNode available (for the given + // `identifier`). Note that the element that specifies contain: style is + // itself considered to be across the boundary from its subtree. + static CounterNode* AncestorNodeAcrossStyleContainment( + const LayoutObject&, + const AtomicString& identifier); + + // Returns the parent of this CounterNode. If the node is the root, then it + // instead tries to find a node with the same identifier across the style + // containment boundary so that it can continue navigating up to the root of + // the document. This is used for reporting content: counters(). + CounterNode* ParentCrossingStyleContainment( + const AtomicString& identifier) const; + CounterNode* Parent() const { return parent_; } CounterNode* PreviousSibling() const { return previous_sibling_; } CounterNode* NextSibling() const { return next_sibling_; } @@ -88,14 +106,14 @@ class CounterNode : public RefCounted<CounterNode> { const AtomicString& identifier); private: - CounterNode(LayoutObject&, bool is_reset, int value); + CounterNode(LayoutObject&, unsigned type_mask, int value); int ComputeCountInParent() const; // Invalidates the text in the layoutObject of this counter, if any, // and in the layoutObjects of all descendants of this counter, if any. void ResetThisAndDescendantsLayoutObjects(); void Recount(); - bool has_reset_type_; + unsigned type_mask_; int value_; int count_in_parent_; LayoutObject& owner_; diff --git a/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.cc b/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.cc index 5136d0d40a3..fee43efde47 100644 --- a/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.cc +++ b/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.cc @@ -46,70 +46,42 @@ CustomScrollbar::CustomScrollbar(ScrollableArea* scrollable_area, nullptr, CustomScrollbarTheme::GetCustomScrollbarTheme()) { DCHECK(style_source); - - // FIXME: We need to do this because CustomScrollbar::styleChanged is called - // as soon as the scrollbar is created. - - // Update the scrollbar size. - IntRect rect(0, 0, 0, 0); - UpdateScrollbarPart(kScrollbarBGPart); - if (LayoutCustomScrollbarPart* part = parts_.at(kScrollbarBGPart)) { - part->UpdateLayout(); - rect.SetSize(FlooredIntSize(part->Size())); - } else if (Orientation() == kHorizontalScrollbar) { - rect.SetWidth(Width()); - } else { - rect.SetHeight(Height()); - } - - SetFrameRect(rect); } CustomScrollbar::~CustomScrollbar() { - if (parts_.IsEmpty()) - return; - - // When a scrollbar is detached from its parent (causing all parts removal) - // and ready to be destroyed, its destruction can be delayed because of - // RefPtr maintained in other classes such as EventHandler - // (m_lastScrollbarUnderMouse). - // Meanwhile, we can have a call to updateScrollbarPart which recreates the - // scrollbar part. So, we need to destroy these parts since we don't want them - // to call on a destroyed scrollbar. See webkit bug 68009. - UpdateScrollbarParts(true); + DCHECK(!scrollable_area_); + DCHECK(parts_.IsEmpty()); } int CustomScrollbar::HypotheticalScrollbarThickness( + const ScrollableArea* scrollable_area, ScrollbarOrientation orientation, - const LayoutBox& enclosing_box, - const LayoutObject& style_source) { - scoped_refptr<const ComputedStyle> part_style = - style_source.GetUncachedPseudoElementStyle( - PseudoElementStyleRequest(kPseudoIdScrollbar, nullptr, - kScrollbarBGPart), - style_source.Style()); - if (orientation == kHorizontalScrollbar) { - return LayoutCustomScrollbarPart::ComputeScrollbarHeight( - enclosing_box.ClientHeight().ToInt(), part_style.get()); - } - return LayoutCustomScrollbarPart::ComputeScrollbarWidth( - enclosing_box.ClientWidth().ToInt(), part_style.get()); + Element* style_source) { + // Create a temporary scrollbar so that we can match style rules like + // ::-webkit-scrollbar:horizontal according to the scrollbar's orientation. + auto* scrollbar = MakeGarbageCollected<CustomScrollbar>( + const_cast<ScrollableArea*>(scrollable_area), orientation, style_source); + scrollbar->UpdateScrollbarPart(kScrollbarBGPart); + auto* part = scrollbar->GetPart(kScrollbarBGPart); + int thickness = part ? part->ComputeThickness() : 0; + scrollbar->DisconnectFromScrollableArea(); + return thickness; } -void CustomScrollbar::Trace(Visitor* visitor) { +void CustomScrollbar::Trace(Visitor* visitor) const { Scrollbar::Trace(visitor); } void CustomScrollbar::DisconnectFromScrollableArea() { - UpdateScrollbarParts(true); + DestroyScrollbarParts(); Scrollbar::DisconnectFromScrollableArea(); } -void CustomScrollbar::SetEnabled(bool e) { - bool was_enabled = Enabled(); - Scrollbar::SetEnabled(e); - if (was_enabled != e) - UpdateScrollbarParts(); +void CustomScrollbar::SetEnabled(bool enabled) { + if (Enabled() == enabled) + return; + Scrollbar::SetEnabled(enabled); + UpdateScrollbarParts(); } void CustomScrollbar::StyleChanged() { @@ -117,6 +89,11 @@ void CustomScrollbar::StyleChanged() { } void CustomScrollbar::SetHoveredPart(ScrollbarPart part) { + // This can be called from EventHandler after the scrollbar has been + // disconnected from the scrollable area. + if (!scrollable_area_) + return; + if (part == hovered_part_) return; @@ -128,10 +105,17 @@ void CustomScrollbar::SetHoveredPart(ScrollbarPart part) { UpdateScrollbarPart(kScrollbarBGPart); UpdateScrollbarPart(kTrackBGPart); + + PositionScrollbarParts(); } void CustomScrollbar::SetPressedPart(ScrollbarPart part, WebInputEvent::Type type) { + // This can be called from EventHandler after the scrollbar has been + // disconnected from the scrollable area. + if (!scrollable_area_) + return; + ScrollbarPart old_part = pressed_part_; Scrollbar::SetPressedPart(part, type); @@ -140,6 +124,8 @@ void CustomScrollbar::SetPressedPart(ScrollbarPart part, UpdateScrollbarPart(kScrollbarBGPart); UpdateScrollbarPart(kTrackBGPart); + + PositionScrollbarParts(); } scoped_refptr<const ComputedStyle> @@ -156,39 +142,33 @@ CustomScrollbar::GetScrollbarPseudoElementStyle(ScrollbarPart part_type, return source_style->AddCachedPseudoElementStyle(std::move(part_style)); } -void CustomScrollbar::UpdateScrollbarParts(bool destroy) { - UpdateScrollbarPart(kScrollbarBGPart, destroy); - UpdateScrollbarPart(kBackButtonStartPart, destroy); - UpdateScrollbarPart(kForwardButtonStartPart, destroy); - UpdateScrollbarPart(kBackTrackPart, destroy); - UpdateScrollbarPart(kThumbPart, destroy); - UpdateScrollbarPart(kForwardTrackPart, destroy); - UpdateScrollbarPart(kBackButtonEndPart, destroy); - UpdateScrollbarPart(kForwardButtonEndPart, destroy); - UpdateScrollbarPart(kTrackBGPart, destroy); - - if (destroy) - return; +void CustomScrollbar::DestroyScrollbarParts() { + for (auto& part : parts_) + part.value->Destroy(); + parts_.clear(); +} + +void CustomScrollbar::UpdateScrollbarParts() { + for (auto part : + {kScrollbarBGPart, kBackButtonStartPart, kForwardButtonStartPart, + kBackTrackPart, kThumbPart, kForwardTrackPart, kBackButtonEndPart, + kForwardButtonEndPart, kTrackBGPart}) + UpdateScrollbarPart(part); // See if the scrollbar's thickness changed. If so, we need to mark our // owning object as needing a layout. bool is_horizontal = Orientation() == kHorizontalScrollbar; int old_thickness = is_horizontal ? Height() : Width(); int new_thickness = 0; - LayoutCustomScrollbarPart* part = parts_.at(kScrollbarBGPart); - if (part) { - part->UpdateLayout(); - new_thickness = - (is_horizontal ? part->Size().Height() : part->Size().Width()).ToInt(); - } + if (auto* part = parts_.at(kScrollbarBGPart)) + new_thickness = part->ComputeThickness(); if (new_thickness != old_thickness) { SetFrameRect( IntRect(Location(), IntSize(is_horizontal ? Width() : new_thickness, is_horizontal ? new_thickness : Height()))); if (LayoutBox* box = GetScrollableArea()->GetLayoutBox()) { - auto* layout_block = DynamicTo<LayoutBlock>(box); - if (layout_block) + if (auto* layout_block = DynamicTo<LayoutBlock>(box)) layout_block->NotifyScrollbarThicknessChanged(); box->SetChildNeedsLayout(); // LayoutNG may attempt to reuse line-box fragments. It will do this even @@ -197,9 +177,19 @@ void CustomScrollbar::UpdateScrollbarParts(bool destroy) { // this is similar to border or padding changing, (which marks the box as // self needs layout). box->SetNeedsLayout(layout_invalidation_reason::kScrollbarChanged); - if (scrollable_area_) - scrollable_area_->SetScrollCornerNeedsPaintInvalidation(); + scrollable_area_->SetScrollCornerNeedsPaintInvalidation(); } + return; + } + + // If we didn't return above, it means that there is no change or the change + // doesn't affect layout of the box. Update position to reflect the change if + // any. + if (LayoutBox* box = GetScrollableArea()->GetLayoutBox()) { + // It's not ready to position scrollbar parts if the containing box has not + // been inserted into the layout tree. + if (box->IsLayoutView() || box->Parent()) + PositionScrollbarParts(); } } @@ -227,18 +217,16 @@ static PseudoId PseudoForScrollbarPart(ScrollbarPart part) { return kPseudoIdScrollbar; } -void CustomScrollbar::UpdateScrollbarPart(ScrollbarPart part_type, - bool destroy) { +void CustomScrollbar::UpdateScrollbarPart(ScrollbarPart part_type) { + DCHECK(scrollable_area_); if (part_type == kNoPart) return; scoped_refptr<const ComputedStyle> part_style = - !destroy ? GetScrollbarPseudoElementStyle( - part_type, PseudoForScrollbarPart(part_type)) - : scoped_refptr<const ComputedStyle>(nullptr); - + GetScrollbarPseudoElementStyle(part_type, + PseudoForScrollbarPart(part_type)); bool need_layout_object = - !destroy && part_style && part_style->Display() != EDisplay::kNone; + part_style && part_style->Display() != EDisplay::kNone; if (need_layout_object && // display:block overrides OS settings. @@ -270,8 +258,7 @@ void CustomScrollbar::UpdateScrollbarPart(ScrollbarPart part_type, parts_.erase(part_type); part_layout_object->Destroy(); part_layout_object = nullptr; - if (!destroy) - SetNeedsPaintInvalidation(part_type); + SetNeedsPaintInvalidation(part_type); } if (part_layout_object) @@ -283,52 +270,40 @@ IntRect CustomScrollbar::ButtonRect(ScrollbarPart part_type) const { if (!part_layout_object) return IntRect(); - part_layout_object->UpdateLayout(); - bool is_horizontal = Orientation() == kHorizontalScrollbar; - if (part_type == kBackButtonStartPart) - return IntRect( - Location(), - IntSize( - is_horizontal ? part_layout_object->PixelSnappedWidth() : Width(), - is_horizontal ? Height() - : part_layout_object->PixelSnappedHeight())); - if (part_type == kForwardButtonEndPart) { - return IntRect( - is_horizontal ? X() + Width() - part_layout_object->PixelSnappedWidth() - : X(), - is_horizontal - ? Y() - : Y() + Height() - part_layout_object->PixelSnappedHeight(), - is_horizontal ? part_layout_object->PixelSnappedWidth() : Width(), - is_horizontal ? Height() : part_layout_object->PixelSnappedHeight()); - } + int button_length = part_layout_object->ComputeLength(); + IntRect button_rect(Location(), is_horizontal + ? IntSize(button_length, Height()) + : IntSize(Width(), button_length)); - if (part_type == kForwardButtonStartPart) { - IntRect previous_button = ButtonRect(kBackButtonStartPart); - return IntRect( - is_horizontal ? X() + previous_button.Width() : X(), - is_horizontal ? Y() : Y() + previous_button.Height(), - is_horizontal ? part_layout_object->PixelSnappedWidth() : Width(), - is_horizontal ? Height() : part_layout_object->PixelSnappedHeight()); + switch (part_type) { + case kBackButtonStartPart: + break; + case kForwardButtonEndPart: + button_rect.Move(is_horizontal ? Width() - button_length : 0, + is_horizontal ? 0 : Height() - button_length); + break; + case kForwardButtonStartPart: { + IntRect previous_button = ButtonRect(kBackButtonStartPart); + button_rect.Move(is_horizontal ? previous_button.Width() : 0, + is_horizontal ? 0 : previous_button.Height()); + break; + } + case kBackButtonEndPart: { + IntRect next_button = ButtonRect(kForwardButtonEndPart); + button_rect.Move( + is_horizontal ? Width() - next_button.Width() - button_length : 0, + is_horizontal ? 0 : Height() - next_button.Height() - button_length); + break; + } + default: + NOTREACHED(); } - - IntRect following_button = ButtonRect(kForwardButtonEndPart); - return IntRect( - is_horizontal ? X() + Width() - following_button.Width() - - part_layout_object->PixelSnappedWidth() - : X(), - is_horizontal ? Y() - : Y() + Height() - following_button.Height() - - part_layout_object->PixelSnappedHeight(), - is_horizontal ? part_layout_object->PixelSnappedWidth() : Width(), - is_horizontal ? Height() : part_layout_object->PixelSnappedHeight()); + return button_rect; } IntRect CustomScrollbar::TrackRect(int start_length, int end_length) const { - LayoutCustomScrollbarPart* part = parts_.at(kTrackBGPart); - if (part) - part->UpdateLayout(); + const LayoutCustomScrollbarPart* part = GetPart(kTrackBGPart); if (Orientation() == kHorizontalScrollbar) { int margin_left = part ? part->MarginLeft().ToInt() : 0; @@ -351,12 +326,10 @@ IntRect CustomScrollbar::TrackRect(int start_length, int end_length) const { IntRect CustomScrollbar::TrackPieceRectWithMargins( ScrollbarPart part_type, const IntRect& old_rect) const { - LayoutCustomScrollbarPart* part_layout_object = parts_.at(part_type); + const LayoutCustomScrollbarPart* part_layout_object = GetPart(part_type); if (!part_layout_object) return old_rect; - part_layout_object->UpdateLayout(); - IntRect rect = old_rect; if (Orientation() == kHorizontalScrollbar) { rect.SetX((rect.X() + part_layout_object->MarginLeft()).ToInt()); @@ -370,24 +343,80 @@ IntRect CustomScrollbar::TrackPieceRectWithMargins( } int CustomScrollbar::MinimumThumbLength() const { - LayoutCustomScrollbarPart* part_layout_object = parts_.at(kThumbPart); - if (!part_layout_object) - return 0; - part_layout_object->UpdateLayout(); - return (Orientation() == kHorizontalScrollbar - ? part_layout_object->Size().Width() - : part_layout_object->Size().Height()) - .ToInt(); + if (const auto* part_layout_object = GetPart(kThumbPart)) + return part_layout_object->ComputeLength(); + return 0; +} + +void CustomScrollbar::OffsetDidChange(mojom::blink::ScrollType scroll_type) { + Scrollbar::OffsetDidChange(scroll_type); + PositionScrollbarParts(); +} + +void CustomScrollbar::PositionScrollbarParts() { + DCHECK_NE( + scrollable_area_->GetLayoutBox()->GetDocument().Lifecycle().GetState(), + DocumentLifecycle::kInPaint); + + // Update frame rect of parts. + IntRect track_rect = GetTheme().TrackRect(*this); + IntRect start_track_rect; + IntRect thumb_rect; + IntRect end_track_rect; + GetTheme().SplitTrack(*this, track_rect, start_track_rect, thumb_rect, + end_track_rect); + for (auto& part : parts_) { + IntRect part_rect; + switch (part.key) { + case kBackButtonStartPart: + case kForwardButtonStartPart: + case kBackButtonEndPart: + case kForwardButtonEndPart: + part_rect = ButtonRect(part.key); + break; + case kBackTrackPart: + part_rect = start_track_rect; + break; + case kForwardTrackPart: + part_rect = end_track_rect; + break; + case kThumbPart: + part_rect = thumb_rect; + break; + case kTrackBGPart: + part_rect = track_rect; + break; + case kScrollbarBGPart: + part_rect = FrameRect(); + break; + default: + NOTREACHED(); + } + part.value->ClearNeedsLayoutWithoutPaintInvalidation(); + // The part's paint offset is relative to the box. + // TODO(crbug.com/1020913): This should be part of PaintPropertyTreeBuilder + // when we support subpixel layout of overflow controls. + part.value->GetMutableForPainting().FirstFragment().SetPaintOffset( + PhysicalOffset(part_rect.Location())); + // The part's frame_rect is relative to the scrollbar. + part_rect.MoveBy(-Location()); + part.value->SetFrameRect(LayoutRect(part_rect)); + } } void CustomScrollbar::InvalidateDisplayItemClientsOfScrollbarParts() { for (auto& part : parts_) { ObjectPaintInvalidator(*part.value) - .InvalidateDisplayItemClientsIncludingNonCompositingDescendants( - PaintInvalidationReason::kScrollControl); + .SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient( + *part.value, PaintInvalidationReason::kScrollControl); } } +void CustomScrollbar::ClearPaintFlags() { + for (auto& part : parts_) + part.value->ClearPaintFlags(); +} + void CustomScrollbar::SetVisualRect(const IntRect& rect) { Scrollbar::SetVisualRect(rect); for (auto& part : parts_) diff --git a/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.h b/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.h index 734cb2d7931..aeebaa58c8d 100644 --- a/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.h +++ b/chromium/third_party/blink/renderer/core/layout/custom_scrollbar.h @@ -36,23 +36,21 @@ namespace blink { class ComputedStyle; class Element; -class LayoutBox; class LayoutCustomScrollbarPart; -class LayoutObject; // Custom scrollbars are created when a box has -webkit-scrollbar* pseudo // styles. The parts of a custom scrollbar are layout objects of class // LayoutCustomScrollbarPart. -class CustomScrollbar final : public Scrollbar { +class CORE_EXPORT CustomScrollbar final : public Scrollbar { public: - CustomScrollbar(ScrollableArea*, ScrollbarOrientation, Element*); + CustomScrollbar(ScrollableArea*, ScrollbarOrientation, Element* style_source); ~CustomScrollbar() override; - // Return the thickness that a custom scrollbar would have, without actually - // constructing the scrollbar. - static int HypotheticalScrollbarThickness(ScrollbarOrientation, - const LayoutBox& enclosing_box, - const LayoutObject& style_source); + // Return the thickness that a custom scrollbar would have, before actually + // constructing the real scrollbar. + static int HypotheticalScrollbarThickness(const ScrollableArea*, + ScrollbarOrientation, + Element* style_source); IntRect ButtonRect(ScrollbarPart) const; IntRect TrackRect(int start_length, int end_length) const; @@ -62,6 +60,10 @@ class CustomScrollbar final : public Scrollbar { bool IsOverlayScrollbar() const override { return false; } + void OffsetDidChange(mojom::blink::ScrollType) override; + + void PositionScrollbarParts(); + LayoutCustomScrollbarPart* GetPart(ScrollbarPart part_type) { return parts_.at(part_type); } @@ -70,10 +72,10 @@ class CustomScrollbar final : public Scrollbar { } void InvalidateDisplayItemClientsOfScrollbarParts(); - + void ClearPaintFlags(); void SetVisualRect(const IntRect&) final; - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: friend class Scrollbar; @@ -88,14 +90,15 @@ class CustomScrollbar final : public Scrollbar { bool IsCustomScrollbar() const override { return true; } - void UpdateScrollbarParts(bool destroy = false); - + void DestroyScrollbarParts(); + void UpdateScrollbarParts(); scoped_refptr<const ComputedStyle> GetScrollbarPseudoElementStyle( ScrollbarPart, PseudoId); - void UpdateScrollbarPart(ScrollbarPart, bool destroy = false); + void UpdateScrollbarPart(ScrollbarPart); - HashMap<unsigned, LayoutCustomScrollbarPart*> parts_; + HashMap<ScrollbarPart, LayoutCustomScrollbarPart*> parts_; + bool needs_position_scrollbar_parts_ = true; }; template <> diff --git a/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc index 91792d6bfff..341c36230e5 100644 --- a/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc @@ -82,7 +82,8 @@ FlexItem::FlexItem(const FlexLayoutAlgorithm* algorithm, base::Optional<MinMaxSizes> min_max_cross_sizes, LayoutUnit main_axis_border_padding, LayoutUnit cross_axis_border_padding, - NGPhysicalBoxStrut physical_margins) + NGPhysicalBoxStrut physical_margins, + NGBoxStrut scrollbars) : algorithm(algorithm), line_number(0), box(box), @@ -95,6 +96,7 @@ FlexItem::FlexItem(const FlexLayoutAlgorithm* algorithm, main_axis_border_padding(main_axis_border_padding), cross_axis_border_padding(cross_axis_border_padding), physical_margins(physical_margins), + scrollbars(scrollbars), frozen(false), needs_relayout_for_stretch(false), ng_input_node(/* LayoutBox* */ nullptr) { @@ -588,16 +590,17 @@ LayoutUnit FlexLayoutAlgorithm::GapBetweenItems( return LayoutUnit(); DCHECK_GE(percent_resolution_sizes.inline_size, 0); if (IsColumnFlow(style)) { - if (LIKELY(style.RowGap().IsNormal())) - return LayoutUnit(); - return MinimumValueForLength( - style.RowGap().GetLength(), - percent_resolution_sizes.block_size.ClampNegativeToZero()); - } - if (LIKELY(style.ColumnGap().IsNormal())) + if (const base::Optional<Length>& row_gap = style.RowGap()) { + return MinimumValueForLength( + *row_gap, percent_resolution_sizes.block_size.ClampNegativeToZero()); + } return LayoutUnit(); - return MinimumValueForLength(style.ColumnGap().GetLength(), - percent_resolution_sizes.inline_size); + } + if (const base::Optional<Length>& column_gap = style.ColumnGap()) { + return MinimumValueForLength(*column_gap, + percent_resolution_sizes.inline_size); + } + return LayoutUnit(); } // static @@ -608,16 +611,17 @@ LayoutUnit FlexLayoutAlgorithm::GapBetweenLines( return LayoutUnit(); DCHECK_GE(percent_resolution_sizes.inline_size, 0); if (!IsColumnFlow(style)) { - if (LIKELY(style.RowGap().IsNormal())) - return LayoutUnit(); - return MinimumValueForLength( - style.RowGap().GetLength(), - percent_resolution_sizes.block_size.ClampNegativeToZero()); - } - if (LIKELY(style.ColumnGap().IsNormal())) + if (const base::Optional<Length>& row_gap = style.RowGap()) { + return MinimumValueForLength( + *row_gap, percent_resolution_sizes.block_size.ClampNegativeToZero()); + } return LayoutUnit(); - return MinimumValueForLength(style.ColumnGap().GetLength(), - percent_resolution_sizes.inline_size); + } + if (const base::Optional<Length>& column_gap = style.ColumnGap()) { + return MinimumValueForLength(*column_gap, + percent_resolution_sizes.inline_size); + } + return LayoutUnit(); } FlexLayoutAlgorithm::FlexLayoutAlgorithm(const ComputedStyle* style, @@ -633,13 +637,13 @@ FlexLayoutAlgorithm::FlexLayoutAlgorithm(const ComputedStyle* style, DCHECK_GE(gap_between_lines_, 0); const auto& row_gap = style->RowGap(); const auto& column_gap = style->ColumnGap(); - if (!row_gap.IsNormal() || !column_gap.IsNormal()) { + if (row_gap || column_gap) { UseCounter::Count(document, WebFeature::kFlexGapSpecified); if (gap_between_items_ || gap_between_lines_) UseCounter::Count(document, WebFeature::kFlexGapPositive); } - if (!row_gap.IsNormal() && row_gap.GetLength().IsPercentOrCalc()) { + if (row_gap && row_gap->IsPercentOrCalc()) { UseCounter::Count(document, WebFeature::kFlexRowGapPercent); if (percent_resolution_sizes.block_size == LayoutUnit(-1)) UseCounter::Count(document, WebFeature::kFlexRowGapPercentIndefinite); @@ -749,7 +753,8 @@ bool FlexLayoutAlgorithm::ShouldApplyMinSizeAutoForChild( IsHorizontalFlow() != child.StyleRef().IsHorizontalWritingMode(); bool intrinsic_in_childs_block_axis = main_axis_is_childs_block_axis && - (min.IsMinContent() || min.IsMaxContent() || min.IsFitContent()); + (min.IsMinContent() || min.IsMaxContent() || min.IsMinIntrinsic() || + min.IsFitContent()); if (!min.IsAuto() && !intrinsic_in_childs_block_axis) return false; diff --git a/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.h b/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.h index 6e0a4c255e4..3da8d3ca1fb 100644 --- a/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/flexible_box_algorithm.h @@ -126,7 +126,8 @@ class FlexItem { base::Optional<MinMaxSizes> min_max_cross_sizes, LayoutUnit main_axis_border_padding, LayoutUnit cross_axis_border_padding, - NGPhysicalBoxStrut physical_margins); + NGPhysicalBoxStrut physical_margins, + NGBoxStrut scrollbars); LayoutUnit HypotheticalMainAxisMarginBoxSize() const { return hypothetical_main_content_size + main_axis_border_padding + @@ -197,6 +198,7 @@ class FlexItem { const LayoutUnit main_axis_border_padding; const LayoutUnit cross_axis_border_padding; NGPhysicalBoxStrut physical_margins; + const NGBoxStrut scrollbars; LayoutUnit flexed_content_size; diff --git a/chromium/third_party/blink/renderer/core/layout/floating_objects.h b/chromium/third_party/blink/renderer/core/layout/floating_objects.h index 11edc377a8d..92a4784eda5 100644 --- a/chromium/third_party/blink/renderer/core/layout/floating_objects.h +++ b/chromium/third_party/blink/renderer/core/layout/floating_objects.h @@ -143,8 +143,7 @@ class FloatingObject { is_lowest_non_overhanging_float_in_child; } - // FIXME: Callers of these methods are dangerous and should be whitelisted - // explicitly or removed. + // FIXME: Callers of these methods are dangerous and should be removed. RootInlineBox* OriginatingLine() const { return originating_line_; } void SetOriginatingLine(RootInlineBox* line) { originating_line_ = line; } diff --git a/chromium/third_party/blink/renderer/core/layout/force_legacy_layout_test.cc b/chromium/third_party/blink/renderer/core/layout/force_legacy_layout_test.cc index e4c5ff110ff..5503864c2e2 100644 --- a/chromium/third_party/blink/renderer/core/layout/force_legacy_layout_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/force_legacy_layout_test.cc @@ -10,11 +10,6 @@ namespace blink { namespace { -bool ForcesLegacyLayout(const Element& element) { - return element.ShouldForceLegacyLayout() && - !element.GetLayoutObject()->IsLayoutNGMixin(); -} - bool UsesNGLayout(const Element& element) { return !element.ShouldForceLegacyLayout() && element.GetLayoutObject()->IsLayoutNGMixin(); @@ -23,9 +18,11 @@ bool UsesNGLayout(const Element& element) { } // anonymous namespace class ForceLegacyLayoutTest : public RenderingTest { - public: + protected: ForceLegacyLayoutTest() : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {} + + bool EditingNGEnabled() { return RuntimeEnabledFeatures::EditingNGEnabled(); } }; TEST_F(ForceLegacyLayoutTest, ForceLegacyBfcRecalcAncestorStyle) { @@ -74,19 +71,19 @@ TEST_F(ForceLegacyLayoutTest, ForceLegacyBfcRecalcAncestorStyle) { EXPECT_TRUE(UsesNGLayout(*bfc)); EXPECT_TRUE(UsesNGLayout(*container)); EXPECT_TRUE(UsesNGLayout(*middle)); - EXPECT_TRUE(ForcesLegacyLayout(*inner)); - EXPECT_TRUE(ForcesLegacyLayout(*child)); + EXPECT_EQ(UsesNGLayout(*inner), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*child), EditingNGEnabled()); // Remove overflow:hidden, so that the contenteditable element no longer // establishes a formatting context. inner->removeAttribute(html_names::kStyleAttr); UpdateAllLifecyclePhasesForTest(); EXPECT_TRUE(UsesNGLayout(*body)); - EXPECT_TRUE(ForcesLegacyLayout(*bfc)); - EXPECT_TRUE(ForcesLegacyLayout(*container)); - EXPECT_TRUE(ForcesLegacyLayout(*middle)); - EXPECT_TRUE(ForcesLegacyLayout(*inner)); - EXPECT_TRUE(ForcesLegacyLayout(*child)); + EXPECT_EQ(UsesNGLayout(*bfc), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*container), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*middle), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*inner), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*child), EditingNGEnabled()); // Change a non-inherited property. Legacy layout is triggered by #inner, but // should be propagated all the way up to #container (which is the node that @@ -95,21 +92,21 @@ TEST_F(ForceLegacyLayoutTest, ForceLegacyBfcRecalcAncestorStyle) { middle->setAttribute(html_names::kStyleAttr, "background-color:blue;"); UpdateAllLifecyclePhasesForTest(); EXPECT_TRUE(UsesNGLayout(*body)); - EXPECT_TRUE(ForcesLegacyLayout(*bfc)); - EXPECT_TRUE(ForcesLegacyLayout(*container)); - EXPECT_TRUE(ForcesLegacyLayout(*middle)); - EXPECT_TRUE(ForcesLegacyLayout(*inner)); - EXPECT_TRUE(ForcesLegacyLayout(*child)); + EXPECT_EQ(UsesNGLayout(*bfc), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*container), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*middle), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*inner), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*child), EditingNGEnabled()); // Change a property that requires re-attachment. container->setAttribute(html_names::kStyleAttr, "display:block;"); UpdateAllLifecyclePhasesForTest(); EXPECT_TRUE(UsesNGLayout(*body)); - EXPECT_TRUE(ForcesLegacyLayout(*bfc)); - EXPECT_TRUE(ForcesLegacyLayout(*container)); - EXPECT_TRUE(ForcesLegacyLayout(*middle)); - EXPECT_TRUE(ForcesLegacyLayout(*inner)); - EXPECT_TRUE(ForcesLegacyLayout(*child)); + EXPECT_EQ(UsesNGLayout(*bfc), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*container), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*middle), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*inner), EditingNGEnabled()); + EXPECT_EQ(UsesNGLayout(*child), EditingNGEnabled()); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.cc b/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.cc index 5d3cbbb6de6..90ed61b1a34 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.cc +++ b/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.cc @@ -7,44 +7,24 @@ #include "third_party/blink/renderer/core/layout/geometry/logical_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/layout/geometry/writing_mode_converter.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { -PhysicalOffset LogicalOffset::ConvertToPhysical(WritingMode mode, +PhysicalOffset LogicalOffset::ConvertToPhysical( + WritingDirectionMode writing_direction, + PhysicalSize outer_size, + PhysicalSize inner_size) const { + return WritingModeConverter(writing_direction, outer_size) + .ToPhysical(*this, inner_size); +} + +PhysicalOffset LogicalOffset::ConvertToPhysical(WritingMode writing_mode, TextDirection direction, PhysicalSize outer_size, PhysicalSize inner_size) const { - switch (mode) { - case WritingMode::kHorizontalTb: - if (direction == TextDirection::kLtr) - return PhysicalOffset(inline_offset, block_offset); - return PhysicalOffset(outer_size.width - inline_offset - inner_size.width, - block_offset); - case WritingMode::kVerticalRl: - case WritingMode::kSidewaysRl: - if (direction == TextDirection::kLtr) { - return PhysicalOffset( - outer_size.width - block_offset - inner_size.width, inline_offset); - } - return PhysicalOffset( - outer_size.width - block_offset - inner_size.width, - outer_size.height - inline_offset - inner_size.height); - case WritingMode::kVerticalLr: - if (direction == TextDirection::kLtr) - return PhysicalOffset(block_offset, inline_offset); - return PhysicalOffset( - block_offset, outer_size.height - inline_offset - inner_size.height); - case WritingMode::kSidewaysLr: - if (direction == TextDirection::kLtr) { - return PhysicalOffset(block_offset, outer_size.height - inline_offset - - inner_size.height); - } - return PhysicalOffset(block_offset, inline_offset); - default: - NOTREACHED(); - return PhysicalOffset(); - } + return ConvertToPhysical({writing_mode, direction}, outer_size, inner_size); } String LogicalOffset::ToString() const { diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.h b/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.h index ae0a241fc25..aa737fd1110 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.h +++ b/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset.h @@ -7,8 +7,7 @@ #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" +#include "third_party/blink/renderer/platform/text/writing_direction_mode.h" namespace blink { @@ -38,8 +37,11 @@ struct CORE_EXPORT LogicalOffset { // 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). - PhysicalOffset ConvertToPhysical(WritingMode, - TextDirection, + PhysicalOffset ConvertToPhysical(WritingDirectionMode writing_direction, + PhysicalSize outer_size, + PhysicalSize inner_size) const; + PhysicalOffset ConvertToPhysical(WritingMode writing_mode, + TextDirection direction, PhysicalSize outer_size, PhysicalSize inner_size) const; diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset_test.cc b/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset_test.cc deleted file mode 100644 index 4ee80cf8c75..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/geometry/logical_offset_test.cc +++ /dev/null @@ -1,75 +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/geometry/logical_offset.h" - -#include "testing/gtest/include/gtest/gtest.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/testing/core_unit_test_helper.h" - -namespace blink { - -namespace { - -TEST(GeometryUnitsTest, ConvertLogicalOffsetToPhysicalOffset) { - LogicalOffset logical_offset(20, 30); - PhysicalSize outer_size(300, 400); - PhysicalSize inner_size(5, 65); - PhysicalOffset offset; - - offset = logical_offset.ConvertToPhysical( - WritingMode::kHorizontalTb, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(20, offset.left); - EXPECT_EQ(30, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kHorizontalTb, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(275, offset.left); - EXPECT_EQ(30, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(265, offset.left); - EXPECT_EQ(20, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(265, offset.left); - EXPECT_EQ(315, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(265, offset.left); - EXPECT_EQ(20, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(265, offset.left); - EXPECT_EQ(315, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(30, offset.left); - EXPECT_EQ(20, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kVerticalLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(30, offset.left); - EXPECT_EQ(315, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(30, offset.left); - EXPECT_EQ(315, offset.top); - - offset = logical_offset.ConvertToPhysical( - WritingMode::kSidewaysLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(30, offset.left); - EXPECT_EQ(20, offset.top); -} - -} // namespace - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.cc b/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.cc index 93912b47183..9bd744cbcc9 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.cc +++ b/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.cc @@ -38,25 +38,6 @@ void LogicalRect::Unite(const LogicalRect& other) { size = new_end_offset - offset; } -PhysicalRect LogicalRect::ConvertToPhysical( - WritingMode writing_mode, - const PhysicalSize& outer_size) const { - if (IsHorizontalWritingMode(writing_mode)) { - return {offset.inline_offset, offset.block_offset, size.inline_size, - size.block_size}; - } - - // Vertical, clock-wise rotation. - if (writing_mode != WritingMode::kSidewaysLr) { - return {outer_size.width - BlockEndOffset(), offset.inline_offset, - size.block_size, size.inline_size}; - } - - // Vertical, counter-clock-wise rotation. - return {offset.block_offset, outer_size.height - InlineEndOffset(), - size.block_size, size.inline_size}; -} - String LogicalRect::ToString() const { return String::Format("%s,%s %sx%s", offset.inline_offset.ToString().Ascii().c_str(), diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.h b/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.h index 43d65daef54..b6196671e85 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.h +++ b/chromium/third_party/blink/renderer/core/layout/geometry/logical_rect.h @@ -13,8 +13,6 @@ namespace blink { class LayoutRect; -struct PhysicalRect; -struct PhysicalSize; // LogicalRect is the position and size of a rect (typically a fragment) // relative to the parent in the logical coordinate system. @@ -68,10 +66,6 @@ struct CORE_EXPORT LogicalRect { void Unite(const LogicalRect&); - // Convert logical coordinate to local physical coordinate. - PhysicalRect ConvertToPhysical(WritingMode writing_mode, - const PhysicalSize& outer_size) const; - String ToString() const; }; diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.cc b/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.cc index d2c6d1634f1..a151caea379 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.cc +++ b/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.cc @@ -6,39 +6,26 @@ #include "third_party/blink/renderer/core/layout/geometry/logical_offset.h" #include "third_party/blink/renderer/core/layout/geometry/physical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.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 { -LogicalOffset PhysicalOffset::ConvertToLogical(WritingMode mode, +LogicalOffset PhysicalOffset::ConvertToLogical( + WritingDirectionMode writing_direction, + PhysicalSize outer_size, + PhysicalSize inner_size) const { + return WritingModeConverter(writing_direction, outer_size) + .ToLogical(*this, inner_size); +} + +LogicalOffset PhysicalOffset::ConvertToLogical(WritingMode writing_mode, TextDirection direction, PhysicalSize outer_size, PhysicalSize inner_size) const { - switch (mode) { - case WritingMode::kHorizontalTb: - if (direction == TextDirection::kLtr) - return LogicalOffset(left, top); - return LogicalOffset(outer_size.width - left - inner_size.width, top); - case WritingMode::kVerticalRl: - case WritingMode::kSidewaysRl: - if (direction == TextDirection::kLtr) - return LogicalOffset(top, outer_size.width - left - inner_size.width); - return LogicalOffset(outer_size.height - top - inner_size.height, - outer_size.width - left - inner_size.width); - case WritingMode::kVerticalLr: - if (direction == TextDirection::kLtr) - return LogicalOffset(top, left); - return LogicalOffset(outer_size.height - top - inner_size.height, left); - case WritingMode::kSidewaysLr: - if (direction == TextDirection::kLtr) - return LogicalOffset(outer_size.height - top - inner_size.height, left); - return LogicalOffset(top, left); - default: - NOTREACHED(); - return LogicalOffset(); - } + return ConvertToLogical({writing_mode, direction}, outer_size, inner_size); } String PhysicalOffset::ToString() const { diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.h b/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.h index 20ddb662d16..a2e93b6e48e 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.h +++ b/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset.h @@ -9,8 +9,7 @@ #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" +#include "third_party/blink/renderer/platform/text/writing_direction_mode.h" namespace blink { @@ -38,8 +37,11 @@ struct CORE_EXPORT PhysicalOffset { // 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). - LogicalOffset ConvertToLogical(WritingMode, - TextDirection, + LogicalOffset ConvertToLogical(WritingDirectionMode writing_direction, + PhysicalSize outer_size, + PhysicalSize inner_size) const; + LogicalOffset ConvertToLogical(WritingMode writing_mode, + TextDirection direction, PhysicalSize outer_size, PhysicalSize inner_size) const; @@ -116,6 +118,11 @@ struct CORE_EXPORT PhysicalOffset { LayoutUnit::FromFloatRound(size.Height())}; } + void Scale(float s) { + left *= s; + top *= s; + } + constexpr explicit operator FloatPoint() const { return {left, top}; } constexpr explicit operator FloatSize() const { return {left, top}; } diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset_test.cc b/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset_test.cc deleted file mode 100644 index 767cab1a2b9..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/geometry/physical_offset_test.cc +++ /dev/null @@ -1,75 +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/geometry/physical_offset.h" - -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h" -#include "third_party/blink/renderer/core/layout/geometry/physical_size.h" -#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" - -namespace blink { - -namespace { - -TEST(GeometryUnitsTest, ConvertPhysicalOffsetToLogicalOffset) { - PhysicalOffset physical_offset(20, 30); - PhysicalSize outer_size(300, 400); - PhysicalSize inner_size(5, 65); - LogicalOffset offset; - - offset = physical_offset.ConvertToLogical( - WritingMode::kHorizontalTb, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(20, offset.inline_offset); - EXPECT_EQ(30, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kHorizontalTb, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(275, offset.inline_offset); - EXPECT_EQ(30, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(30, offset.inline_offset); - EXPECT_EQ(275, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(305, offset.inline_offset); - EXPECT_EQ(275, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysRl, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(30, offset.inline_offset); - EXPECT_EQ(275, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysRl, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(305, offset.inline_offset); - EXPECT_EQ(275, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(30, offset.inline_offset); - EXPECT_EQ(20, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kVerticalLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(305, offset.inline_offset); - EXPECT_EQ(20, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysLr, TextDirection::kLtr, outer_size, inner_size); - EXPECT_EQ(305, offset.inline_offset); - EXPECT_EQ(20, offset.block_offset); - - offset = physical_offset.ConvertToLogical( - WritingMode::kSidewaysLr, TextDirection::kRtl, outer_size, inner_size); - EXPECT_EQ(30, offset.inline_offset); - EXPECT_EQ(20, offset.block_offset); -} - -} // namespace - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.cc b/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.cc index 56d55e143a2..706ae77c154 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.cc +++ b/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.cc @@ -13,15 +13,6 @@ namespace blink { -LogicalRect PhysicalRect::ConvertToLogical(WritingMode mode, - TextDirection direction, - PhysicalSize outer_size, - PhysicalSize inner_size) const { - return LogicalRect( - offset.ConvertToLogical(mode, direction, outer_size, inner_size), - size.ConvertToLogical(mode)); -} - bool PhysicalRect::Contains(const PhysicalRect& other) const { return offset.left <= other.offset.left && offset.top <= other.offset.top && Right() >= other.Right() && Bottom() >= other.Bottom(); diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.h b/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.h index 8c8a236c58c..238d9e39add 100644 --- a/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.h +++ b/chromium/third_party/blink/renderer/core/layout/geometry/physical_rect.h @@ -22,7 +22,6 @@ class TextStream; namespace blink { class ComputedStyle; -struct LogicalRect; struct NGPhysicalBoxStrut; // PhysicalRect is the position and size of a rect (typically a fragment) @@ -50,15 +49,6 @@ struct CORE_EXPORT PhysicalRect { PhysicalOffset offset; PhysicalSize size; - // 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). - LogicalRect ConvertToLogical(WritingMode, - TextDirection, - PhysicalSize outer_size, - PhysicalSize inner_size) const; - constexpr bool IsEmpty() const { return size.IsEmpty(); } constexpr LayoutUnit X() const { return offset.left; } diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.cc b/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.cc new file mode 100644 index 00000000000..4de9df959bf --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.cc @@ -0,0 +1,90 @@ +// Copyright 2020 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/geometry/writing_mode_converter.h" + +namespace blink { + +LogicalOffset WritingModeConverter::SlowToLogical( + const PhysicalOffset& offset, + const PhysicalSize& inner_size) const { + switch (GetWritingMode()) { + case WritingMode::kHorizontalTb: + DCHECK(!IsLtr()); // LTR is in the fast code path. + return LogicalOffset(outer_size_.width - offset.left - inner_size.width, + offset.top); + case WritingMode::kVerticalRl: + case WritingMode::kSidewaysRl: + if (IsLtr()) { + return LogicalOffset( + offset.top, outer_size_.width - offset.left - inner_size.width); + } + return LogicalOffset(outer_size_.height - offset.top - inner_size.height, + outer_size_.width - offset.left - inner_size.width); + case WritingMode::kVerticalLr: + if (IsLtr()) + return LogicalOffset(offset.top, offset.left); + return LogicalOffset(outer_size_.height - offset.top - inner_size.height, + offset.left); + case WritingMode::kSidewaysLr: + if (IsLtr()) { + return LogicalOffset( + outer_size_.height - offset.top - inner_size.height, offset.left); + } + return LogicalOffset(offset.top, offset.left); + } + NOTREACHED(); + return LogicalOffset(); +} + +PhysicalOffset WritingModeConverter::SlowToPhysical( + const LogicalOffset& offset, + const PhysicalSize& inner_size) const { + switch (GetWritingMode()) { + case WritingMode::kHorizontalTb: + DCHECK(!IsLtr()); // LTR is in the fast code path. + return PhysicalOffset( + outer_size_.width - offset.inline_offset - inner_size.width, + offset.block_offset); + case WritingMode::kVerticalRl: + case WritingMode::kSidewaysRl: + if (IsLtr()) { + return PhysicalOffset( + outer_size_.width - offset.block_offset - inner_size.width, + offset.inline_offset); + } + return PhysicalOffset( + outer_size_.width - offset.block_offset - inner_size.width, + outer_size_.height - offset.inline_offset - inner_size.height); + case WritingMode::kVerticalLr: + if (IsLtr()) + return PhysicalOffset(offset.block_offset, offset.inline_offset); + return PhysicalOffset( + offset.block_offset, + outer_size_.height - offset.inline_offset - inner_size.height); + case WritingMode::kSidewaysLr: + if (IsLtr()) { + return PhysicalOffset( + offset.block_offset, + outer_size_.height - offset.inline_offset - inner_size.height); + } + return PhysicalOffset(offset.block_offset, offset.inline_offset); + } + NOTREACHED(); + return PhysicalOffset(); +} + +LogicalRect WritingModeConverter::SlowToLogical( + const PhysicalRect& rect) const { + return LogicalRect(SlowToLogical(rect.offset, rect.size), + ToLogical(rect.size)); +} + +PhysicalRect WritingModeConverter::SlowToPhysical( + const LogicalRect& rect) const { + const PhysicalSize size = ToPhysical(rect.size); + return PhysicalRect(SlowToPhysical(rect.offset, size), size); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h b/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h new file mode 100644 index 00000000000..54a1f8a7a1e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h @@ -0,0 +1,124 @@ +// Copyright 2020 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_GEOMETRY_WRITING_MODE_CONVERTER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_GEOMETRY_WRITING_MODE_CONVERTER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" +#include "third_party/blink/renderer/platform/text/writing_direction_mode.h" + +namespace blink { + +// This class represents CSS property values to convert between logical and +// physical coordinate systems. See: +// https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical +class CORE_EXPORT WritingModeConverter { + STACK_ALLOCATED(); + + public: + // @param writing_direction |WritingMode| and |TextDirection|. + // @param outer_size the size of the rect (typically a fragment). Some + // combinations of |WritingMode| and |TextDirection| require the size of the + // container to make the offset relative to the right or the bottom edges. + WritingModeConverter(WritingDirectionMode writing_direction, + const PhysicalSize& outer_size) + : writing_direction_(writing_direction), outer_size_(outer_size) {} + + // Construct without |outer_size|. Caller should call |SetOuterSize| before + // conversions. + explicit WritingModeConverter(WritingDirectionMode writing_direction) + : writing_direction_(writing_direction) {} + + // Conversion properties and utilities. + WritingDirectionMode GetWritingDirection() const { + return writing_direction_; + } + WritingMode GetWritingMode() const { + return writing_direction_.GetWritingMode(); + } + TextDirection Direction() const { return writing_direction_.Direction(); } + bool IsLtr() const { return writing_direction_.IsLtr(); } + + void SetOuterSize(const PhysicalSize& outer_size) { + outer_size_ = outer_size; + } + + // |LogicalOffset| and |PhysicalOffset| conversions. + // 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 inner_size the size of the inner rect (typically a child fragment). + LogicalOffset ToLogical(const PhysicalOffset& offset, + const PhysicalSize& inner_size) const; + PhysicalOffset ToPhysical(const LogicalOffset& offset, + const PhysicalSize& inner_size) const; + + // |LogicalSize| and |PhysicalSize| conversions. + LogicalSize ToLogical(const PhysicalSize& size) const; + PhysicalSize ToPhysical(const LogicalSize& size) const; + + // |LogicalRect| and |PhysicalRect| conversions. + LogicalRect ToLogical(const PhysicalRect& rect) const; + PhysicalRect ToPhysical(const LogicalRect& rect) const; + + private: + LogicalOffset SlowToLogical(const PhysicalOffset& offset, + const PhysicalSize& inner_size) const; + PhysicalOffset SlowToPhysical(const LogicalOffset& offset, + const PhysicalSize& inner_size) const; + + LogicalRect SlowToLogical(const PhysicalRect& rect) const; + PhysicalRect SlowToPhysical(const LogicalRect& rect) const; + + WritingDirectionMode writing_direction_; + PhysicalSize outer_size_; +}; + +inline LogicalOffset WritingModeConverter::ToLogical( + const PhysicalOffset& offset, + const PhysicalSize& inner_size) const { + if (writing_direction_.IsHorizontalLtr()) + return LogicalOffset(offset.left, offset.top); + return SlowToLogical(offset, inner_size); +} + +inline PhysicalOffset WritingModeConverter::ToPhysical( + const LogicalOffset& offset, + const PhysicalSize& inner_size) const { + if (writing_direction_.IsHorizontalLtr()) + return PhysicalOffset(offset.inline_offset, offset.block_offset); + return SlowToPhysical(offset, inner_size); +} + +inline LogicalSize WritingModeConverter::ToLogical( + const PhysicalSize& size) const { + return size.ConvertToLogical(GetWritingMode()); +} + +inline PhysicalSize WritingModeConverter::ToPhysical( + const LogicalSize& size) const { + return ToPhysicalSize(size, GetWritingMode()); +} + +inline LogicalRect WritingModeConverter::ToLogical( + const PhysicalRect& rect) const { + if (writing_direction_.IsHorizontalLtr()) + return LogicalRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + return SlowToLogical(rect); +} + +inline PhysicalRect WritingModeConverter::ToPhysical( + const LogicalRect& rect) const { + if (writing_direction_.IsHorizontalLtr()) { + return PhysicalRect(rect.offset.inline_offset, rect.offset.block_offset, + rect.size.inline_size, rect.size.block_size); + } + return SlowToPhysical(rect); +} + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_GEOMETRY_WRITING_MODE_CONVERTER_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter_test.cc b/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter_test.cc new file mode 100644 index 00000000000..9ff283d175d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/geometry/writing_mode_converter_test.cc @@ -0,0 +1,130 @@ +// 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/geometry/writing_mode_converter.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" + +namespace blink { + +namespace { + +TEST(WritingModeConverterTest, ConvertLogicalOffsetToPhysicalOffset) { + LogicalOffset logical_offset(20, 30); + PhysicalSize outer_size(300, 400); + PhysicalSize inner_size(5, 65); + PhysicalOffset offset; + + offset = WritingModeConverter( + {WritingMode::kHorizontalTb, TextDirection::kLtr}, outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(20, 30), offset); + + offset = WritingModeConverter( + {WritingMode::kHorizontalTb, TextDirection::kRtl}, outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(275, 30), offset); + + offset = WritingModeConverter({WritingMode::kVerticalRl, TextDirection::kLtr}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(265, 20), offset); + + offset = WritingModeConverter({WritingMode::kVerticalRl, TextDirection::kRtl}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(265, 315), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysRl, TextDirection::kLtr}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(265, 20), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysRl, TextDirection::kRtl}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(265, 315), offset); + + offset = WritingModeConverter({WritingMode::kVerticalLr, TextDirection::kLtr}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(30, 20), offset); + + offset = WritingModeConverter({WritingMode::kVerticalLr, TextDirection::kRtl}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(30, 315), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysLr, TextDirection::kLtr}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(30, 315), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysLr, TextDirection::kRtl}, + outer_size) + .ToPhysical(logical_offset, inner_size); + EXPECT_EQ(PhysicalOffset(30, 20), offset); +} + +TEST(WritingModeConverterTest, ConvertPhysicalOffsetToLogicalOffset) { + PhysicalOffset physical_offset(20, 30); + PhysicalSize outer_size(300, 400); + PhysicalSize inner_size(5, 65); + LogicalOffset offset; + + offset = WritingModeConverter( + {WritingMode::kHorizontalTb, TextDirection::kLtr}, outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(20, 30), offset); + + offset = WritingModeConverter( + {WritingMode::kHorizontalTb, TextDirection::kRtl}, outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(275, 30), offset); + + offset = WritingModeConverter({WritingMode::kVerticalRl, TextDirection::kLtr}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(30, 275), offset); + + offset = WritingModeConverter({WritingMode::kVerticalRl, TextDirection::kRtl}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(305, 275), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysRl, TextDirection::kLtr}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(30, 275), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysRl, TextDirection::kRtl}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(305, 275), offset); + + offset = WritingModeConverter({WritingMode::kVerticalLr, TextDirection::kLtr}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(30, 20), offset); + + offset = WritingModeConverter({WritingMode::kVerticalLr, TextDirection::kRtl}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(305, 20), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysLr, TextDirection::kLtr}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(305, 20), offset); + + offset = WritingModeConverter({WritingMode::kSidewaysLr, TextDirection::kRtl}, + outer_size) + .ToLogical(physical_offset, inner_size); + EXPECT_EQ(LogicalOffset(30, 20), offset); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/grid.cc b/chromium/third_party/blink/renderer/core/layout/grid.cc index d86bcfc238c..64bac6a4b0f 100644 --- a/chromium/third_party/blink/renderer/core/layout/grid.cc +++ b/chromium/third_party/blink/renderer/core/layout/grid.cc @@ -28,13 +28,13 @@ std::unique_ptr<Grid> Grid::Create(const LayoutGrid* layout_grid) { Grid::Grid(const LayoutGrid* grid) : order_iterator_(grid) {} -void Grid::SetSmallestTracksStart(int row_start, int column_start) { - smallest_row_start_ = row_start; - smallest_column_start_ = column_start; +void Grid::SetExplicitGridStart(size_t row_start, size_t column_start) { + explicit_row_start_ = row_start; + explicit_column_start_ = column_start; } -int Grid::SmallestTrackStart(GridTrackSizingDirection direction) const { - return direction == kForRows ? smallest_row_start_ : smallest_column_start_; +size_t Grid::ExplicitGridStart(GridTrackSizingDirection direction) const { + return direction == kForRows ? explicit_row_start_ : explicit_column_start_; } GridArea Grid::GridItemArea(const LayoutBox& item) const { @@ -119,8 +119,8 @@ void Grid::SetNeedsItemsPlacement(bool needs_items_placement) { ClearGridDataStructure(); grid_item_area_.clear(); grid_items_indexes_map_.clear(); - smallest_row_start_ = 0; - smallest_column_start_ = 0; + explicit_row_start_ = 0; + explicit_column_start_ = 0; auto_repeat_columns_ = 0; auto_repeat_rows_ = 0; auto_repeat_empty_columns_ = nullptr; diff --git a/chromium/third_party/blink/renderer/core/layout/grid.h b/chromium/third_party/blink/renderer/core/layout/grid.h index 528bed93c14..2831f8a8bf4 100644 --- a/chromium/third_party/blink/renderer/core/layout/grid.h +++ b/chromium/third_party/blink/renderer/core/layout/grid.h @@ -57,8 +57,8 @@ class CORE_EXPORT Grid { size_t GridItemPaintOrder(const LayoutBox&) const; void SetGridItemPaintOrder(const LayoutBox&, size_t order); - int SmallestTrackStart(GridTrackSizingDirection) const; - void SetSmallestTracksStart(int row_start, int column_start); + size_t ExplicitGridStart(GridTrackSizingDirection) const; + void SetExplicitGridStart(size_t row_start, size_t column_start); size_t AutoRepeatTracks(GridTrackSizingDirection) const; void SetAutoRepeatTracks(size_t auto_repeat_rows, size_t auto_repeat_columns); @@ -124,8 +124,8 @@ class CORE_EXPORT Grid { OrderIterator order_iterator_; - int smallest_column_start_{0}; - int smallest_row_start_{0}; + size_t explicit_column_start_{0}; + size_t explicit_row_start_{0}; size_t auto_repeat_columns_{0}; size_t auto_repeat_rows_{0}; diff --git a/chromium/third_party/blink/renderer/core/layout/grid_test.cc b/chromium/third_party/blink/renderer/core/layout/grid_test.cc index e9779901a9d..2ce29e71b0f 100644 --- a/chromium/third_party/blink/renderer/core/layout/grid_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/grid_test.cc @@ -36,8 +36,8 @@ TEST_F(GridTest, EmptyGrid) { EXPECT_FALSE(grid->HasGridItems()); - EXPECT_EQ(0, grid->SmallestTrackStart(kForRows)); - EXPECT_EQ(0, grid->SmallestTrackStart(kForColumns)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForRows)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForColumns)); EXPECT_EQ(0u, grid->AutoRepeatTracks(kForRows)); EXPECT_EQ(0u, grid->AutoRepeatTracks(kForColumns)); @@ -65,8 +65,8 @@ TEST_F(GridTest, SingleChild) { EXPECT_TRUE(grid->HasGridItems()); - EXPECT_EQ(0, grid->SmallestTrackStart(kForRows)); - EXPECT_EQ(0, grid->SmallestTrackStart(kForColumns)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForRows)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForColumns)); auto area = grid->GridItemArea(*child); EXPECT_EQ(0u, area.columns.StartLine()); @@ -166,8 +166,8 @@ TEST_F(GridTest, IntrinsicGrid) { EXPECT_TRUE(grid->HasGridItems()); - EXPECT_EQ(-2, grid->SmallestTrackStart(kForRows)); - EXPECT_EQ(0, grid->SmallestTrackStart(kForColumns)); + EXPECT_EQ(2u, grid->ExplicitGridStart(kForRows)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForColumns)); auto area = grid->GridItemArea(*child1); EXPECT_EQ(0u, area.columns.StartLine()); @@ -291,8 +291,8 @@ TEST_F(GridTest, ExplicitlyPositionedChild) { EXPECT_TRUE(grid->HasGridItems()); - EXPECT_EQ(0, grid->SmallestTrackStart(kForRows)); - EXPECT_EQ(0, grid->SmallestTrackStart(kForColumns)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForRows)); + EXPECT_EQ(0u, grid->ExplicitGridStart(kForColumns)); auto area = grid->GridItemArea(*child); EXPECT_EQ(1u, area.columns.StartLine()); diff --git a/chromium/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc index 16d8d1e7d2c..fc0baf9c2a4 100644 --- a/chromium/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc @@ -798,7 +798,9 @@ LayoutUnit IndefiniteSizeStrategy::MaxContentForChild(LayoutBox& child) const { DCHECK(GridLayoutUtils::IsOrthogonalChild(*GetLayoutGrid(), child)); return child.LogicalHeight() + - GridLayoutUtils::MarginLogicalHeightForChild(*GetLayoutGrid(), child); + GridLayoutUtils::MarginLogicalHeightForChild(*GetLayoutGrid(), child) + + algorithm_.BaselineOffsetForChild(child, + GridAxisForDirection(Direction())); } bool IndefiniteSizeStrategy::IsComputingSizeContainment() const { @@ -875,7 +877,7 @@ const GridTrackSize& GridTrackSizingAlgorithm::RawGridTrackSize( size_t explicit_tracks_count = track_styles.size() + auto_repeat_tracks_count; int untranslated_index_as_int = - translated_index + grid_.SmallestTrackStart(direction); + translated_index - grid_.ExplicitGridStart(direction); size_t auto_track_styles_size = auto_track_styles.size(); if (untranslated_index_as_int < 0) { int index = @@ -946,8 +948,20 @@ GridTrackSize GridTrackSizingAlgorithm::CalculateGridTrackSize( // values are treated as <auto>. if (IsRelativeSizedTrackAsAuto(track_size, direction)) { if (direction == kForRows) { - UseCounter::Count(layout_grid_->GetDocument(), - WebFeature::kGridRowTrackPercentIndefiniteHeight); + // We avoid counting the cases in which it doesn't matter if we resolve + // the percentages row tracks against the intrinsic height of the grid + // container or we treat them as auto. Basically if we have just one row, + // it has 100% size and the max-block-size is none. + if ((grid_.NumTracks(direction) != 1) || !min_track_breadth.IsLength() || + !min_track_breadth.length().IsPercent() || + (min_track_breadth.length().Percent() != 100.0f) || + !max_track_breadth.IsLength() || + !max_track_breadth.length().IsPercent() || + (max_track_breadth.length().Percent() != 100.0f) || + !layout_grid_->StyleRef().LogicalMaxHeight().IsNone()) { + UseCounter::Count(layout_grid_->GetDocument(), + WebFeature::kGridRowTrackPercentIndefiniteHeight); + } } if (min_track_breadth.HasPercentage()) min_track_breadth = Length::Auto(); diff --git a/chromium/third_party/blink/renderer/core/layout/hit_test_cache.cc b/chromium/third_party/blink/renderer/core/layout/hit_test_cache.cc index 5a46d3b68cb..9a007761a6a 100644 --- a/chromium/third_party/blink/renderer/core/layout/hit_test_cache.cc +++ b/chromium/third_party/blink/renderer/core/layout/hit_test_cache.cc @@ -40,7 +40,7 @@ bool HitTestCache::LookupCachedResult(const HitTestLocation& location, return result; } -void HitTestCacheEntry::Trace(Visitor* visitor) { +void HitTestCacheEntry::Trace(Visitor* visitor) const { visitor->Trace(result); } @@ -85,7 +85,7 @@ void HitTestCache::Clear() { items_.clear(); } -void HitTestCache::Trace(Visitor* visitor) { +void HitTestCache::Trace(Visitor* visitor) const { visitor->Trace(items_); } diff --git a/chromium/third_party/blink/renderer/core/layout/hit_test_cache.h b/chromium/third_party/blink/renderer/core/layout/hit_test_cache.h index 985e58c1840..b4d0fce618b 100644 --- a/chromium/third_party/blink/renderer/core/layout/hit_test_cache.h +++ b/chromium/third_party/blink/renderer/core/layout/hit_test_cache.h @@ -37,7 +37,7 @@ namespace blink { struct HitTestCacheEntry { DISALLOW_NEW(); - void Trace(Visitor*); + void Trace(Visitor*) const; HitTestLocation location; HitTestResult result; @@ -61,7 +61,7 @@ class CORE_EXPORT HitTestCache final : public GarbageCollected<HitTestCache> { const HitTestResult&, uint64_t dom_tree_version); - void Trace(Visitor*); + void Trace(Visitor*) const; private: // The below UMA values reference a validity region. This code has not diff --git a/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.cc b/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.cc index 79e9b485d94..4568de680e0 100644 --- a/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.cc +++ b/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.cc @@ -17,7 +17,7 @@ Element* HitTestCanvasResult::GetControl() const { return control_.Get(); } -void HitTestCanvasResult::Trace(Visitor* visitor) { +void HitTestCanvasResult::Trace(Visitor* visitor) const { visitor->Trace(control_); } diff --git a/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.h b/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.h index 4dcc8d19cdb..773f1cdff2f 100644 --- a/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.h +++ b/chromium/third_party/blink/renderer/core/layout/hit_test_canvas_result.h @@ -17,7 +17,7 @@ class CORE_EXPORT HitTestCanvasResult final String GetId() const; Element* GetControl() const; - void Trace(Visitor*); + void Trace(Visitor*) const; private: String id_; diff --git a/chromium/third_party/blink/renderer/core/layout/hit_test_result.cc b/chromium/third_party/blink/renderer/core/layout/hit_test_result.cc index 68ac6dfdaa8..1359aa6bdd5 100644 --- a/chromium/third_party/blink/renderer/core/layout/hit_test_result.cc +++ b/chromium/third_party/blink/renderer/core/layout/hit_test_result.cc @@ -128,7 +128,7 @@ void HitTestResult::PopulateFromCachedResult(const HitTestResult& other) { : nullptr; } -void HitTestResult::Trace(Visitor* visitor) { +void HitTestResult::Trace(Visitor* visitor) const { visitor->Trace(inner_node_); visitor->Trace(inert_node_); visitor->Trace(inner_element_); diff --git a/chromium/third_party/blink/renderer/core/layout/hit_test_result.h b/chromium/third_party/blink/renderer/core/layout/hit_test_result.h index d40ba852d00..3db892ee822 100644 --- a/chromium/third_party/blink/renderer/core/layout/hit_test_result.h +++ b/chromium/third_party/blink/renderer/core/layout/hit_test_result.h @@ -67,7 +67,7 @@ class CORE_EXPORT HitTestResult { HitTestResult(const HitTestResult&); ~HitTestResult(); HitTestResult& operator=(const HitTestResult&); - void Trace(Visitor*); + void Trace(Visitor*) const; bool EqualForCacheability(const HitTestResult&) const; void CacheValues(const HitTestResult& other); diff --git a/chromium/third_party/blink/renderer/core/layout/intrinsic_sizing_info.h b/chromium/third_party/blink/renderer/core/layout/intrinsic_sizing_info.h index 3c75bfad4a4..8fcac461125 100644 --- a/chromium/third_party/blink/renderer/core/layout/intrinsic_sizing_info.h +++ b/chromium/third_party/blink/renderer/core/layout/intrinsic_sizing_info.h @@ -15,6 +15,9 @@ struct IntrinsicSizingInfo { IntrinsicSizingInfo() : has_width(true), has_height(true) {} + // Both size and aspect_ratio use logical coordinates. + // Because they are using float instead of LayoutUnit, we can't use + // LogicalSize here. FloatSize size; FloatSize aspect_ratio; bool has_width; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_block.cc b/chromium/third_party/blink/renderer/core/layout/layout_block.cc index 4e97d89ff2f..ce16a5a2801 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_block.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_block.cc @@ -442,6 +442,8 @@ void LayoutBlock::AddVisualOverflowFromChildren() { if (PrePaintBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)) return; + DCHECK(!NeedsLayout()); + if (ChildrenInline()) To<LayoutBlockFlow>(this)->AddVisualOverflowFromInlineChildren(); else @@ -459,6 +461,8 @@ void LayoutBlock::AddLayoutOverflowFromChildren() { } void LayoutBlock::ComputeVisualOverflow(bool) { + DCHECK(!SelfNeedsLayout()); + LayoutRect previous_visual_overflow_rect = VisualOverflowRect(); ClearVisualOverflow(); AddVisualOverflowFromChildren(); @@ -499,6 +503,18 @@ void LayoutBlock::ComputeLayoutOverflow(LayoutUnit old_client_after_edge, LayoutUnit(1)); AddLayoutOverflow(rect_to_apply); SetLayoutClientAfterEdge(old_client_after_edge); + + if (PaddingEnd() && !ChildrenInline()) { + EOverflow overflow = StyleRef().OverflowInlineDirection(); + if (overflow == EOverflow::kAuto) { + UseCounter::Count(GetDocument(), + WebFeature::kInlineOverflowAutoWithInlineEndPadding); + } else if (overflow == EOverflow::kScroll) { + UseCounter::Count( + GetDocument(), + WebFeature::kInlineOverflowScrollWithInlineEndPadding); + } + } } } @@ -959,6 +975,7 @@ void LayoutBlock::InsertPositionedObject(LayoutBox* o) { if (container_map_it->value == this) { DCHECK(HasPositionedObjects()); DCHECK(PositionedObjects()->Contains(o)); + PositionedObjects()->AppendOrMoveToLast(o); return; } RemovePositionedObject(o); @@ -1667,7 +1684,8 @@ void LayoutBlock::ComputeChildPreferredLogicalWidths( const Length& computed_inline_size = child.StyleRef().LogicalWidth(); if (computed_inline_size.IsMaxContent()) min_preferred_logical_width = max_preferred_logical_width; - else if (computed_inline_size.IsMinContent()) + else if (computed_inline_size.IsMinContent() || + computed_inline_size.IsMinIntrinsic()) max_preferred_logical_width = min_preferred_logical_width; } } @@ -1684,9 +1702,15 @@ LayoutUnit LayoutBlock::EmptyLineBaseline( LineDirectionMode line_direction) const { if (!HasLineIfEmpty()) return LayoutUnit(-1); + const auto baseline_offset = BaselineForEmptyLine(line_direction); + return baseline_offset ? *baseline_offset : LayoutUnit(-1); +} + +base::Optional<LayoutUnit> LayoutBlock::BaselineForEmptyLine( + LineDirectionMode line_direction) const { const SimpleFontData* font_data = FirstLineStyle()->GetFont().PrimaryFont(); if (!font_data) - return LayoutUnit(-1); + return base::nullopt; const auto& font_metrics = font_data->GetFontMetrics(); const LayoutUnit line_height = LineHeight(true, line_direction, kPositionOfInteriorLineBoxes); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_block.h b/chromium/third_party/blink/renderer/core/layout/layout_block.h index 68f16211d48..c729be28a63 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_block.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_block.h @@ -401,6 +401,12 @@ class CORE_EXPORT LayoutBlock : public LayoutBox { void UpdateAfterLayout() override; MinMaxSizes PreferredLogicalWidths() const override; + virtual bool HasLineIfEmpty() const; + // Returns baseline offset if we can get |SimpleFontData| from primary font. + // Or returns no value if we can't get font data. + base::Optional<LayoutUnit> BaselineForEmptyLine( + LineDirectionMode line_direction) const; + protected: virtual void AdjustInlineDirectionLineBounds( unsigned /* expansionOpportunityCount */, @@ -435,8 +441,6 @@ class CORE_EXPORT LayoutBlock : public LayoutBox { // this object even if overflow is non-visible. virtual bool AllowsOverflowClip() const; - virtual bool HasLineIfEmpty() const; - bool SimplifiedLayout(); virtual void SimplifiedNormalFlowLayout(); @@ -477,6 +481,9 @@ class CORE_EXPORT LayoutBlock : public LayoutBox { hit_test_action == kHitTestChildBlockBackground; } + // Returns baseline offset of this block if is empty editable or having + // CSS property "--internal-empty-line-height"fabricated", otherwise + // returns |LayoutUnit(-1)|. LayoutUnit EmptyLineBaseline(LineDirectionMode line_direction) const; private: diff --git a/chromium/third_party/blink/renderer/core/layout/layout_block_flow.cc b/chromium/third_party/blink/renderer/core/layout/layout_block_flow.cc index 6214dce7169..ee76ca597cd 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_block_flow.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_block_flow.cc @@ -255,16 +255,15 @@ class BlockChildrenLayoutInfo { bool IsAtFirstInFlowChild() const { return is_at_first_in_flow_child_; } void ClearIsAtFirstInFlowChild() { is_at_first_in_flow_child_ = false; } - // The page name of the previous sibling. Consecutive siblings with the same - // name are allowed on the same page, but if they differ, we need a page - // break. - const AtomicString& ChildPageName() const { return child_page_name_; } - void SetChildPageName(const AtomicString& name) { child_page_name_ = name; } + const AtomicString& PreviousEndPage() const { return previous_end_page_; } + void SetPreviousEndPage(const AtomicString& name) { + previous_end_page_ = name; + } private: MultiColumnLayoutState multi_column_layout_state_; MarginInfo margin_info_; - AtomicString child_page_name_; + AtomicString previous_end_page_; LayoutUnit previous_float_logical_bottom_; EBreakBetween previous_break_after_value_; bool is_at_first_in_flow_child_; @@ -595,6 +594,9 @@ void LayoutBlockFlow::ResetLayout() { // [1] https://drafts.csswg.org/css-break/#possible-breaks SetBreakBefore(LayoutBlock::BreakBefore()); SetBreakAfter(LayoutBlock::BreakAfter()); + + SetPropagatedStartPageName(AtomicString()); + SetPropagatedEndPageName(AtomicString()); } } @@ -839,12 +841,49 @@ bool LayoutBlockFlow::PositionAndLayoutOnceIfNeeded( void LayoutBlockFlow::InsertForcedBreakBeforeChildIfNeeded( LayoutBox& child, BlockChildrenLayoutInfo& layout_info) { + LayoutState* layout_state = View()->GetLayoutState(); + + // If the child has a start/end page name, that's the current name. Otherwise + // we'll use the input page name of this block (the name specified by this + // block, or by an ancestor). Adjacent siblings with the same page name may be + // placed on the same page. Otherwise, if there's a mismatch between the + // previous end page name and the current start page name, we need a break, + // except before the first in-flow child, since there's no valid class A + // breakpoint there. + const AtomicString child_start_page = child.StartPageName(); + const AtomicString child_end_page = child.EndPageName(); + const AtomicString& current_start_page = + child_start_page ? child_start_page : layout_state->InputPageName(); + const AtomicString& current_end_page = + child_end_page ? child_end_page : layout_state->InputPageName(); + bool page_name_has_changed = + current_start_page != layout_info.PreviousEndPage(); + + // Page name changes are detected above by comparing the previous end page + // name and the current start page name. We're now storing the current *end* + // page name, for the next sibling to use in its comparison. This means that + // we're not paying any attention to any page name changes within the current + // child. That's fine, though. We're done with this child, and we've already + // inserted any named page breaks that were needed inside the child. Note that + // all of that will be discarded and re-laid out, if it turns out that we need + // a break before this child as well. This is how block fragmentation works; + // if we insert a break in front of something that we've laid out, we need + // another deep layout pass of all subsequent content, since pagination struts + // (or the whereabouts of the fragmentation boundary relative to the child) + // may change. + layout_info.SetPreviousEndPage(current_end_page); + if (layout_info.IsAtFirstInFlowChild()) { // There's no class A break point before the first child (only *between* // siblings), so steal its break value and join it with what we already have // here. SetBreakBefore( JoinFragmentainerBreakValues(BreakBefore(), child.BreakBefore())); + + // Similarly, since there's no valid class A breakpoint here, if the first + // child has a start page name associated, it will be propagated upwards. + SetPropagatedStartPageName(child_start_page); + return; } @@ -854,21 +893,7 @@ void LayoutBlockFlow::InsertForcedBreakBeforeChildIfNeeded( EBreakBetween class_a_break_point_value = child.ClassABreakPointValue(layout_info.PreviousBreakAfterValue()); - bool is_named_page_break; - if (layout_info.ChildPageName()) { - // Adjacent siblings with the same page name may be put on the same - // page. Otherwise, we need a break. - is_named_page_break = - layout_info.ChildPageName() != child.StyleRef().Page(); - } else { - // If the previous sibling (if any) didn't specify a page name, see if one - // is specified on an ancestor. If the child specifies a page name, and it - // doesn't match what's specified further up (if anything), we need a break. - is_named_page_break = - child.StyleRef().Page() && - child.StyleRef().Page() != View()->GetLayoutState()->PageName(); - } - if (is_named_page_break) + if (page_name_has_changed && IsBreakBetweenControllable(EBreakBetween::kPage)) class_a_break_point_value = EBreakBetween::kPage; if (IsForcedFragmentainerBreakValue(class_a_break_point_value)) { @@ -879,14 +904,13 @@ void LayoutBlockFlow::InsertForcedBreakBeforeChildIfNeeded( SetLogicalHeight(new_logical_top); LayoutUnit pagination_strut = new_logical_top - old_logical_top; child.SetPaginationStrut(pagination_strut); - if (is_named_page_break) { + if (page_name_has_changed) { // This was a forced break because of named pages. We now need to store // the page number where this happened, so that we can apply the right // descriptors (size, margins, page-orientation, etc.) when printing the // page. - layout_info.SetChildPageName(child.StyleRef().Page()); if (NamedPagesMapper* mapper = View()->GetNamedPagesMapper()) { - mapper->AddNamedPage(child.StyleRef().Page(), + mapper->AddNamedPage(current_start_page, CurrentPageNumber(new_logical_top)); } } @@ -2266,13 +2290,18 @@ void LayoutBlockFlow::HandleAfterSideOfBlock(LayoutBox* last_child, // Update our bottom collapsed margin info. SetCollapsedBottomMargin(margin_info); - // There's no class A break point right after the last child, only *between* - // siblings. So propagate the break-after value, and keep looking for a class - // A break point (at the next in-flow block-level object), where we'll join - // this break-after value with the break-before value there. - if (View()->GetLayoutState()->IsPaginated() && last_child) + if (View()->GetLayoutState()->IsPaginated() && last_child) { + // There's no class A break point right after the last child, only *between* + // siblings. So propagate the break-after value, and keep looking for a + // class A break point (at the next in-flow block-level object), where we'll + // join this break-after value with the break-before value there. SetBreakAfter( JoinFragmentainerBreakValues(BreakAfter(), last_child->BreakAfter())); + + // Similarly, since there's no valid class A breakpoint here, if the last + // child has a end page name associated, it will be propagated upwards. + SetPropagatedEndPageName(last_child->EndPageName()); + } } void LayoutBlockFlow::SetMaxMarginBeforeValues(LayoutUnit pos, LayoutUnit neg) { @@ -2356,9 +2385,12 @@ EBreakBetween LayoutBlockFlow::BreakAfter() const { } void LayoutBlockFlow::AddVisualOverflowFromFloats() { - if (!floating_objects_) + if (PrePaintBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren) || + !floating_objects_) return; + DCHECK(!NeedsLayout()); + for (auto& floating_object : floating_objects_->Set()) { if (floating_object->IsDescendant()) { AddVisualOverflowFromChild( @@ -2371,7 +2403,10 @@ void LayoutBlockFlow::AddVisualOverflowFromFloats() { void LayoutBlockFlow::AddVisualOverflowFromFloats( const NGPhysicalContainerFragment& fragment) { + DCHECK(!NeedsLayout()); + DCHECK(!PrePaintBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)); DCHECK(fragment.HasFloatingDescendantsForPaint()); + for (const NGLink& child : fragment.Children()) { if (child->HasSelfPaintingLayer()) continue; @@ -2391,7 +2426,8 @@ void LayoutBlockFlow::AddVisualOverflowFromFloats( } void LayoutBlockFlow::AddLayoutOverflowFromFloats() { - if (!floating_objects_) + if (LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren) || + !floating_objects_) return; for (auto& floating_object : floating_objects_->Set()) { @@ -2416,6 +2452,8 @@ const NGFragmentItems* LayoutBlockFlow::FragmentItems() const { void LayoutBlockFlow::ComputeVisualOverflow( bool recompute_floats) { + DCHECK(!SelfNeedsLayout()); + LayoutRect previous_visual_overflow_rect = VisualOverflowRect(); ClearVisualOverflow(); AddVisualOverflowFromChildren(); @@ -2427,6 +2465,7 @@ void LayoutBlockFlow::ComputeVisualOverflow( (recompute_floats || CreatesNewFormattingContext() || HasSelfPaintingLayer())) AddVisualOverflowFromFloats(); + if (VisualOverflowRect() != previous_visual_overflow_rect) { InvalidateIntersectionObserverCachedRects(); SetShouldCheckForPaintInvalidation(); @@ -3059,8 +3098,7 @@ void LayoutBlockFlow::RemoveChild(LayoutObject* old_child) { // If we are an empty anonymous block in the continuation chain, // we need to remove ourself and fix the continuation chain. - if (!BeingDestroyed() && IsAnonymousBlockContinuation() && - !old_child->IsListMarker()) { + if (!BeingDestroyed() && IsAnonymousBlockContinuation()) { LayoutObject* containing_block_ignoring_anonymous = ContainingBlock(); while (containing_block_ignoring_anonymous && containing_block_ignoring_anonymous->IsAnonymous()) @@ -4070,14 +4108,14 @@ bool LayoutBlockFlow::HitTestFloats(HitTestResult& result, return false; } -PhysicalOffset LayoutBlockFlow::AccumulateInFlowPositionOffsets() const { +PhysicalOffset LayoutBlockFlow::AccumulateRelativePositionOffsets() const { if (!IsAnonymousBlock() || !IsInFlowPositioned()) return PhysicalOffset(); PhysicalOffset offset; for (const LayoutObject* p = InlineElementContinuation(); p && p->IsLayoutInline(); p = p->Parent()) { if (p->IsInFlowPositioned()) - offset += ToLayoutInline(p)->OffsetForInFlowPosition(); + offset += ToLayoutInline(p)->RelativePositionOffset(); } return offset; } @@ -4247,6 +4285,44 @@ void LayoutBlockFlow::SetFirstForcedBreakOffset(LayoutUnit block_offset) { rare_data_->first_forced_break_offset_ = block_offset; } +const AtomicString LayoutBlockFlow::StartPageName() const { + if (const AtomicString& propagated_name = PropagatedStartPageName()) + return propagated_name; + return StyleRef().Page(); +} + +const AtomicString LayoutBlockFlow::EndPageName() const { + if (const AtomicString& propagated_name = PropagatedEndPageName()) + return propagated_name; + return StyleRef().Page(); +} + +const AtomicString LayoutBlockFlow::PropagatedStartPageName() const { + if (!rare_data_) + return AtomicString(); + return rare_data_->propagated_start_page_name_; +} + +void LayoutBlockFlow::SetPropagatedStartPageName(const AtomicString& name) { + if (name.IsEmpty() && !rare_data_) + return; + LayoutBlockFlowRareData& rare_data = EnsureRareData(); + rare_data.propagated_start_page_name_ = name; +} + +const AtomicString LayoutBlockFlow::PropagatedEndPageName() const { + if (!rare_data_) + return AtomicString(); + return rare_data_->propagated_end_page_name_; +} + +void LayoutBlockFlow::SetPropagatedEndPageName(const AtomicString& name) { + if (name.IsEmpty() && !rare_data_) + return; + LayoutBlockFlowRareData& rare_data = EnsureRareData(); + rare_data.propagated_end_page_name_ = name; +} + void LayoutBlockFlow::PositionSpannerDescendant( LayoutMultiColumnSpannerPlaceholder& child) { LayoutBox& spanner = *child.LayoutObjectInFlowThread(); @@ -4263,6 +4339,7 @@ bool LayoutBlockFlow::CreatesNewFormattingContext() const { IsDocumentElement() || IsGridItem() || IsWritingModeRoot() || IsMathItem() || StyleRef().Display() == EDisplay::kFlowRoot || ShouldApplyPaintContainment() || ShouldApplyLayoutContainment() || + StyleRef().IsDeprecatedWebkitBoxWithVerticalLineClamp() || StyleRef().SpecifiesColumns() || StyleRef().GetColumnSpan() == EColumnSpan::kAll) { // The specs require this object to establish a new formatting context. @@ -4468,7 +4545,7 @@ void LayoutBlockFlow::RecalcFloatingDescendantsVisualOverflow( const NGPhysicalContainerFragment& fragment) { DCHECK(fragment.HasFloatingDescendantsForPaint()); - for (const NGLink& child : fragment.Children()) { + for (const NGLink& child : fragment.PostLayoutChildren()) { if (child->IsFloating()) { child->GetMutableLayoutObject() ->RecalcNormalFlowChildVisualOverflowIfNeeded(); @@ -4574,13 +4651,8 @@ PositionWithAffinity LayoutBlockFlow::PositionForPoint( } } - bool move_caret_to_boundary = - GetDocument() - .GetFrame() - ->GetEditor() - .Behavior() - .ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(); - + const bool move_caret_to_boundary = + ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(); if (!move_caret_to_boundary && !closest_box && last_root_box_with_children) { // y coordinate is below last root line box, pretend we hit it closest_box = @@ -4644,6 +4716,15 @@ PositionWithAffinity LayoutBlockFlow::PositionForPoint( return CreatePositionWithAffinity(0); } +bool LayoutBlockFlow::ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom() + const { + return GetDocument() + .GetFrame() + ->GetEditor() + .Behavior() + .ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(); +} + #if DCHECK_IS_ON() void LayoutBlockFlow::ShowLineTreeAndMark(const InlineBox* marked_box1, diff --git a/chromium/third_party/blink/renderer/core/layout/layout_block_flow.h b/chromium/third_party/blink/renderer/core/layout/layout_block_flow.h index e7f1a334a53..b36394a18dd 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_block_flow.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_block_flow.h @@ -356,6 +356,9 @@ class CORE_EXPORT LayoutBlockFlow : public LayoutBlock { } void SetFirstForcedBreakOffset(LayoutUnit); + const AtomicString StartPageName() const final; + const AtomicString EndPageName() const final; + void PositionSpannerDescendant(LayoutMultiColumnSpannerPlaceholder& child); bool CreatesNewFormattingContext() const override; @@ -433,6 +436,7 @@ class CORE_EXPORT LayoutBlockFlow : public LayoutBlock { PositionWithAffinity PositionForPoint(const PhysicalOffset&) const override; PositionWithAffinity PositionForPoint(const LayoutObject& offset_parent, const PhysicalOffset& offset) const; + bool ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom() const; LayoutUnit LowestFloatLogicalBottom(EClear = EClear::kBoth) const; @@ -535,7 +539,7 @@ class CORE_EXPORT LayoutBlockFlow : public LayoutBlock { const PhysicalOffset& accumulated_offset, HitTestAction) override; - PhysicalOffset AccumulateInFlowPositionOffsets() const override; + PhysicalOffset AccumulateRelativePositionOffsets() const override; private: void ResetLayout(); @@ -669,6 +673,16 @@ class CORE_EXPORT LayoutBlockFlow : public LayoutBlock { return rare_data_ && rare_data_->did_break_at_line_to_avoid_widow_; } + // Start page name propagated from the first child, if there are children, and + // the first child has a start page name associated with it. + const AtomicString PropagatedStartPageName() const; + void SetPropagatedStartPageName(const AtomicString&); + + // End page name propagated from the last child, if there are children, and + // the last child has a end page name associated with it. + const AtomicString PropagatedEndPageName() const; + void SetPropagatedEndPageName(const AtomicString&); + public: struct FloatWithRect { DISALLOW_NEW(); @@ -753,7 +767,7 @@ class CORE_EXPORT LayoutBlockFlow : public LayoutBlock { return (-block->MarginAfter()).ClampNegativeToZero(); } - void Trace(Visitor*) {} + void Trace(Visitor*) const {} MarginValues margins_; LayoutUnit pagination_strut_propagated_from_child_; @@ -768,6 +782,14 @@ class CORE_EXPORT LayoutBlockFlow : public LayoutBlock { // |offset_mapping_| here. std::unique_ptr<NGOffsetMapping> offset_mapping_; + // Name of the start page for this object, if propagated from a descendant; + // see https://drafts.csswg.org/css-page-3/#start-page-value + AtomicString propagated_start_page_name_; + + // Name of the end page for this object, if propagated from a descendant; + // see https://drafts.csswg.org/css-page-3/#end-page-value + AtomicString propagated_end_page_name_; + unsigned break_before_ : 4; unsigned break_after_ : 4; int line_break_to_avoid_widow_; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_block_flow_line.cc b/chromium/third_party/blink/renderer/core/layout/layout_block_flow_line.cc index dc9f0999a5f..a928cded339 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_block_flow_line.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_block_flow_line.cc @@ -1901,7 +1901,7 @@ void LayoutBlockFlow::ComputeInlinePreferredLogicalWidths( } // Ignore spaces after a list marker. - if (child->IsListMarkerIncludingNGOutside()) + if (child->IsBoxListMarkerIncludingNG()) strip_front_spaces = true; } else { min_logical_width = std::max(min_logical_width, inline_min); @@ -2385,12 +2385,8 @@ bool LayoutBlockFlow::GeneratesLineBoxesForInlineChild(LayoutObject* inline_obj) } void LayoutBlockFlow::AddVisualOverflowFromInlineChildren() { - LayoutUnit end_padding = HasOverflowClip() ? PaddingEnd() : LayoutUnit(); - // FIXME: Need to find another way to do this, since scrollbars could show - // when we don't want them to. - if (HasOverflowClip() && !end_padding && GetNode() && - IsRootEditableElement(*GetNode()) && StyleRef().IsLeftToRightDirection()) - end_padding = LayoutUnit(1); + DCHECK(!NeedsLayout()); + DCHECK(!PrePaintBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)); if (const NGPaintFragment* paint_fragment = PaintFragment()) { for (const NGPaintFragment* child : paint_fragment->Children()) { @@ -2454,12 +2450,46 @@ void LayoutBlockFlow::AddVisualOverflowFromInlineChildren() { } void LayoutBlockFlow::AddLayoutOverflowFromInlineChildren() { + DCHECK(!LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)); + LayoutUnit end_padding = HasOverflowClip() ? PaddingEnd() : LayoutUnit(); // FIXME: Need to find another way to do this, since scrollbars could show // when we don't want them to. + // The test[1] verifies this. + // [1] editing/input/editable-container-with-word-wrap-normal.html if (HasOverflowClip() && !end_padding && GetNode() && - IsRootEditableElement(*GetNode()) && StyleRef().IsLeftToRightDirection()) + IsRootEditableElement(*GetNode()) && + StyleRef().IsLeftToRightDirection()) { + if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { + if (const NGFragmentItems* items = fragment->Items()) { + for (NGInlineCursor cursor(*items); cursor; + cursor.MoveToNextSkippingChildren()) { + if (!cursor.Current().IsLineBox()) + continue; + const NGFragmentItem& child = *cursor.CurrentItem(); + LogicalRect logical_rect = + fragment->ConvertChildToLogical(child.RectInContainerBlock()); + logical_rect.size.inline_size += 1; + AddLayoutOverflow( + fragment->ConvertChildToPhysical(logical_rect).ToLayoutRect()); + } + return; + } + // Note: Paint fragment for this block isn't set yet. + for (const NGLink& child : fragment->Children()) { + if (!child->IsLineBox()) + continue; + LogicalRect logical_rect = fragment->ConvertChildToLogical( + PhysicalRect(child.Offset(), child->Size())); + logical_rect.size.inline_size += 1; + AddLayoutOverflow( + fragment->ConvertChildToPhysical(logical_rect).ToLayoutRect()); + } + return; + } end_padding = LayoutUnit(1); + } + for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox()) AddLayoutOverflow(curr->PaddedLayoutOverflowRect(end_padding)); } @@ -2772,8 +2802,12 @@ void LayoutBlockFlow::SetShouldDoFullPaintInvalidationForFirstLine() { // Mark all descendants of the first line if first-line style. for (NGInlineCursor descendants = first_line.CursorForDescendants(); descendants; descendants.MoveToNext()) { - LayoutObject* layout_object = - descendants.Current()->GetMutableLayoutObject(); + const NGFragmentItem* item = descendants.Current().Item(); + if (UNLIKELY(item->IsLayoutObjectDestroyedOrMoved())) { + descendants.MoveToNextSkippingChildren(); + continue; + } + LayoutObject* layout_object = item->GetMutableLayoutObject(); DCHECK(layout_object); layout_object->StyleRef().ClearCachedPseudoElementStyles(); layout_object->SetShouldDoFullPaintInvalidation(); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box.cc b/chromium/third_party/blink/renderer/core/layout/layout_box.cc index c0cc36526af..54f13b0b9d5 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_box.cc @@ -248,7 +248,7 @@ LayoutBoxRareData::LayoutBoxRareData() snap_container_(nullptr), snap_areas_(nullptr) {} -void LayoutBoxRareData::Trace(Visitor* visitor) { +void LayoutBoxRareData::Trace(Visitor* visitor) const { visitor->Trace(layout_child_); } @@ -266,14 +266,8 @@ LayoutBox::LayoutBox(ContainerNode* node) LayoutBox::~LayoutBox() = default; PaintLayerType LayoutBox::LayerTypeRequired() const { - // hasAutoZIndex only returns true if the element is positioned or a flex-item - // since position:static elements that are not flex-items get their z-index - // coerced to auto. - if (IsPositioned() || CreatesGroup() || HasTransformRelatedProperty() || - HasHiddenBackface() || HasReflection() || + if (IsStacked() || HasHiddenBackface() || (StyleRef().SpecifiesColumns() && !CanTraversePhysicalFragments()) || - StyleRef().IsStackingContext() || - StyleRef().ShouldCompositeForCurrentAnimations() || IsEffectiveRootScroller()) return kNormalPaintLayer; @@ -386,6 +380,16 @@ void LayoutBox::StyleWillChange(StyleDifference diff, // recalculation. SetNeedsLayoutAndIntrinsicWidthsRecalc( layout_invalidation_reason::kStyleChange); + + if (IsInLayoutNGInlineFormattingContext() && + RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled() && + FirstInlineFragmentItemIndex()) { + // Out of flow are not part of |NGFragmentItems|, and that further + // changes including destruction cannot be tracked. Mark it is moved + // out from this IFC. + NGFragmentItems::LayoutObjectWillBeMoved(*this); + ClearFirstInlineFragmentItemIndex(); + } } else { MarkContainerChainForLayout(); } @@ -492,8 +496,11 @@ void LayoutBox::StyleDidChange(StyleDifference diff, // The overflow clip paint property depends on border sizes through // overflowClipRect(), and border radii, so we update properties on // border size or radii change. + // + // For some controls, it depends on paddings. if (!old_style->BorderSizeEquals(new_style) || - !old_style->RadiiEqual(new_style)) { + !old_style->RadiiEqual(new_style) || + (HasControlClip() && !old_style->PaddingEqual(new_style))) { SetNeedsPaintPropertyUpdate(); if (Layer()) Layer()->SetNeedsCompositingInputsUpdate(); @@ -1000,6 +1007,7 @@ LayoutUnit LayoutBox::ConstrainLogicalHeightByMinMax( const Length& logical_max_height = StyleRef().LogicalMaxHeight(); if (!logical_max_height.IsNone() && !logical_max_height.IsMinContent() && !logical_max_height.IsMaxContent() && + !logical_max_height.IsMinIntrinsic() && !logical_max_height.IsFitContent()) { LayoutUnit max_h = ComputeLogicalHeightUsing(kMaxSize, logical_max_height, intrinsic_content_height); @@ -1008,7 +1016,7 @@ LayoutUnit LayoutBox::ConstrainLogicalHeightByMinMax( } Length logical_min_height = StyleRef().LogicalMinHeight(); if (logical_min_height.IsMinContent() || logical_min_height.IsMaxContent() || - logical_min_height.IsFitContent()) + logical_min_height.IsMinIntrinsic() || logical_min_height.IsFitContent()) logical_min_height = Length::Auto(); return std::max(logical_height, ComputeLogicalHeightUsing(kMinSize, logical_min_height, @@ -1066,9 +1074,18 @@ void LayoutBox::SetLocationAndUpdateOverflowControlsIfNeeded( IntSize old_pixel_snapped_border_rect_size = PixelSnappedBorderBoxRect().Size(); SetLocation(location); + // TODO(crbug.com/1020913): This is problematic because this function may be + // called after layout of this LayoutBox. Changing scroll container size here + // will cause inconsistent layout. Also we should be careful not to set + // this LayoutBox NeedsLayout. This will be unnecessary when we support + // subpixel layout of scrollable area and overflow controls. if (PixelSnappedBorderBoxRect().Size() != old_pixel_snapped_border_rect_size) { + bool needed_layout = NeedsLayout(); + PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbar; Layer()->UpdateSizeAndScrollingAfterLayout(); + // The above call should not schedule new NeedsLayout. + DCHECK(needed_layout || !NeedsLayout()); } } @@ -1079,6 +1096,11 @@ FloatQuad LayoutBox::AbsoluteContentQuad(MapCoordinatesFlags flags) const { PhysicalRect LayoutBox::PhysicalBackgroundRect( BackgroundRectType rect_type) const { + // If the background transfers to view, the used background of this object + // is transparent. + if (rect_type == kBackgroundKnownOpaqueRect && BackgroundTransfersToView()) + return PhysicalRect(); + EFillBox background_box = EFillBox::kText; // Find the largest background rect of the given opaqueness. if (const FillLayer* current = &(StyleRef().BackgroundLayers())) { @@ -1419,9 +1441,14 @@ bool LayoutBox::MapVisualRectToContainer( transform.PostTranslate(offset.Width(), offset.Height()); } + bool has_perspective = container_object && container_object->HasLayer() && + container_object->StyleRef().HasPerspective(); + if (has_perspective && RuntimeEnabledFeatures::TransformInteropEnabled() && + container_object != NonAnonymousAncestor()) + has_perspective = false; + // d) Perspective applied by container. - if (container_object && container_object->HasLayer() && - container_object->StyleRef().HasPerspective()) { + if (has_perspective) { // Perspective on the container affects us, so we have to factor it in here. DCHECK(container_object->HasLayer()); FloatPoint perspective_origin = @@ -1919,11 +1946,6 @@ bool LayoutBox::GetBackgroundPaintedExtent(PhysicalRect& painted_extent) const { bool LayoutBox::BackgroundIsKnownToBeOpaqueInRect( const PhysicalRect& local_rect) const { - // If the background transfers to view, the used background of this object - // is transparent. - if (BackgroundTransfersToView()) - return false; - // If the element has appearance, it might be painted by theme. // We cannot be sure if theme paints the background opaque. // In this case it is safe to not assume opaqueness. @@ -1943,6 +1965,20 @@ bool LayoutBox::BackgroundIsKnownToBeOpaqueInRect( .Contains(local_rect); } +// TODO(wangxianzhu): The current rules are very basic. May use more complex +// rules if they can improve LCD text. +bool LayoutBox::TextIsKnownToBeOnOpaqueBackground() const { + // Text may overflow the background area. + if (!ShouldClipOverflow()) + return false; + // Same as BackgroundIsKnownToBeOpaqueInRect() about appearance. + if (StyleRef().HasEffectiveAppearance()) + return false; + + PhysicalRect rect = OverflowClipRect(PhysicalOffset()); + return PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).Contains(rect); +} + static bool IsCandidateForOpaquenessTest(const LayoutBox& child_box) { const ComputedStyle& child_style = child_box.StyleRef(); if (child_style.GetPosition() != EPosition::kStatic && @@ -1961,7 +1997,7 @@ static bool IsCandidateForOpaquenessTest(const LayoutBox& child_box) { if (child_layer->GetCompositingState() != kNotComposited) return false; // FIXME: Deal with z-index. - if (child_style.IsStackingContext()) + if (child_box.IsStackingContext()) return false; if (child_layer->HasTransformRelatedProperty() || child_layer->IsTransparent() || @@ -2179,6 +2215,19 @@ void LayoutBox::InvalidatePaint(const PaintInvalidatorContext& context) const { BoxPaintInvalidator(*this, context).InvalidatePaint(); } +void LayoutBox::ClearPaintFlags() { + LayoutObject::ClearPaintFlags(); + + if (auto* scrollable_area = GetScrollableArea()) { + if (auto* scrollbar = + DynamicTo<CustomScrollbar>(scrollable_area->HorizontalScrollbar())) + scrollbar->ClearPaintFlags(); + if (auto* scrollbar = + DynamicTo<CustomScrollbar>(scrollable_area->VerticalScrollbar())) + scrollbar->ClearPaintFlags(); + } +} + PhysicalRect LayoutBox::OverflowClipRect( const PhysicalOffset& location, OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const { @@ -2641,7 +2690,7 @@ const NGLayoutResult* LayoutBox::GetCachedLayoutResult() const { const NGLayoutResult* result = layout_results_[0].get(); if (result->IsSingleUse()) return nullptr; - DCHECK(result->PhysicalFragment().IsAlive()); + DCHECK(result->PhysicalFragment().IsAlive() || BeingDestroyed()); DCHECK_EQ(layout_results_.size(), 1u); return result; } @@ -2758,7 +2807,8 @@ scoped_refptr<const NGLayoutResult> LayoutBox::CachedLayoutResult( // Update our temporary cache status, if the size cache check indicated we // might need simplified layout. - if (size_cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout) + if (size_cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout && + cache_status == NGLayoutCacheStatus::kHit) cache_status = NGLayoutCacheStatus::kNeedsSimplifiedLayout; LayoutUnit bfc_line_offset = new_space.BfcOffset().line_offset; @@ -3073,6 +3123,14 @@ bool LayoutBox::NeedsForcedBreakBefore( return IsForcedFragmentainerBreakValue(break_value); } +const AtomicString LayoutBox::StartPageName() const { + return StyleRef().Page(); +} + +const AtomicString LayoutBox::EndPageName() const { + return StyleRef().Page(); +} + PhysicalRect LayoutBox::LocalVisualRectIgnoringVisibility() const { return PhysicalSelfVisualOverflowRect(); } @@ -3274,7 +3332,7 @@ static float GetMaxWidthListMarker(const LayoutBox* layout_object) { LayoutBox* list_item = ToLayoutBox(child); for (LayoutObject* item_child = list_item->SlowFirstChild(); item_child; item_child = item_child->NextSibling()) { - if (!item_child->IsListMarker()) + if (!item_child->IsListMarkerForNormalContent()) continue; LayoutBox* item_marker = ToLayoutBox(item_child); // Make sure to compute the autosized width. @@ -3455,7 +3513,8 @@ LayoutUnit LayoutBox::ComputeIntrinsicLogicalWidthUsing( MinMaxSizes sizes = IntrinsicLogicalWidths(); - if (logical_width_length.IsMinContent()) + if (logical_width_length.IsMinContent() || + logical_width_length.IsMinIntrinsic()) return sizes.min_size; if (logical_width_length.IsMaxContent()) @@ -3970,6 +4029,7 @@ LayoutUnit LayoutBox::ComputeIntrinsicLogicalContentHeightUsing( // If that happens, this code will have to change. if (logical_height_length.IsMinContent() || logical_height_length.IsMaxContent() || + logical_height_length.IsMinIntrinsic() || logical_height_length.IsFitContent()) { if (IsAtomicInlineLevel() && !IsFlexibleBoxIncludingNG() && !IsLayoutGrid()) return IntrinsicSize().Height(); @@ -4213,7 +4273,8 @@ LayoutUnit LayoutBox::ComputeReplacedLogicalWidthUsing( case Length::kFixed: return AdjustContentBoxLogicalWidthForBoxSizing(logical_width.Value()); case Length::kMinContent: - case Length::kMaxContent: { + case Length::kMaxContent: + case Length::kMinIntrinsic: { // MinContent/MaxContent don't need the availableLogicalWidth argument. LayoutUnit available_logical_width; return ComputeIntrinsicLogicalWidthUsing(logical_width, @@ -4275,7 +4336,7 @@ bool LayoutBox::LogicalHeightComputesAsNone(SizeType size_type) const { // Note that the values 'min-content', 'max-content' and 'fit-content' should // behave as the initial value if specified in the block direction. if (logical_height.IsMinContent() || logical_height.IsMaxContent() || - logical_height.IsFitContent()) + logical_height.IsMinIntrinsic() || logical_height.IsFitContent()) return true; Length initial_logical_height = @@ -4471,11 +4532,14 @@ LayoutUnit LayoutBox::AvailableLogicalHeightUsing( } else if (HasOverrideLogicalHeight() && IsOverrideLogicalHeightDefinite()) { return OverrideContentLogicalHeight(); - } else if (const auto* previous_result = GetCachedLayoutResult()) { - const NGConstraintSpace& space = - previous_result->GetConstraintSpaceForCaching(); - if (space.IsFixedBlockSize() && !space.IsFixedBlockSizeIndefinite()) - return space.AvailableSize().block_size; + } else if (!GetBoxLayoutExtraInput()) { + // TODO(ikilpatrick): Remove this post M86. + if (const auto* previous_result = GetCachedLayoutResult()) { + const NGConstraintSpace& space = + previous_result->GetConstraintSpaceForCaching(); + if (space.IsFixedBlockSize() && !space.IsFixedBlockSizeIndefinite()) + return space.AvailableSize().block_size; + } } } @@ -5275,6 +5339,7 @@ void LayoutBox::ComputePositionedLogicalHeight( const Length& logical_max_height = style_to_use.LogicalMaxHeight(); if (!logical_max_height.IsNone() && !logical_max_height.IsMinContent() && !logical_max_height.IsMaxContent() && + !logical_max_height.IsMinIntrinsic() && !logical_max_height.IsFitContent()) { LogicalExtentComputedValues max_values; @@ -5294,7 +5359,7 @@ void LayoutBox::ComputePositionedLogicalHeight( // Calculate constraint equation values for 'min-height' case. Length logical_min_height = style_to_use.LogicalMinHeight(); if (logical_min_height.IsMinContent() || logical_min_height.IsMaxContent() || - logical_min_height.IsFitContent()) + logical_min_height.IsMinIntrinsic() || logical_min_height.IsFitContent()) logical_min_height = Length::Auto(); if (!logical_min_height.IsZero() || logical_min_height.IsFillAvailable()) { LogicalExtentComputedValues min_values; @@ -5752,18 +5817,6 @@ bool LayoutBox::ShouldBeConsideredAsReplaced() const { return IsA<HTMLImageElement>(element); } -bool LayoutBox::HasNonCompositedScrollbars() const { - if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { - if (scrollable_area->HasHorizontalScrollbar() && - !scrollable_area->LayerForHorizontalScrollbar()) - return true; - if (scrollable_area->HasVerticalScrollbar() && - !scrollable_area->LayerForVerticalScrollbar()) - return true; - } - return false; -} - void LayoutBox::UpdateFragmentationInfoForChild(LayoutBox& child) { LayoutState* layout_state = View()->GetLayoutState(); DCHECK(layout_state->IsPaginated()); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box.h b/chromium/third_party/blink/renderer/core/layout/layout_box.h index 963867ebfdd..5921913da3a 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_box.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_box.h @@ -69,7 +69,7 @@ struct LayoutBoxRareData final : public GarbageCollected<LayoutBoxRareData> { public: LayoutBoxRareData(); - void Trace(Visitor* visitor); + void Trace(Visitor* visitor) const; // For spanners, the spanner placeholder that lays us out within the multicol // container. @@ -220,6 +220,7 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject { bool BackgroundIsKnownToBeOpaqueInRect( const PhysicalRect& local_rect) const override; + bool TextIsKnownToBeOnOpaqueBackground() const override; virtual bool BackgroundShouldAlwaysBeClipped() const { return false; } @@ -307,10 +308,12 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject { LayoutUnit MinimumLogicalHeightForEmptyLine() const { return BorderAndPaddingLogicalHeight() + ScrollbarLogicalHeight() + - LineHeight( - true, - IsHorizontalWritingMode() ? kHorizontalLine : kVerticalLine, - kPositionOfInteriorLineBoxes); + LogicalHeightForEmptyLine(); + } + LayoutUnit LogicalHeightForEmptyLine() const { + return LineHeight( + true, IsHorizontalWritingMode() ? kHorizontalLine : kVerticalLine, + kPositionOfInteriorLineBoxes); } void SetLogicalLeft(LayoutUnit left) { @@ -1055,6 +1058,14 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject { // value of the previous in-flow sibling. bool NeedsForcedBreakBefore(EBreakBetween previous_break_after_value) const; + // Get the name of the start page name for this object; see + // https://drafts.csswg.org/css-page-3/#start-page-value + virtual const AtomicString StartPageName() const; + + // Get the name of the end page name for this object; see + // https://drafts.csswg.org/css-page-3/#end-page-value + virtual const AtomicString EndPageName() const; + bool MapToVisualRectInAncestorSpaceInternal( const LayoutBoxModelObject* ancestor, TransformState&, @@ -1547,9 +1558,8 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject { // Returns true if the box intersects the viewport visible to the user. bool IntersectsVisibleViewport() const; - bool HasNonCompositedScrollbars() const final; - void EnsureIsReadyForPaintInvalidation() override; + void ClearPaintFlags() override; bool HasControlClip() const; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.cc index 42a4f5d5b50..efb9bb50267 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.cc @@ -122,11 +122,6 @@ LayoutBoxModelObject::ComputeBackgroundPaintLocationIfComposited() const { return kBackgroundPaintInScrollingContents; } - // TODO(flackr): When we correctly clip the scrolling contents layer we can - // paint locally equivalent backgrounds into it. https://crbug.com/645957 - if (HasClip()) - return kBackgroundPaintInGraphicsLayer; - // Inset box shadow is painted in the scrolling area above the background, and // it doesn't scroll, so the background can only be painted in the main layer. if (HasInsetBoxShadow(StyleRef())) @@ -245,8 +240,8 @@ void LayoutBoxModelObject::StyleWillChange(StyleDifference diff, // invalidate the current compositing container chain which may have painted // cached subsequences containing this object or descendant objects. if (Style() && - (StyleRef().IsStacked() != new_style.IsStacked() || - StyleRef().IsStackingContext() != new_style.IsStackingContext()) && + (IsStacked() != IsStacked(new_style) || + IsStackingContext() != IsStackingContext(new_style)) && // ObjectPaintInvalidator requires this. IsRooted()) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { @@ -599,7 +594,7 @@ void LayoutBoxModelObject::AddOutlineRectsForDescendant( Vector<PhysicalRect>& rects, const PhysicalOffset& additional_offset, NGOutlineType include_block_overflows) const { - if (descendant.IsText() || descendant.IsListMarker()) + if (descendant.IsText() || descendant.IsListMarkerForNormalContent()) return; if (descendant.HasLayer()) { @@ -755,6 +750,12 @@ bool LayoutBoxModelObject::HasAutoHeightOrContainingBlockWithAutoHeight( this_box->HasOverrideContainingBlockContentLogicalHeight()) { return this_box->OverrideContainingBlockContentLogicalHeight() == LayoutUnit(-1); + } else if (this_box && this_box->GetCachedLayoutResult() && + !this_box->GetBoxLayoutExtraInput()) { + return this_box->GetCachedLayoutResult() + ->GetConstraintSpaceForCaching() + .AvailableSize() + .block_size == LayoutUnit(-1); } return !cb->HasDefiniteLogicalHeight(); } @@ -765,7 +766,7 @@ bool LayoutBoxModelObject::HasAutoHeightOrContainingBlockWithAutoHeight( PhysicalOffset LayoutBoxModelObject::RelativePositionOffset() const { DCHECK(IsRelPositioned()); - PhysicalOffset offset = AccumulateInFlowPositionOffsets(); + PhysicalOffset offset = AccumulateRelativePositionOffsets(); LayoutBlock* containing_block = ContainingBlock(); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.h b/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.h index 2e33f628434..15236cff8dc 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_box_model_object.h @@ -416,6 +416,9 @@ class CORE_EXPORT LayoutBoxModelObject : public LayoutObject { virtual bool BackgroundIsKnownToBeOpaqueInRect(const PhysicalRect&) const { return false; } + // Returns true if all text in the paint-order subtree will be painted on + // opaque background. + virtual bool TextIsKnownToBeOnOpaqueBackground() const { return false; } // This object's background is transferred to its LayoutView if: // 1. it's the document element, or @@ -470,7 +473,7 @@ class CORE_EXPORT LayoutBoxModelObject : public LayoutObject { // See continuation above for more details. void SetContinuation(LayoutBoxModelObject*); - virtual PhysicalOffset AccumulateInFlowPositionOffsets() const { + virtual PhysicalOffset AccumulateRelativePositionOffsets() const { return PhysicalOffset(); } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc index a34bab16c6e..5bfeb9b92a9 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc @@ -1070,16 +1070,16 @@ TEST_F(LayoutBoxModelObjectTest, InvalidatePaintLayerOnStackedChange) { auto* parent = target->Parent(); auto* original_compositing_container = target->Layer()->CompositingContainer(); - EXPECT_FALSE(target->StyleRef().IsStackingContext()); - EXPECT_TRUE(target->StyleRef().IsStacked()); - EXPECT_FALSE(parent->StyleRef().IsStacked()); + EXPECT_FALSE(target->IsStackingContext()); + EXPECT_TRUE(target->IsStacked()); + EXPECT_FALSE(parent->IsStacked()); EXPECT_NE(parent, original_compositing_container->GetLayoutObject()); target_element->setAttribute(html_names::kClassAttr, "non-stacked"); GetDocument().View()->UpdateLifecycleToLayoutClean( DocumentUpdateReason::kTest); - EXPECT_FALSE(target->StyleRef().IsStacked()); + EXPECT_FALSE(target->IsStacked()); EXPECT_TRUE(target->Layer()->SelfNeedsRepaint()); EXPECT_TRUE(original_compositing_container->DescendantNeedsRepaint()); auto* new_compositing_container = target->Layer()->CompositingContainer(); @@ -1090,7 +1090,7 @@ TEST_F(LayoutBoxModelObjectTest, InvalidatePaintLayerOnStackedChange) { GetDocument().View()->UpdateLifecycleToLayoutClean( DocumentUpdateReason::kTest); - EXPECT_TRUE(target->StyleRef().IsStacked()); + EXPECT_TRUE(target->IsStacked()); EXPECT_TRUE(target->Layer()->SelfNeedsRepaint()); EXPECT_TRUE(new_compositing_container->DescendantNeedsRepaint()); EXPECT_EQ(original_compositing_container, @@ -1327,7 +1327,7 @@ TEST_F(LayoutBoxModelObjectTest, UpdateStackingContextForOption) { auto* option_element = GetDocument().getElementById("opt"); auto* option_layout = option_element->GetLayoutObject(); ASSERT_TRUE(option_layout); - EXPECT_TRUE(option_layout->StyleRef().IsStackingContext()); + EXPECT_TRUE(option_layout->IsStackingContext()); EXPECT_TRUE(option_layout->StyleRef().HasCurrentOpacityAnimation()); } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_box_test.cc index 99bcb9ca795..91b31b8f6f9 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_box_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_box_test.cc @@ -1436,4 +1436,31 @@ TEST_P(LayoutBoxTest, HasNonCollapsedBorderDecoration) { EXPECT_TRUE(div->HasNonCollapsedBorderDecoration()); } +TEST_P(LayoutBoxTest, + ThickScrollbarSubpixelSizeMarginNoDirtyLayoutAfterLayout) { + // |target| creates horizontal scrollbar during layout because the contents + // overflow horizontally, which causes vertical overflow because the + // horizontal scrollbar reduces available height. For now we suppress + // creation of the vertical scrollbar because otherwise we would need another + // layout. The subpixel margin and size cause change of pixel snapped border + // size after layout which requires repositioning of the overflow controls. + // This test ensures there is no left-over dirty layout. + SetBodyInnerHTML(R"HTML( + <style> + ::-webkit-scrollbar { + width: 100px; + height: 100px; + background: blue; + } + </style> + <div id="target" + style="width: 150.3px; height: 150.3px; margin: 10.4px; + font-size: 30px; overflow: auto"> + <div style="width: 200px; height: 80px"></div> + </div> + )HTML"); + + DCHECK(!GetLayoutObjectByElementId("target")->NeedsLayout()); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_counter.cc b/chromium/third_party/blink/renderer/core/layout/layout_counter.cc index d3a3e1359e0..07e09e99039 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_counter.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_counter.cc @@ -43,15 +43,16 @@ namespace blink { -typedef HashMap<AtomicString, scoped_refptr<CounterNode>> CounterMap; typedef HashMap<const LayoutObject*, std::unique_ptr<CounterMap>> CounterMaps; -static CounterNode* MakeCounterNodeIfNeeded(LayoutObject&, - const AtomicString& identifier, - bool always_create_counter); +namespace { + +CounterNode* MakeCounterNodeIfNeeded(LayoutObject&, + const AtomicString& identifier, + bool always_create_counter); // See class definition as to why we have this map. -static CounterMaps& GetCounterMaps() { +CounterMaps& GetCounterMaps() { DEFINE_STATIC_LOCAL(CounterMaps, static_counter_maps, ()); return static_counter_maps; } @@ -67,15 +68,19 @@ Element* AncestorStyleContainmentObject(const Element& element) { return nullptr; } +int ValueForText(CounterNode* node) { + return node->ActsAsReset() ? node->Value() : node->CountInParent(); +} + // This function processes the DOM tree including pseudo elements as defined in // CSS 2.1. This method will always return either a previous element within the // same contain: style scope or nullptr. -static Element* PreviousInPreOrderRespectingContainment( - const Element& element) { +Element* PreviousInPreOrderRespectingContainment(const Element& element) { Element* previous = ElementTraversal::PreviousIncludingPseudo(element); Element* style_contain_ancestor = AncestorStyleContainmentObject(element); while (true) { + // Find the candidate previous element. while (previous && !previous->GetLayoutObject() && !previous->HasDisplayContentsStyle()) previous = ElementTraversal::PreviousIncludingPseudo(*previous); @@ -83,18 +88,27 @@ static Element* PreviousInPreOrderRespectingContainment( return nullptr; Element* previous_style_contain_ancestor = AncestorStyleContainmentObject(*previous); + // If the candidate's containment ancestor is the same as elements, then + // that's a valid candidate. if (previous_style_contain_ancestor == style_contain_ancestor) return previous; + + // Otherwise, if previous does not have a containment ancestor, it means + // that we have already escaped `element`'s containment ancestor, so return + // nullptr. if (!previous_style_contain_ancestor) return nullptr; + + // If, however, the candidate does have a containment ancestor, it could be + // that we entered a new sub-containment. Try again starting from the + // contain ancestor. previous = previous_style_contain_ancestor; } } // This function processes the DOM including pseudo elements as defined in // CSS 2.1. This method avoids crossing contain: style boundaries. -static Element* PreviousSiblingOrParentRespectingContainment( - const Element& element) { +Element* PreviousSiblingOrParentRespectingContainment(const Element& element) { Element* previous = ElementTraversal::PseudoAwarePreviousSibling(element); // Skip display:none elements. while (previous && !previous->GetLayoutObject() && @@ -105,23 +119,22 @@ static Element* PreviousSiblingOrParentRespectingContainment( previous = element.parentElement(); if (previous) { if (const ComputedStyle* style = previous->GetComputedStyle()) { - if (style->Contain() & kContainsStyle) + if (style->ContainsStyle()) return nullptr; } } return previous; } -static inline bool AreElementsSiblings(const Element& first, - const Element& second) { +inline bool AreElementsSiblings(const Element& first, const Element& second) { return first.parentElement() == second.parentElement(); } // This function processes the the DOM tree including pseudo elements as defined // in CSS 2.1. -static LayoutObject* NextInPreOrder(const LayoutObject& object, - const Element* stay_within, - bool skip_descendants = false) { +LayoutObject* NextInPreOrder(const LayoutObject& object, + const Element* stay_within, + bool skip_descendants = false) { auto* self = To<Element>(object.GetNode()); DCHECK(self); Element* next = @@ -137,10 +150,10 @@ static LayoutObject* NextInPreOrder(const LayoutObject& object, return next ? next->GetLayoutObject() : nullptr; } -static bool PlanCounter(LayoutObject& object, - const AtomicString& identifier, - bool& is_reset, - int& value) { +bool PlanCounter(LayoutObject& object, + const AtomicString& identifier, + unsigned& type_mask, + int& value) { // Real text nodes don't have their own style so they can't have counters. // We can't even look at their styles or we'll see extra resets and // increments! @@ -167,10 +180,13 @@ static bool PlanCounter(LayoutObject& object, return false; // Counters are forbidden from all other pseudo elements. } + type_mask = 0; const CounterDirectives directives = style.GetCounterDirectives(identifier); if (directives.IsDefined()) { value = directives.CombinedValue(); - is_reset = directives.IsReset(); + type_mask |= directives.IsIncrement() ? CounterNode::kIncrementType : 0; + type_mask |= directives.IsReset() ? CounterNode::kResetType : 0; + type_mask |= directives.IsSet() ? CounterNode::kSetType : 0; return true; } @@ -179,22 +195,22 @@ static bool PlanCounter(LayoutObject& object, if (ListItemOrdinal* ordinal = ListItemOrdinal::Get(*e)) { if (const auto& explicit_value = ordinal->ExplicitValue()) { value = explicit_value.value(); - is_reset = true; + type_mask = CounterNode::kResetType; return true; } value = 1; - is_reset = false; + type_mask = CounterNode::kIncrementType; return true; } if (auto* olist = DynamicTo<HTMLOListElement>(*e)) { value = olist->StartConsideringItemCount(); - is_reset = true; + type_mask = CounterNode::kResetType; return true; } if (IsA<HTMLUListElement>(*e) || IsA<HTMLMenuElement>(*e) || IsA<HTMLDirectoryElement>(*e)) { value = 0; - is_reset = true; + type_mask = CounterNode::kResetType; return true; } } @@ -218,11 +234,11 @@ static bool PlanCounter(LayoutObject& object, // references that are in the scope of the counter or nested counter defined // by that reset node. // - Non-reset CounterNodes cannot have descendants. -static bool FindPlaceForCounter(LayoutObject& counter_owner, - const AtomicString& identifier, - bool is_reset, - scoped_refptr<CounterNode>& parent, - scoped_refptr<CounterNode>& previous_sibling) { +bool FindPlaceForCounter(LayoutObject& counter_owner, + const AtomicString& identifier, + bool is_reset, + scoped_refptr<CounterNode>& parent, + scoped_refptr<CounterNode>& previous_sibling) { // We cannot stop searching for counters with the same identifier before we // also check this layout object, because it may affect the positioning in the // tree of our counter. @@ -364,13 +380,13 @@ static bool FindPlaceForCounter(LayoutObject& counter_owner, return false; } -static inline Element* ParentElement(LayoutObject& object) { +inline Element* ParentElement(LayoutObject& object) { return To<Element>(object.GetNode())->parentElement(); } -static CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, - const AtomicString& identifier, - bool always_create_counter) { +CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, + const AtomicString& identifier, + bool always_create_counter) { if (object.HasCounterNodeMap()) { if (CounterMap* node_map = GetCounterMaps().at(&object)) { if (CounterNode* node = node_map->at(identifier)) @@ -378,18 +394,18 @@ static CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, } } - bool is_reset = false; + unsigned type_mask = 0; int value = 0; - if (!PlanCounter(object, identifier, is_reset, value) && + if (!PlanCounter(object, identifier, type_mask, value) && !always_create_counter) return nullptr; scoped_refptr<CounterNode> new_parent = nullptr; scoped_refptr<CounterNode> new_previous_sibling = nullptr; scoped_refptr<CounterNode> new_node = - CounterNode::Create(object, is_reset, value); + CounterNode::Create(object, type_mask, value); - if (is_reset) { + if (type_mask & CounterNode::kResetType) { // Find the place where we would've inserted the new node if it was a // non-reset node. We have to move every non-reset sibling after the // insertion point to a child of the new node. @@ -407,7 +423,8 @@ static CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, } } - if (FindPlaceForCounter(object, identifier, is_reset, new_parent, + if (FindPlaceForCounter(object, identifier, + type_mask & CounterNode::kResetType, new_parent, new_previous_sibling)) new_parent->InsertAfter(new_node.get(), new_previous_sibling.get(), identifier); @@ -420,8 +437,13 @@ static CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, object.SetHasCounterNodeMap(true); } node_map->Set(identifier, new_node); - if (new_node->Parent()) + // If the new node has a parent, that means any descendant would have been + // updated by `CounterNode::MoveNonResetSiblingsToChildOf()` above, so we + // don't need to update descendants. Likewise, if the object has style + // containment, any descendant should not become parented across the boundary. + if (new_node->Parent() || object.ShouldApplyStyleContainment()) return new_node.get(); + // Checking if some nodes that were previously counter tree root nodes // should become children of this node now. CounterMaps& maps = GetCounterMaps(); @@ -432,13 +454,20 @@ static CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, current_layout_object; current_layout_object = NextInPreOrder(*current_layout_object, stay_within, skip_descendants)) { - skip_descendants = false; + // We'll update the current object and we might recurse into the + // descendants. However, if the object has style containment then we do not + // cross the boundary which begins right after the object. In other words we + // skip the descendants of this object. + skip_descendants = current_layout_object->ShouldApplyStyleContainment(); if (!current_layout_object->HasCounterNodeMap()) continue; CounterNode* current_counter = maps.at(current_layout_object)->at(identifier); if (!current_counter) continue; + // At this point we found a counter to reparent. So we don't need to descend + // into the layout tree further, since any further counters we find would be + // at most parented to `current_counter` we just found. skip_descendants = true; if (current_counter->Parent()) continue; @@ -450,6 +479,8 @@ static CounterNode* MakeCounterNodeIfNeeded(LayoutObject& object, return new_node.get(); } +} // namespace + LayoutCounter::LayoutCounter(PseudoElement& pseudo, const CounterContent& counter) : LayoutText(nullptr, StringImpl::empty_), @@ -473,8 +504,18 @@ void LayoutCounter::WillBeDestroyed() { } scoped_refptr<StringImpl> LayoutCounter::OriginalText() const { - if (!counter_node_) { - LayoutObject* container = Parent(); + // Child will be the base of our text that we report. First, we need to find + // an appropriate child. + CounterNode* child = nullptr; + + // Find a container on which to create the counter if one needs creating. + LayoutObject* container = Parent(); + bool should_create_counter = counter_.Separator().IsNull(); + // Optimization: the only reason we need a proper container is if we might not + // need to create a counter (in which case, we navigate container's + // ancestors), or if we don't have a counter_node_ (in which case we need to + // find the container to place the counter on). + if (!should_create_counter || !counter_node_) { while (true) { if (!container) return nullptr; @@ -488,23 +529,74 @@ scoped_refptr<StringImpl> LayoutCounter::OriginalText() const { break; container = container->Parent(); } - MakeCounterNodeIfNeeded(*container, counter_.Identifier(), true) - ->AddLayoutObject(const_cast<LayoutCounter*>(this)); - DCHECK(counter_node_); } - CounterNode* child = counter_node_; - int value = child->ActsAsReset() ? child->Value() : child->CountInParent(); - String text = list_marker_text::GetText(counter_.ListStyle(), value); + // Now that we have a container, check if the counter directives are + // defined between us and the first style containment element, meaning that + // the counter would be created for our scope even if there is no content + // request. If not, and if the separator is not null, meaning the request was + // for something like counters(n, "."), then we first have to check our + // ancestors across the style containment boundary. If the ancestors have the + // value for our identifier, then we don't need a counter here and it is + // instead omitted. See counter-scoping-001.html WPT and crbug.com/882383#c11 + // for more context. + if (!should_create_counter) { + for (auto* scope_ancestor = container; scope_ancestor; + scope_ancestor = scope_ancestor->Parent()) { + auto& style = scope_ancestor->StyleRef(); + if (style.ContainsStyle()) + break; + const CounterDirectives directives = + style.GetCounterDirectives(counter_.Identifier()); + if (directives.IsDefined()) { + should_create_counter = true; + break; + } + } + } + if (!should_create_counter) { + // If we have an ancestor across the the containment boundary, then use it + // as the child, without needing to create a counter on `this`. If we don't + // have such an ancestor, we need to create a `counter_node_` on `this`. + if (auto* node = CounterNode::AncestorNodeAcrossStyleContainment( + *this, counter_.Identifier())) { + child = node; + } else { + should_create_counter = true; + } + } + + if (should_create_counter) { + if (!counter_node_) { + MakeCounterNodeIfNeeded(*container, counter_.Identifier(), true) + ->AddLayoutObject(const_cast<LayoutCounter*>(this)); + DCHECK(counter_node_); + } + child = counter_node_; + } + + // In all cases we should end up with a `child` which is the base of our + // navigation. + DCHECK(child); + + int value = ValueForText(child); + String text = list_marker_text::GetText(counter_.ListStyle(), value); + // If the separator exists, we need to append all of the parent values as well, + // including the ones that cross the style containment boundary. if (!counter_.Separator().IsNull()) { if (!child->ActsAsReset()) - child = child->Parent(); - while (CounterNode* parent = child->Parent()) { + child = child->ParentCrossingStyleContainment(counter_.Identifier()); + bool next_result_uses_parent_value = !child->Parent(); + while (CounterNode* parent = + child->ParentCrossingStyleContainment(counter_.Identifier())) { text = list_marker_text::GetText(counter_.ListStyle(), - child->CountInParent()) + + next_result_uses_parent_value + ? ValueForText(parent) + : child->CountInParent()) + counter_.Separator() + text; child = parent; + next_result_uses_parent_value = !child->Parent(); } } @@ -637,6 +729,8 @@ static void UpdateCounters(LayoutObject& layout_object) { void LayoutCounter::LayoutObjectSubtreeAttached(LayoutObject* layout_object) { DCHECK(layout_object->View()); + // Only update counters if we have LayoutCounter which is created when we have + // a content: field with a counter requirement. if (!layout_object->View()->HasLayoutCounters()) return; Node* node = layout_object->GetNode(); @@ -646,9 +740,21 @@ void LayoutCounter::LayoutObjectSubtreeAttached(LayoutObject* layout_object) { node = layout_object->GeneratingNode(); if (node && node->NeedsReattachLayoutTree()) return; // No need to update if the parent is not attached yet + + // Update the descendants. for (LayoutObject* descendant = layout_object; descendant; descendant = descendant->NextInPreOrder(layout_object)) UpdateCounters(*descendant); + + bool crossed_boundary = false; + // Since we skipped counter updates if there were no counters, we might need + // to update parent counters that lie beyond the style containment boundary. + for (LayoutObject* parent = layout_object->Parent(); parent; + parent = parent->Parent()) { + crossed_boundary |= parent->ShouldApplyStyleContainment(); + if (crossed_boundary) + UpdateCounters(*parent); + } } void LayoutCounter::LayoutObjectStyleChanged(LayoutObject& layout_object, @@ -713,12 +819,19 @@ void LayoutCounter::LayoutObjectStyleChanged(LayoutObject& layout_object, } } +// static +CounterMap* LayoutCounter::GetCounterMap(LayoutObject* object) { + if (object->HasCounterNodeMap()) + return GetCounterMaps().at(object); + return nullptr; +} + } // namespace blink #if DCHECK_IS_ON() -void showCounterLayoutObjectTree(const blink::LayoutObject* layout_object, - const char* counter_name) { +void showCounterLayoutTree(const blink::LayoutObject* layout_object, + const char* counter_name) { if (!layout_object) return; const blink::LayoutObject* root = layout_object; @@ -732,15 +845,19 @@ void showCounterLayoutObjectTree(const blink::LayoutObject* layout_object, for (const blink::LayoutObject* parent = current; parent && parent != root; parent = parent->Parent()) fprintf(stderr, " "); - fprintf( - stderr, "%p N:%p P:%p PS:%p NS:%p C:%p\n", current, current->GetNode(), - current->Parent(), current->PreviousSibling(), current->NextSibling(), - current->HasCounterNodeMap() - ? counter_name ? blink::GetCounterMaps().at(current)->at(identifier) - : (blink::CounterNode*)1 - : (blink::CounterNode*)nullptr); + fprintf(stderr, "%p %s", current, current->DebugName().Utf8().c_str()); + auto* counter_node = + current->HasCounterNodeMap() && current + ? blink::GetCounterMaps().at(current)->at(identifier) + : nullptr; + if (counter_node) { + fprintf(stderr, " counter:%p parent:%p value:%d countInParent:%d\n", + counter_node, counter_node->Parent(), counter_node->Value(), + counter_node->CountInParent()); + } else { + fprintf(stderr, "\n"); + } } - fflush(stderr); } #endif // DCHECK_IS_ON() diff --git a/chromium/third_party/blink/renderer/core/layout/layout_counter.h b/chromium/third_party/blink/renderer/core/layout/layout_counter.h index 6766dae45fe..41f35c77b7a 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_counter.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_counter.h @@ -30,6 +30,8 @@ namespace blink { class CounterNode; class PseudoElement; +using CounterMap = HashMap<AtomicString, scoped_refptr<CounterNode>>; + // LayoutCounter is used to represent the text of a counter. // See http://www.w3.org/TR/CSS21/generate.html#counters // @@ -65,6 +67,8 @@ class LayoutCounter final : public LayoutText { const ComputedStyle* old_style, const ComputedStyle& new_style); + static CounterMap* GetCounterMap(LayoutObject*); + void UpdateCounter(); const char* GetName() const override { return "LayoutCounter"; } @@ -95,8 +99,7 @@ DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutCounter, IsCounter()); #if DCHECK_IS_ON() // Outside the blink namespace for ease of invocation from gdb. -void showCounterLayoutTree(const blink::LayoutObject*, - const char* counterName = nullptr); +void showCounterLayoutTree(const blink::LayoutObject*, const char* counterName); #endif #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_COUNTER_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.cc b/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.cc index 2b76e6100ae..c1717f47d23 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.cc @@ -39,7 +39,7 @@ LayoutCustomScrollbarPart::LayoutCustomScrollbarPart( ScrollableArea* scrollable_area, CustomScrollbar* scrollbar, ScrollbarPart part) - : LayoutBlock(nullptr), + : LayoutReplaced(nullptr, LayoutSize()), scrollable_area_(scrollable_area), scrollbar_(scrollbar), part_(part) { @@ -91,148 +91,102 @@ LayoutCustomScrollbarPart* LayoutCustomScrollbarPart::CreateAnonymous( return layout_object; } -void LayoutCustomScrollbarPart::UpdateLayout() { - // We don't worry about positioning ourselves. We're just determining our - // minimum width/height. - SetLocation(LayoutPoint()); - if (scrollbar_->Orientation() == kHorizontalScrollbar) - LayoutHorizontalPart(); - else - LayoutVerticalPart(); - - ClearNeedsLayout(); +// TODO(crbug.com/1020913): Support subpixel layout of scrollbars and remove +// ToInt() in the following functions. +static int ComputeSize(SizeType size_type, + const Length& length, + int container_size) { + if (!length.IsIntrinsicOrAuto() || (size_type == kMinSize && length.IsAuto())) + return MinimumValueForLength(length, LayoutUnit(container_size)).ToInt(); + return CustomScrollbarTheme::GetCustomScrollbarTheme()->ScrollbarThickness(); } -void LayoutCustomScrollbarPart::LayoutHorizontalPart() { - if (part_ == kScrollbarBGPart) { - SetWidth(LayoutUnit(scrollbar_->Width())); - UpdateScrollbarHeight(); - } else { - UpdateScrollbarWidth(); - SetHeight(LayoutUnit(scrollbar_->Height())); - } -} +static int ComputeWidth(int container_width, const ComputedStyle& style) { + if (style.Display() == EDisplay::kNone) + return 0; -void LayoutCustomScrollbarPart::LayoutVerticalPart() { - if (part_ == kScrollbarBGPart) { - UpdateScrollbarWidth(); - SetHeight(LayoutUnit(scrollbar_->Height())); - } else { - SetWidth(LayoutUnit(scrollbar_->Width())); - UpdateScrollbarHeight(); - } + int w = ComputeSize(kMainOrPreferredSize, style.Width(), container_width); + int min_width = ComputeSize(kMinSize, style.MinWidth(), container_width); + int max_width = w; + if (!style.MaxWidth().IsNone()) + max_width = ComputeSize(kMaxSize, style.MaxWidth(), container_width); + return std::max(min_width, std::min(max_width, w)); } -static int CalcScrollbarThicknessUsing(SizeType size_type, - const Length& length, - int containing_length, - ScrollbarTheme* theme) { - if (!length.IsIntrinsicOrAuto() || (size_type == kMinSize && length.IsAuto())) - return MinimumValueForLength(length, LayoutUnit(containing_length)).ToInt(); - return theme->ScrollbarThickness(); +static int ComputeHeight(int container_height, const ComputedStyle& style) { + if (style.Display() == EDisplay::kNone) + return 0; + + int h = ComputeSize(kMainOrPreferredSize, style.Height(), container_height); + int min_height = ComputeSize(kMinSize, style.MinHeight(), container_height); + int max_height = h; + if (!style.MaxHeight().IsNone()) + max_height = ComputeSize(kMaxSize, style.MaxHeight(), container_height); + return std::max(min_height, std::min(max_height, h)); } -int LayoutCustomScrollbarPart::ComputeScrollbarWidth( - int visible_size, - const ComputedStyle* style) { - CustomScrollbarTheme* theme = CustomScrollbarTheme::GetCustomScrollbarTheme(); - int w = CalcScrollbarThicknessUsing(kMainOrPreferredSize, style->Width(), - visible_size, theme); - int min_width = CalcScrollbarThicknessUsing(kMinSize, style->MinWidth(), - visible_size, theme); - int max_width = w; - if (!style->MaxWidth().IsNone()) { - max_width = CalcScrollbarThicknessUsing(kMaxSize, style->MaxWidth(), - visible_size, theme); - } +int LayoutCustomScrollbarPart::ComputeThickness() const { + DCHECK_EQ(kScrollbarBGPart, part_); - return std::max(min_width, std::min(max_width, w)); + // Use 0 for container width/height, so percentage size will be ignored. + // We have never supported that. + if (scrollbar_->Orientation() == kHorizontalScrollbar) + return ComputeHeight(0, StyleRef()); + return ComputeWidth(0, StyleRef()); } -int LayoutCustomScrollbarPart::ComputeScrollbarHeight( - int visible_size, - const ComputedStyle* style) { - CustomScrollbarTheme* theme = CustomScrollbarTheme::GetCustomScrollbarTheme(); - int h = CalcScrollbarThicknessUsing(kMainOrPreferredSize, style->Height(), - visible_size, theme); - int min_height = CalcScrollbarThicknessUsing(kMinSize, style->MinHeight(), - visible_size, theme); - int max_height = h; - if (!style->MaxHeight().IsNone()) { - max_height = CalcScrollbarThicknessUsing(kMaxSize, style->MaxHeight(), - visible_size, theme); - } - return std::max(min_height, std::min(max_height, h)); +int LayoutCustomScrollbarPart::ComputeLength() const { + DCHECK_NE(kScrollbarBGPart, part_); + + IntRect visible_content_rect = + scrollbar_->GetScrollableArea()->VisibleContentRect(kIncludeScrollbars); + if (scrollbar_->Orientation() == kHorizontalScrollbar) + return ComputeWidth(visible_content_rect.Width(), StyleRef()); + return ComputeHeight(visible_content_rect.Height(), StyleRef()); } -void LayoutCustomScrollbarPart::UpdateScrollbarWidth() { - LayoutBox* box = scrollbar_->GetScrollableArea()->GetLayoutBox(); - if (!box) - return; - // FIXME: We are querying layout information but nothing guarantees that it's - // up to date, especially since we are called at style change. - // FIXME: Querying the style's border information doesn't work on table cells - // with collapsing borders. - int visible_size = box->Size().Width() - box->StyleRef().BorderLeftWidth() - - box->StyleRef().BorderRightWidth(); - SetWidth(LayoutUnit(ComputeScrollbarWidth(visible_size, Style()))); +static LayoutUnit ComputeMargin(const Length& style_margin) { + // TODO(crbug.com/1020913): Support subpixel layout of scrollbars and remove + // Round() below. + return LayoutUnit(MinimumValueForLength(style_margin, LayoutUnit()).Round()); +} - // Buttons and track pieces can all have margins along the axis of the - // scrollbar. Values are rounded because scrollbar parts need to be rendered - // at device pixel boundaries. - SetMarginLeft(LayoutUnit( - MinimumValueForLength(StyleRef().MarginLeft(), LayoutUnit(visible_size)) - .Round())); - SetMarginRight(LayoutUnit( - MinimumValueForLength(StyleRef().MarginRight(), LayoutUnit(visible_size)) - .Round())); +LayoutUnit LayoutCustomScrollbarPart::MarginTop() const { + if (scrollbar_->Orientation() == kHorizontalScrollbar) + return LayoutUnit(); + return ComputeMargin(StyleRef().MarginTop()); } -void LayoutCustomScrollbarPart::UpdateScrollbarHeight() { - LayoutBox* box = scrollbar_->GetScrollableArea()->GetLayoutBox(); - if (!box) - return; - // FIXME: We are querying layout information but nothing guarantees that it's - // up to date, especially since we are called at style change. - // FIXME: Querying the style's border information doesn't work on table cells - // with collapsing borders. - int visible_size = box->Size().Height() - box->StyleRef().BorderTopWidth() - - box->StyleRef().BorderBottomWidth(); - SetHeight(LayoutUnit(ComputeScrollbarHeight(visible_size, Style()))); +LayoutUnit LayoutCustomScrollbarPart::MarginBottom() const { + if (scrollbar_->Orientation() == kHorizontalScrollbar) + return LayoutUnit(); + return ComputeMargin(StyleRef().MarginBottom()); +} - // Buttons and track pieces can all have margins along the axis of the - // scrollbar. Values are rounded because scrollbar parts need to be rendered - // at device pixel boundaries. - SetMarginTop(LayoutUnit( - MinimumValueForLength(StyleRef().MarginTop(), LayoutUnit(visible_size)) - .Round())); - SetMarginBottom(LayoutUnit( - MinimumValueForLength(StyleRef().MarginBottom(), LayoutUnit(visible_size)) - .Round())); +LayoutUnit LayoutCustomScrollbarPart::MarginLeft() const { + if (scrollbar_->Orientation() == kVerticalScrollbar) + return LayoutUnit(); + return ComputeMargin(StyleRef().MarginLeft()); } -MinMaxSizes LayoutCustomScrollbarPart::PreferredLogicalWidths() const { - return MinMaxSizes(); +LayoutUnit LayoutCustomScrollbarPart::MarginRight() const { + if (scrollbar_->Orientation() == kVerticalScrollbar) + return LayoutUnit(); + return ComputeMargin(StyleRef().MarginRight()); } -void LayoutCustomScrollbarPart::StyleWillChange( - StyleDifference diff, - const ComputedStyle& new_style) { - LayoutBlock::StyleWillChange(diff, new_style); +void LayoutCustomScrollbarPart::UpdateFromStyle() { + LayoutReplaced::UpdateFromStyle(); SetInline(false); + ClearPositionedState(); + SetFloating(false); } void LayoutCustomScrollbarPart::StyleDidChange(StyleDifference diff, const ComputedStyle* old_style) { - LayoutBlock::StyleDidChange(diff, old_style); - // See adjustStyleBeforeSet() above. - DCHECK(!IsOrthogonalWritingModeRoot()); - SetInline(false); - ClearPositionedState(); - SetFloating(false); + LayoutReplaced::StyleDidChange(diff, old_style); if (old_style && (diff.NeedsPaintInvalidation() || diff.NeedsLayout())) SetNeedsPaintInvalidation(); - RecordPercentLengthStats(); } @@ -250,11 +204,11 @@ void LayoutCustomScrollbarPart::RecordPercentLengthStats() const { // "==" below tests both direct percent length and percent used in calculated // length. if (scrollbar_->Orientation() == width_orientation) { - if (ComputeScrollbarWidth(0, Style()) == - ComputeScrollbarWidth(LayoutUnit::NearlyMax().ToInt(), Style())) + if (ComputeWidth(0, StyleRef()) == + ComputeWidth(LayoutUnit::NearlyMax().ToInt(), StyleRef())) return; - } else if (ComputeScrollbarHeight(0, Style()) == - ComputeScrollbarHeight(LayoutUnit::NearlyMax().ToInt(), Style())) { + } else if (ComputeHeight(0, StyleRef()) == + ComputeHeight(LayoutUnit::NearlyMax().ToInt(), StyleRef())) { return; } @@ -264,7 +218,7 @@ void LayoutCustomScrollbarPart::RecordPercentLengthStats() const { void LayoutCustomScrollbarPart::ImageChanged(WrappedImagePtr image, CanDeferInvalidation defer) { SetNeedsPaintInvalidation(); - LayoutBlock::ImageChanged(image, defer); + LayoutReplaced::ImageChanged(image, defer); } void LayoutCustomScrollbarPart::SetNeedsPaintInvalidation() { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h b/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h index 0832faddd20..9795e0f0b19 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h @@ -26,7 +26,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_CUSTOM_SCROLLBAR_PART_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_CUSTOM_SCROLLBAR_PART_H_ -#include "third_party/blink/renderer/core/layout/layout_block.h" +#include "third_party/blink/renderer/core/layout/layout_replaced.h" #include "third_party/blink/renderer/core/scroll/scroll_types.h" namespace blink { @@ -34,7 +34,7 @@ namespace blink { class CustomScrollbar; class ScrollableArea; -class LayoutCustomScrollbarPart final : public LayoutBlock { +class CORE_EXPORT LayoutCustomScrollbarPart final : public LayoutReplaced { public: static LayoutCustomScrollbarPart* CreateAnonymous(Document*, ScrollableArea*, @@ -45,45 +45,46 @@ class LayoutCustomScrollbarPart final : public LayoutBlock { PaintLayerType LayerTypeRequired() const override { return kNoPaintLayer; } - void UpdateLayout() override; + // Computes thickness of the scrollbar (which defines thickness of all parts). + // For kScrollbarBGPart only. This can be called during style update. + // Percentage size will be ignored. + int ComputeThickness() const; - static int ComputeScrollbarWidth(int visible_size, const ComputedStyle*); - static int ComputeScrollbarHeight(int visible_size, const ComputedStyle*); + // Computes size of the part in the direction of the scrollbar orientation. + // This doesn't apply to kScrollbarBGPart because its length is not determined + // by the style of the part of itself. For kThumbPart this returns the + // minimum length of the thumb. The length may depend on the size of the + // containing box, so this function can only be called after the size is + // available. + int ComputeLength() const; - // Scrollbar parts needs to be rendered at device pixel boundaries. - LayoutUnit MarginTop() const override { - DCHECK(IsIntegerValue(LayoutBlock::MarginTop())); - return LayoutBlock::MarginTop(); - } - LayoutUnit MarginBottom() const override { - DCHECK(IsIntegerValue(LayoutBlock::MarginBottom())); - return LayoutBlock::MarginBottom(); - } - LayoutUnit MarginLeft() const override { - DCHECK(IsIntegerValue(LayoutBlock::MarginLeft())); - return LayoutBlock::MarginLeft(); - } - LayoutUnit MarginRight() const override { - DCHECK(IsIntegerValue(LayoutBlock::MarginRight())); - return LayoutBlock::MarginRight(); - } + LayoutUnit MarginTop() const override; + LayoutUnit MarginBottom() const override; + LayoutUnit MarginLeft() const override; + LayoutUnit MarginRight() const override; bool IsOfType(LayoutObjectType type) const override { return type == kLayoutObjectLayoutCustomScrollbarPart || - LayoutBlock::IsOfType(type); + LayoutReplaced::IsOfType(type); } ScrollableArea* GetScrollableArea() const { return scrollable_area_; } - protected: - void StyleWillChange(StyleDifference, - const ComputedStyle& new_style) override; + private: + LayoutCustomScrollbarPart(ScrollableArea*, CustomScrollbar*, ScrollbarPart); + + void UpdateFromStyle() override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; void ImageChanged(WrappedImagePtr, CanDeferInvalidation) override; - private: - LayoutCustomScrollbarPart(ScrollableArea*, CustomScrollbar*, ScrollbarPart); + // A scrollbar part's Location() and PhysicalLocation() are relative to the + // scrollbar (instead of relative to any LayoutBox ancestor), and both are + // in physical coordinates. + LayoutBox* LocationContainer() const override { return nullptr; } - MinMaxSizes PreferredLogicalWidths() const override; + // A scrollbar part is not in the layout tree and is not laid out like other + // layout objects. CustomScrollbar will call scrollbar parts' SetFrameRect() + // from its SetFrameRect() when needed. + void UpdateLayout() override { NOTREACHED(); } // Have all padding getters return 0. The important point here is to avoid // resolving percents against the containing block, since scroll bar corners @@ -95,20 +96,13 @@ class LayoutCustomScrollbarPart final : public LayoutBlock { LayoutUnit PaddingLeft() const override { return LayoutUnit(); } LayoutUnit PaddingRight() const override { return LayoutUnit(); } - void LayoutHorizontalPart(); - void LayoutVerticalPart(); - - void UpdateScrollbarWidth(); - void UpdateScrollbarHeight(); - void SetNeedsPaintInvalidation(); - bool AllowsOverflowClip() const override { return false; } - void RecordPercentLengthStats() const; UntracedMember<ScrollableArea> scrollable_area_; UntracedMember<CustomScrollbar> scrollbar_; + ScrollbarPart part_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.cc b/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.cc index 41b92eaa2eb..20d3666cc78 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.cc @@ -92,6 +92,14 @@ FrameView* LayoutEmbeddedContent::ChildFrameView() const { return DynamicTo<FrameView>(GetEmbeddedContentView()); } +LayoutView* LayoutEmbeddedContent::ChildLayoutView() const { + if (HTMLFrameOwnerElement* owner_element = GetFrameOwnerElement()) { + if (Document* content_document = owner_element->contentDocument()) + return content_document->GetLayoutView(); + } + return nullptr; +} + WebPluginContainerImpl* LayoutEmbeddedContent::Plugin() const { EmbeddedContentView* embedded_content_view = GetEmbeddedContentView(); if (embedded_content_view && embedded_content_view->IsPluginView()) @@ -106,44 +114,30 @@ EmbeddedContentView* LayoutEmbeddedContent::GetEmbeddedContentView() const { } PaintLayerType LayoutEmbeddedContent::LayerTypeRequired() const { - if (RequiresAcceleratedCompositing()) + if (AdditionalCompositingReasons()) return kNormalPaintLayer; PaintLayerType type = LayoutReplaced::LayerTypeRequired(); if (type != kNoPaintLayer) return type; - return kForcedPaintLayer; -} -bool LayoutEmbeddedContent::RequiresAcceleratedCompositing() const { - // There are two general cases in which we can return true. First, if this is - // a plugin LayoutObject and the plugin has a layer, then we need a layer. - // Second, if this is a LayoutObject with a contentDocument and that document - // needs a layer, then we need a layer. - WebPluginContainerImpl* plugin_view = Plugin(); - if (plugin_view && plugin_view->CcLayer()) - return true; - - auto* element = GetFrameOwnerElement(); - if (!element) - return false; - - if (Frame* content_frame = element->ContentFrame()) { - if (content_frame->IsRemoteFrame()) - return true; - if (base::FeatureList::IsEnabled( - blink::features::kCompositeCrossOriginIframes) && - content_frame->IsCrossOriginToParentFrame()) { - return true; + // We can't check layout_view->Layer()->GetCompositingReasons() here because + // we're only in style update, so haven't run compositing update yet. + if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + if (LayoutView* child_layout_view = ChildLayoutView()) { + if (child_layout_view->AdditionalCompositingReasons()) + return kNormalPaintLayer; } } - if (Document* content_document = element->contentDocument()) { - auto* layout_view = content_document->GetLayoutView(); - if (layout_view) - return layout_view->UsesCompositing(); - } + return kForcedPaintLayer; +} +bool LayoutEmbeddedContent::ContentDocumentIsCompositing() const { + if (PaintLayerCompositor* inner_compositor = + PaintLayerCompositor::FrameContentsCompositor(*this)) { + return inner_compositor->StaleInCompositingMode(); + } return false; } @@ -253,8 +247,15 @@ bool LayoutEmbeddedContent::NodeAtPoint( } CompositingReasons LayoutEmbeddedContent::AdditionalCompositingReasons() const { - if (RequiresAcceleratedCompositing()) - return CompositingReason::kIFrame; + WebPluginContainerImpl* plugin_view = Plugin(); + if (plugin_view && plugin_view->CcLayer()) + return CompositingReason::kPlugin; + if (auto* element = GetFrameOwnerElement()) { + if (Frame* content_frame = element->ContentFrame()) { + if (content_frame->IsRemoteFrame()) + return CompositingReason::kIFrame; + } + } return CompositingReason::kNone; } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.h b/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.h index ce5d1ef6a97..223f1a6d66e 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_embedded_content.h @@ -44,7 +44,7 @@ class CORE_EXPORT LayoutEmbeddedContent : public LayoutReplaced { explicit LayoutEmbeddedContent(HTMLFrameOwnerElement*); ~LayoutEmbeddedContent() override; - bool RequiresAcceleratedCompositing() const; + bool ContentDocumentIsCompositing() const; bool NodeAtPoint(HitTestResult&, const HitTestLocation&, @@ -59,6 +59,7 @@ class CORE_EXPORT LayoutEmbeddedContent : public LayoutReplaced { // to LayoutObject::GetFrameView which returns the LocalFrameView associated // with the root Document Frame. FrameView* ChildFrameView() const; + LayoutView* ChildLayoutView() const; WebPluginContainerImpl* Plugin() const; EmbeddedContentView* GetEmbeddedContentView() const; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.cc b/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.cc index dc65bd18f88..913e46224b8 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.cc @@ -107,12 +107,6 @@ void LayoutEmbeddedObject::UpdateLayout() { ClearNeedsLayout(); } -CompositingReasons LayoutEmbeddedObject::AdditionalCompositingReasons() const { - if (RequiresAcceleratedCompositing()) - return CompositingReason::kPlugin; - return CompositingReason::kNone; -} - void LayoutEmbeddedObject::ComputeIntrinsicSizingInfo( IntrinsicSizingInfo& intrinsic_sizing_info) const { DCHECK(!ShouldApplySizeContainment()); @@ -122,6 +116,13 @@ void LayoutEmbeddedObject::ComputeIntrinsicSizingInfo( // doesn't know about them. intrinsic_sizing_info.size.Scale(StyleRef().EffectiveZoom()); + // Handle an overridden aspect ratio + if (const base::Optional<IntSize>& aspect_ratio = + StyleRef().AspectRatio()) { + intrinsic_sizing_info.aspect_ratio.SetWidth(aspect_ratio->Width()); + intrinsic_sizing_info.aspect_ratio.SetHeight(aspect_ratio->Height()); + } + if (!IsHorizontalWritingMode()) intrinsic_sizing_info.Transpose(); return; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.h b/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.h index 8aa0c87086b..8cc0293e75b 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_embedded_object.h @@ -62,8 +62,6 @@ class LayoutEmbeddedObject final : public LayoutEmbeddedContent { void ComputeIntrinsicSizingInfo(IntrinsicSizingInfo&) const override; bool NeedsPreferredWidthsRecalculation() const override; - CompositingReasons AdditionalCompositingReasons() const override; - PluginAvailability plugin_availability_ = kPluginAvailable; String unavailable_plugin_replacement_text_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_flexible_box.cc b/chromium/third_party/blink/renderer/core/layout/layout_flexible_box.cc index 379e91b8265..1f75974198c 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_flexible_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_flexible_box.cc @@ -41,6 +41,7 @@ #include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/text_autosizer.h" #include "third_party/blink/renderer/core/paint/block_painter.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" @@ -656,7 +657,7 @@ LayoutUnit LayoutFlexibleBox::ComputeMainAxisExtentForChild( // our logical width is auto, we can just use our cached value. So let's do // that here. (Compare code in LayoutBlock::computePreferredLogicalWidths) if (child.StyleRef().LogicalWidth().IsAuto() && !HasAspectRatio(child)) { - if (size.IsMinContent()) + if (size.IsMinContent() || size.IsMinIntrinsic()) return child.PreferredLogicalWidths().min_size - border_and_padding; if (size.IsMaxContent()) return child.PreferredLogicalWidths().max_size - border_and_padding; @@ -958,9 +959,8 @@ void LayoutFlexibleBox::LayoutFlexItems(bool relayout_children, PaintLayerScrollableArea::PreventRelayoutScope prevent_relayout_scope( layout_scope); - // Set up our master list of flex items. All of the rest of the algorithm - // should work off this list of a subset. - // TODO(cbiesinger): That second part is not yet true. + // Set up our list of flex items. All of the rest of the algorithm should + // work off this list of a subset. ChildLayoutType layout_type = relayout_children ? kForceLayout : kLayoutIfNeeded; const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max()); @@ -1257,7 +1257,7 @@ void LayoutFlexibleBox::ConstructAndAppendFlexItem( algorithm->emplace_back( &child, child.StyleRef(), child_inner_flex_base_size, sizes, /* min_max_cross_sizes */ base::nullopt, main_axis_border_padding, - cross_axis_border_padding, physical_margins); + cross_axis_border_padding, physical_margins, /* unused */ NGBoxStrut()); } void LayoutFlexibleBox::SetOverrideMainAxisContentSizeForChild(FlexItem& item) { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_grid.cc b/chromium/third_party/blink/renderer/core/layout/layout_grid.cc index 6e500101073..3d4fee9109d 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_grid.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_grid.cc @@ -378,8 +378,8 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) { LayoutGridItems(); track_sizing_algorithm_.Reset(); - if (NumTracks(kForRows, *grid_) > 1u && !StyleRef().RowGap().IsNormal() && - StyleRef().RowGap().GetLength().IsPercentOrCalc()) { + if (NumTracks(kForRows, *grid_) > 1u && StyleRef().RowGap() && + StyleRef().RowGap()->IsPercentOrCalc()) { UseCounter::Count(GetDocument(), WebFeature::kGridRowGapPercent); if (!CachedHasDefiniteLogicalHeight()) { UseCounter::Count(GetDocument(), @@ -406,31 +406,31 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) { LayoutUnit LayoutGrid::GridGap( GridTrackSizingDirection direction, base::Optional<LayoutUnit> available_size) const { - const GapLength& gap = + const base::Optional<Length>& gap = direction == kForColumns ? StyleRef().ColumnGap() : StyleRef().RowGap(); - if (gap.IsNormal()) + if (!gap) return LayoutUnit(); - return ValueForLength(gap.GetLength(), available_size.value_or(LayoutUnit())); + return ValueForLength(*gap, available_size.value_or(LayoutUnit())); } LayoutUnit LayoutGrid::GridGap(GridTrackSizingDirection direction) const { LayoutUnit available_size; bool is_row_axis = direction == kForColumns; - const GapLength& gap = + const base::Optional<Length>& gap = is_row_axis ? StyleRef().ColumnGap() : StyleRef().RowGap(); - if (gap.IsNormal()) + if (!gap) return LayoutUnit(); - if (gap.GetLength().IsPercentOrCalc()) { + if (gap->IsPercentOrCalc()) { available_size = is_row_axis ? AvailableLogicalWidth() : ContentLogicalHeight(); } // TODO(rego): Maybe we could cache the computed percentage as a performance // improvement. - return ValueForLength(gap.GetLength(), available_size); + return ValueForLength(*gap, available_size); } LayoutUnit LayoutGrid::GuttersSize( @@ -766,7 +766,7 @@ LayoutGrid::ComputeEmptyTracksForAutoRepeat( is_row_axis ? StyleRef().GridAutoRepeatColumnsInsertionPoint() : StyleRef().GridAutoRepeatRowsInsertionPoint(); size_t first_auto_repeat_track = - insertion_point + std::abs(grid.SmallestTrackStart(direction)); + insertion_point + grid.ExplicitGridStart(direction); size_t last_auto_repeat_track = first_auto_repeat_track + grid.AutoRepeatTracks(direction); @@ -862,9 +862,9 @@ void LayoutGrid::PlaceItemsOnGrid( GridArea area = grid.GridItemArea(*child); if (!area.rows.IsIndefinite()) - area.rows.Translate(abs(grid.SmallestTrackStart(kForRows))); + area.rows.Translate(grid.ExplicitGridStart(kForRows)); if (!area.columns.IsIndefinite()) - area.columns.Translate(abs(grid.SmallestTrackStart(kForColumns))); + area.columns.Translate(grid.ExplicitGridStart(kForColumns)); if (area.rows.IsIndefinite() || area.columns.IsIndefinite()) { grid.SetGridItemArea(*child, area); @@ -954,7 +954,8 @@ void LayoutGrid::PerformGridItemsPreLayout( // TODO (jfernandez): Can we avoid it ? if (IsBaselineAlignmentForChild(*child)) { if (child->HasRelativeLogicalWidth() || - child->HasRelativeLogicalHeight()) { + child->HasRelativeLogicalHeight() || + child->StyleRef().LogicalHeight().IsAuto()) { UpdateGridAreaLogicalSize( *child, algorithm.EstimatedGridAreaBreadthForChild(*child)); } @@ -965,8 +966,8 @@ void LayoutGrid::PerformGridItemsPreLayout( void LayoutGrid::PopulateExplicitGridAndOrderIterator(Grid& grid) const { OrderIteratorPopulator populator(grid.GetOrderIterator()); - int smallest_row_start = 0; - int smallest_column_start = 0; + size_t explicit_row_start = 0; + size_t explicit_column_start = 0; size_t auto_repeat_rows = grid.AutoRepeatTracks(kForRows); size_t auto_repeat_columns = grid.AutoRepeatTracks(kForColumns); @@ -991,8 +992,8 @@ void LayoutGrid::PopulateExplicitGridAndOrderIterator(Grid& grid) const { // |positions| is 0 if we need to run the auto-placement algorithm. if (!row_positions.IsIndefinite()) { - smallest_row_start = - std::min(smallest_row_start, row_positions.UntranslatedStartLine()); + explicit_row_start = std::max<int>( + explicit_row_start, -row_positions.UntranslatedStartLine()); maximum_row_index = std::max<int>(maximum_row_index, row_positions.UntranslatedEndLine()); } else { @@ -1004,8 +1005,8 @@ void LayoutGrid::PopulateExplicitGridAndOrderIterator(Grid& grid) const { } if (!column_positions.IsIndefinite()) { - smallest_column_start = std::min( - smallest_column_start, column_positions.UntranslatedStartLine()); + explicit_column_start = std::max<int>( + explicit_column_start, -column_positions.UntranslatedStartLine()); maximum_column_index = std::max<int>( maximum_column_index, column_positions.UntranslatedEndLine()); } else { @@ -1017,9 +1018,9 @@ void LayoutGrid::PopulateExplicitGridAndOrderIterator(Grid& grid) const { } } - grid.SetSmallestTracksStart(smallest_row_start, smallest_column_start); - grid.EnsureGridSize(maximum_row_index + abs(smallest_row_start), - maximum_column_index + abs(smallest_column_start)); + grid.SetExplicitGridStart(explicit_row_start, explicit_column_start); + grid.EnsureGridSize(maximum_row_index + explicit_row_start, + maximum_column_index + explicit_column_start); } std::unique_ptr<GridArea> @@ -2053,10 +2054,10 @@ LayoutUnit LayoutGrid::GridAreaBreadthForOutOfFlowChild( if (span.IsIndefinite()) return is_row_axis ? ClientLogicalWidth() : ClientLogicalHeight(); - int smallest_start = abs(grid_->SmallestTrackStart(direction)); - int start_line = span.UntranslatedStartLine() + smallest_start; - int end_line = span.UntranslatedEndLine() + smallest_start; - int last_line = NumTracks(direction, *grid_); + size_t explicit_start = grid_->ExplicitGridStart(direction); + size_t start_line = span.UntranslatedStartLine() + explicit_start; + size_t end_line = span.UntranslatedEndLine() + explicit_start; + size_t last_line = NumTracks(direction, *grid_); GridPosition start_position = direction == kForColumns ? child.StyleRef().GridColumnStart() : child.StyleRef().GridRowStart(); @@ -2482,6 +2483,19 @@ size_t LayoutGrid::NumTracks(GridTrackSizingDirection direction, StyleRef(), grid.AutoRepeatTracks(kForColumns)); } +size_t LayoutGrid::ExplicitGridEndForDirection( + GridTrackSizingDirection direction) const { + size_t leading = ExplicitGridStartForDirection(direction); + + if (direction == kForRows) { + return leading + GridPositionsResolver::ExplicitGridRowCount( + StyleRef(), grid_->AutoRepeatTracks(direction)); + } + + return leading + GridPositionsResolver::ExplicitGridColumnCount( + StyleRef(), grid_->AutoRepeatTracks(direction)); +} + LayoutUnit LayoutGrid::GridItemOffset( GridTrackSizingDirection direction) const { return direction == kForRows ? offset_between_rows_.distribution_offset diff --git a/chromium/third_party/blink/renderer/core/layout/layout_grid.h b/chromium/third_party/blink/renderer/core/layout/layout_grid.h index 9b9bb6b31d7..6599f9236bc 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_grid.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_grid.h @@ -87,6 +87,11 @@ class LayoutGrid final : public LayoutBlock { return grid_->AutoRepeatTracks(direction); } + size_t ExplicitGridStartForDirection( + GridTrackSizingDirection direction) const { + return grid_->ExplicitGridStart(direction); + } + LayoutUnit TranslateOutOfFlowRTLCoordinate(const LayoutBox&, LayoutUnit) const; @@ -113,6 +118,8 @@ class LayoutGrid final : public LayoutBlock { StyleContentAlignmentData ContentAlignment(GridTrackSizingDirection) const; + size_t ExplicitGridEndForDirection(GridTrackSizingDirection) const; + // Exposed for testing *ONLY*. Grid* InternalGrid() const { return grid_.get(); } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_image.cc b/chromium/third_party/blink/renderer/core/layout/layout_image.cc index 6f2a3ae49ac..eff5e034b5c 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_image.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_image.cc @@ -116,7 +116,7 @@ void LayoutImage::ImageChanged(WrappedImagePtr new_image, // If error occurred, image marker should be replaced by a LayoutText. // NotifyOfSubtreeChange to make list item updating its marker content. - if (IsLayoutNGListMarkerImage() && image_resource_->ErrorOccurred()) + if (IsListMarkerImage() && image_resource_->ErrorOccurred()) NotifyOfSubtreeChange(); // Per the spec, we let the server-sent header override srcset/other sources @@ -363,6 +363,13 @@ void LayoutImage::ComputeIntrinsicSizingInfo( if (StyleRef().GetObjectFit() != EObjectFit::kScaleDown) intrinsic_sizing_info.size.Scale(ImageDevicePixelRatio()); + // Handle an overridden aspect ratio + if (const base::Optional<IntSize>& aspect_ratio = + StyleRef().AspectRatio()) { + intrinsic_sizing_info.aspect_ratio.SetWidth(aspect_ratio->Width()); + intrinsic_sizing_info.aspect_ratio.SetHeight(aspect_ratio->Height()); + } + if (!IsHorizontalWritingMode()) intrinsic_sizing_info.Transpose(); return; @@ -373,7 +380,7 @@ void LayoutImage::ComputeIntrinsicSizingInfo( // Our intrinsicSize is empty if we're laying out generated images with // relative width/height. Figure out the right intrinsic size to use. if (intrinsic_sizing_info.size.IsEmpty() && - !image_resource_->HasIntrinsicSize() && !IsLayoutNGListMarkerImage()) { + !image_resource_->HasIntrinsicSize() && !IsListMarkerImage()) { if (HasOverrideContainingBlockContentLogicalWidth() && HasOverrideContainingBlockContentLogicalHeight()) { intrinsic_sizing_info.size.SetWidth( diff --git a/chromium/third_party/blink/renderer/core/layout/layout_image_resource.h b/chromium/third_party/blink/renderer/core/layout/layout_image_resource.h index 808a0958044..286f9912e18 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_image_resource.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_image_resource.h @@ -70,7 +70,7 @@ class CORE_EXPORT LayoutImageResource const LayoutSize&) const; virtual WrappedImagePtr ImagePtr() const { return cached_image_.Get(); } - virtual void Trace(Visitor* visitor) { visitor->Trace(cached_image_); } + virtual void Trace(Visitor* visitor) const { visitor->Trace(cached_image_); } protected: // Device scale factor for the associated LayoutObject. diff --git a/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.cc b/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.cc index ed8c3dfe576..db5476d73ee 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.cc @@ -28,6 +28,7 @@ #include "third_party/blink/renderer/core/layout/layout_image_resource_style_image.h" +#include "third_party/blink/renderer/core/layout/layout_list_marker_image.h" #include "third_party/blink/renderer/core/layout/layout_replaced.h" #include "third_party/blink/renderer/core/style/style_fetched_image.h" @@ -70,9 +71,12 @@ scoped_refptr<Image> LayoutImageResourceStyleImage::GetImage( FloatSize LayoutImageResourceStyleImage::ImageSize(float multiplier) const { // TODO(davve): Find out the correct default object size in this context. - return ImageSizeWithDefaultSize(multiplier, - LayoutSize(LayoutReplaced::kDefaultWidth, - LayoutReplaced::kDefaultHeight)); + LayoutSize default_size = + layout_object_->IsListMarkerImage() + ? ToLayoutListMarkerImage(layout_object_)->DefaultSize() + : LayoutSize(LayoutReplaced::kDefaultWidth, + LayoutReplaced::kDefaultHeight); + return ImageSizeWithDefaultSize(multiplier, default_size); } FloatSize LayoutImageResourceStyleImage::ImageSizeWithDefaultSize( @@ -82,7 +86,7 @@ FloatSize LayoutImageResourceStyleImage::ImageSizeWithDefaultSize( layout_object_->GetDocument(), multiplier, default_size, LayoutObject::ShouldRespectImageOrientation(layout_object_)); } -void LayoutImageResourceStyleImage::Trace(Visitor* visitor) { +void LayoutImageResourceStyleImage::Trace(Visitor* visitor) const { visitor->Trace(style_image_); LayoutImageResource::Trace(visitor); } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.h b/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.h index 108d42d137f..b1b08dd182c 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_image_resource_style_image.h @@ -55,7 +55,7 @@ class LayoutImageResourceStyleImage final : public LayoutImageResource { const LayoutSize&) const override; WrappedImagePtr ImagePtr() const override { return style_image_->Data(); } - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: Member<StyleImage> style_image_; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_inline.cc b/chromium/third_party/blink/renderer/core/layout/layout_inline.cc index d55ad530de2..3e25c8e4e67 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_inline.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_inline.cc @@ -470,8 +470,16 @@ LayoutRect LayoutInline::LocalCaretRect( LayoutRect caret_rect = LocalCaretRectForEmptyElement(BorderAndPaddingWidth(), LayoutUnit()); - if (InlineBox* first_box = FirstLineBox()) + if (InlineBox* first_box = FirstLineBox()) { caret_rect.MoveBy(first_box->Location()); + } else if (IsInLayoutNGInlineFormattingContext()) { + NGInlineCursor cursor; + cursor.MoveTo(*this); + if (cursor) { + caret_rect.MoveBy( + cursor.Current().OffsetInContainerBlock().ToLayoutPoint()); + } + } return caret_rect; } @@ -1786,27 +1794,25 @@ void LayoutInline::InvalidateDisplayItemClients( PaintInvalidationReason invalidation_reason) const { ObjectPaintInvalidator paint_invalidator(*this); - if (RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled() && - !RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - auto fragments = NGPaintFragment::InlineFragmentsFor(this); - if (fragments.IsInLayoutNGInlineFormattingContext()) { - for (NGPaintFragment* fragment : fragments) { - paint_invalidator.InvalidateDisplayItemClient(*fragment, - invalidation_reason); - } - return; - } - } - if (IsInLayoutNGInlineFormattingContext()) { if (!ShouldCreateBoxFragment()) return; - NGInlineCursor cursor; - for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) { - DCHECK_EQ(cursor.Current().GetLayoutObject(), this); - paint_invalidator.InvalidateDisplayItemClient( - *cursor.Current().GetDisplayItemClient(), invalidation_reason); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + NGInlineCursor cursor; + for (cursor.MoveTo(*this); cursor; + cursor.MoveToNextForSameLayoutObject()) { + DCHECK_EQ(cursor.Current().GetLayoutObject(), this); + paint_invalidator.InvalidateDisplayItemClient( + *cursor.Current().GetDisplayItemClient(), invalidation_reason); + } + return; } +#if DCHECK_IS_ON() + NGInlineCursor cursor; + for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) + DCHECK_EQ(cursor.Current().GetDisplayItemClient(), this); +#endif + paint_invalidator.InvalidateDisplayItemClient(*this, invalidation_reason); return; } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_inline_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_inline_test.cc index f2af84a79e1..c6c6352c2d8 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_inline_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_inline_test.cc @@ -111,14 +111,6 @@ TEST_F(LayoutInlineTest, RegionHitTest) { ToLayoutInline(GetLayoutObjectByElementId("lotsOfBoxes")); ASSERT_TRUE(lots_of_boxes); - if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - NGInlineCursor cursor; - cursor.MoveTo(*lots_of_boxes); - ASSERT_TRUE(cursor); - EXPECT_EQ(lots_of_boxes, cursor.Current().GetLayoutObject()); - return; - } - HitTestRequest hit_request(HitTestRequest::kTouchEvent | HitTestRequest::kListBased); @@ -149,9 +141,10 @@ TEST_F(LayoutInlineTest, RegionHitTest) { } const auto* div = To<LayoutBlockFlow>(lots_of_boxes->Parent()); - for (const NGPaintFragment* line : div->PaintFragment()->Children()) { - DCHECK(line->PhysicalFragment().IsLineBox()); - NGInlineCursor line_cursor(*line); + NGInlineCursor cursor(*div); + for (cursor.MoveToFirstLine(); cursor; cursor.MoveToNextLine()) { + DCHECK(cursor.Current().IsLineBox()); + NGInlineCursor line_cursor = cursor.CursorForDescendants(); bool hit_outcome = lots_of_boxes->HitTestCulledInline( hit_result, location, hit_offset, &line_cursor); EXPECT_FALSE(hit_outcome); @@ -287,6 +280,34 @@ TEST_P(ParameterizedLayoutInlineTest, MultilineRelativePositionedHitTest) { } } +TEST_P(ParameterizedLayoutInlineTest, HitTestCulledInlinePreWrap) { + SetBodyInnerHTML(R"HTML( + <style> + html, body { margin: 0; } + body { + width: 250px; + } + span { + white-space: pre-wrap; + font: 30px serif; + } + </style> + <div id="container"> + <span id="span">The quick brown fox jumps over the lazy dog.</span> + </div> + )HTML"); + HitTestRequest hit_request(HitTestRequest::kReadOnly); + PhysicalOffset hit_location(100, 15); + HitTestLocation location(hit_location); + HitTestResult hit_result(hit_request, location); + LayoutObject* container = GetLayoutObjectByElementId("container"); + container->HitTestAllPhases(hit_result, location, PhysicalOffset()); + + Element* span = GetElementById("span"); + Node* text_node = span->firstChild(); + EXPECT_EQ(hit_result.InnerNode(), text_node); +} + TEST_P(ParameterizedLayoutInlineTest, VisualRectInDocument) { LoadAhem(); SetBodyInnerHTML(R"HTML( diff --git a/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.cc index ada7d6b11d0..d18bce73b67 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.cc @@ -7,7 +7,7 @@ namespace blink { LayoutInsideListMarker::LayoutInsideListMarker(Element* element) - : LayoutListMarker(element) {} + : LayoutInline(element) {} LayoutInsideListMarker::~LayoutInsideListMarker() = default; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.h b/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.h index 22426dfdec1..b5a8d26fa23 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_inside_list_marker.h @@ -6,27 +6,34 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_INSIDE_LIST_MARKER_H_ #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/layout_list_marker.h" +#include "third_party/blink/renderer/core/layout/layout_inline.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" namespace blink { -// Used to layout the list item's inside marker. +// Used to layout a list item's inside marker with non-normal 'content'. // The LayoutInsideListMarker always has to be a child of a LayoutListItem. -class CORE_EXPORT LayoutInsideListMarker final : public LayoutListMarker { +class CORE_EXPORT LayoutInsideListMarker final : public LayoutInline { public: explicit LayoutInsideListMarker(Element*); ~LayoutInsideListMarker() override; const char* GetName() const override { return "LayoutInsideListMarker"; } + const ListMarker& Marker() const { return list_marker_; } + ListMarker& Marker() { return list_marker_; } + private: bool IsOfType(LayoutObjectType type) const override { return type == kLayoutObjectInsideListMarker || - LayoutListMarker::IsOfType(type); + LayoutInline::IsOfType(type); } + + ListMarker list_marker_; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutInsideListMarker, IsInsideListMarker()); +DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutInsideListMarker, + IsInsideListMarkerForCustomContent()); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_list_item.cc b/chromium/third_party/blink/renderer/core/layout/layout_list_item.cc index df0dd904451..c519fb4b27c 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_list_item.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_list_item.cc @@ -279,8 +279,7 @@ bool LayoutListItem::UpdateMarkerLocation() { } } - if (!marker_parent || - (marker_parent != line_box_parent && NormalChildNeedsLayout())) { + if (!marker_parent || marker_parent != line_box_parent) { marker->Remove(); line_box_parent->AddChild(marker, FirstNonMarkerChild(line_box_parent)); // TODO(rhogan): line_box_parent and marker_parent may be deleted by diff --git a/chromium/third_party/blink/renderer/core/layout/layout_list_item.h b/chromium/third_party/blink/renderer/core/layout/layout_list_item.h index b324ba8020a..df62f2b0a49 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_list_item.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_list_item.h @@ -44,9 +44,8 @@ class LayoutListItem final : public LayoutBlockFlow { Element* list_item = To<Element>(GetNode()); if (LayoutObject* marker = list_item->PseudoElementLayoutObject(kPseudoIdMarker)) { - if (marker->IsListMarker()) + if (marker->IsListMarkerForNormalContent()) return ToLayoutListMarker(marker); - NOTREACHED(); } return nullptr; } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/layout_list_marker.cc index 0425425e887..eb35e4a09d2 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_list_marker.cc @@ -28,19 +28,13 @@ #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" #include "third_party/blink/renderer/core/layout/layout_list_item.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" #include "third_party/blink/renderer/core/layout/list_marker_text.h" #include "third_party/blink/renderer/core/paint/list_marker_painter.h" #include "third_party/blink/renderer/platform/fonts/font.h" namespace blink { -const int kCMarkerPaddingPx = 7; - -// TODO(glebl): Move to core/html/resources/html.css after -// Blink starts to support ::marker crbug.com/457718 -// Recommended UA margin for list markers. -const int kCUAMarkerMarginEm = 1; - LayoutListMarker::LayoutListMarker(Element* element) : LayoutBox(element) { DCHECK(ListItem()); SetInline(true); @@ -183,24 +177,24 @@ void LayoutListMarker::UpdateContent() { return; switch (GetListStyleCategory()) { - case ListStyleCategory::kNone: + case ListMarker::ListStyleCategory::kNone: break; - case ListStyleCategory::kSymbol: + case ListMarker::ListStyleCategory::kSymbol: text_ = list_marker_text::GetText(StyleRef().ListStyleType(), 0); // value is ignored for these types break; - case ListStyleCategory::kLanguage: + case ListMarker::ListStyleCategory::kLanguage: text_ = list_marker_text::GetText(StyleRef().ListStyleType(), ListItem()->Value()); break; - case ListStyleCategory::kStaticString: + case ListMarker::ListStyleCategory::kStaticString: text_ = StyleRef().ListStyleStringValue(); break; } } String LayoutListMarker::TextAlternative() const { - if (GetListStyleCategory() == ListStyleCategory::kStaticString) + if (GetListStyleCategory() == ListMarker::ListStyleCategory::kStaticString) return text_; UChar suffix = list_marker_text::Suffix(StyleRef().ListStyleType(), ListItem()->Value()); @@ -208,13 +202,14 @@ String LayoutListMarker::TextAlternative() const { return text_ + suffix + ' '; } -LayoutUnit LayoutListMarker::GetWidthOfText(ListStyleCategory category) const { +LayoutUnit LayoutListMarker::GetWidthOfText( + ListMarker::ListStyleCategory category) const { // TODO(crbug.com/1012289): this code doesn't support bidi algorithm. if (text_.IsEmpty()) return LayoutUnit(); const Font& font = StyleRef().GetFont(); LayoutUnit item_width = LayoutUnit(font.Width(TextRun(text_))); - if (category == ListStyleCategory::kStaticString) { + if (category == ListMarker::ListStyleCategory::kStaticString) { // Don't add a suffix. return item_width; } @@ -239,15 +234,15 @@ MinMaxSizes LayoutListMarker::ComputeIntrinsicLogicalWidths() const { sizes = StyleRef().IsHorizontalWritingMode() ? image_size.Width() : image_size.Height(); } else { - ListStyleCategory category = GetListStyleCategory(); + ListMarker::ListStyleCategory category = GetListStyleCategory(); switch (category) { - case ListStyleCategory::kNone: + case ListMarker::ListStyleCategory::kNone: break; - case ListStyleCategory::kSymbol: - sizes = WidthOfSymbol(StyleRef()); + case ListMarker::ListStyleCategory::kSymbol: + sizes = ListMarker::WidthOfSymbol(StyleRef()); break; - case ListStyleCategory::kLanguage: - case ListStyleCategory::kStaticString: + case ListMarker::ListStyleCategory::kLanguage: + case ListMarker::ListStyleCategory::kStaticString: sizes = GetWidthOfText(category); break; } @@ -261,82 +256,22 @@ MinMaxSizes LayoutListMarker::PreferredLogicalWidths() const { return IntrinsicLogicalWidths(); } -LayoutUnit LayoutListMarker::WidthOfSymbol(const ComputedStyle& style) { - const Font& font = style.GetFont(); - const SimpleFontData* font_data = font.PrimaryFont(); - DCHECK(font_data); - if (!font_data) - return LayoutUnit(); - return LayoutUnit((font_data->GetFontMetrics().Ascent() * 2 / 3 + 1) / 2 + 2); -} - void LayoutListMarker::UpdateMargins(LayoutUnit marker_inline_size) { LayoutUnit margin_start; LayoutUnit margin_end; const ComputedStyle& style = StyleRef(); - if (IsInsideListMarker()) { + if (IsInside()) { std::tie(margin_start, margin_end) = - InlineMarginsForInside(style, IsImage()); + ListMarker::InlineMarginsForInside(style, IsImage()); } else { - std::tie(margin_start, margin_end) = - InlineMarginsForOutside(style, IsImage(), marker_inline_size); + std::tie(margin_start, margin_end) = ListMarker::InlineMarginsForOutside( + style, IsImage(), marker_inline_size); } SetMarginStart(margin_start); SetMarginEnd(margin_end); } -std::pair<LayoutUnit, LayoutUnit> LayoutListMarker::InlineMarginsForInside( - const ComputedStyle& style, - bool is_image) { - if (!style.ContentBehavesAsNormal()) - return {}; - if (is_image) - return {LayoutUnit(), LayoutUnit(kCMarkerPaddingPx)}; - switch (GetListStyleCategory(style.ListStyleType())) { - case ListStyleCategory::kSymbol: - return {LayoutUnit(-1), - LayoutUnit(kCUAMarkerMarginEm * style.ComputedFontSize())}; - default: - break; - } - return {}; -} - -std::pair<LayoutUnit, LayoutUnit> LayoutListMarker::InlineMarginsForOutside( - const ComputedStyle& style, - bool is_image, - LayoutUnit marker_inline_size) { - LayoutUnit margin_start; - LayoutUnit margin_end; - if (!style.ContentBehavesAsNormal()) { - margin_start = -marker_inline_size; - } else if (is_image) { - margin_start = -marker_inline_size - kCMarkerPaddingPx; - margin_end = LayoutUnit(kCMarkerPaddingPx); - } else { - switch (GetListStyleCategory(style.ListStyleType())) { - case ListStyleCategory::kNone: - break; - case ListStyleCategory::kSymbol: { - const SimpleFontData* font_data = style.GetFont().PrimaryFont(); - DCHECK(font_data); - if (!font_data) - return {}; - const FontMetrics& font_metrics = font_data->GetFontMetrics(); - int offset = font_metrics.Ascent() * 2 / 3; - margin_start = LayoutUnit(-offset - kCMarkerPaddingPx - 1); - margin_end = offset + kCMarkerPaddingPx + 1 - marker_inline_size; - break; - } - default: - margin_start = -marker_inline_size; - } - } - DCHECK_EQ(margin_start + margin_end, -marker_inline_size); - return {margin_start, margin_end}; -} - LayoutUnit LayoutListMarker::LineHeight( bool first_line, LineDirectionMode direction, @@ -360,79 +295,16 @@ LayoutUnit LayoutListMarker::BaselinePosition( line_position_mode); } -LayoutListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory() - const { - return GetListStyleCategory(StyleRef().ListStyleType()); +ListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory() const { + return ListMarker::GetListStyleCategory(StyleRef().ListStyleType()); } -LayoutListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory( - EListStyleType type) { - switch (type) { - case EListStyleType::kNone: - return ListStyleCategory::kNone; - case EListStyleType::kString: - return ListStyleCategory::kStaticString; - case EListStyleType::kDisc: - case EListStyleType::kCircle: - case EListStyleType::kSquare: - return ListStyleCategory::kSymbol; - case EListStyleType::kArabicIndic: - case EListStyleType::kArmenian: - case EListStyleType::kBengali: - case EListStyleType::kCambodian: - case EListStyleType::kCjkIdeographic: - case EListStyleType::kCjkEarthlyBranch: - case EListStyleType::kCjkHeavenlyStem: - case EListStyleType::kDecimalLeadingZero: - case EListStyleType::kDecimal: - case EListStyleType::kDevanagari: - case EListStyleType::kEthiopicHalehame: - case EListStyleType::kEthiopicHalehameAm: - case EListStyleType::kEthiopicHalehameTiEr: - case EListStyleType::kEthiopicHalehameTiEt: - case EListStyleType::kGeorgian: - case EListStyleType::kGujarati: - case EListStyleType::kGurmukhi: - case EListStyleType::kHangul: - case EListStyleType::kHangulConsonant: - case EListStyleType::kHebrew: - case EListStyleType::kHiragana: - case EListStyleType::kHiraganaIroha: - case EListStyleType::kKannada: - case EListStyleType::kKatakana: - case EListStyleType::kKatakanaIroha: - case EListStyleType::kKhmer: - case EListStyleType::kKoreanHangulFormal: - case EListStyleType::kKoreanHanjaFormal: - case EListStyleType::kKoreanHanjaInformal: - case EListStyleType::kLao: - case EListStyleType::kLowerAlpha: - case EListStyleType::kLowerArmenian: - case EListStyleType::kLowerGreek: - case EListStyleType::kLowerLatin: - case EListStyleType::kLowerRoman: - case EListStyleType::kMalayalam: - case EListStyleType::kMongolian: - case EListStyleType::kMyanmar: - case EListStyleType::kOriya: - case EListStyleType::kPersian: - case EListStyleType::kSimpChineseFormal: - case EListStyleType::kSimpChineseInformal: - case EListStyleType::kTelugu: - case EListStyleType::kThai: - case EListStyleType::kTibetan: - case EListStyleType::kTradChineseFormal: - case EListStyleType::kTradChineseInformal: - case EListStyleType::kUpperAlpha: - case EListStyleType::kUpperArmenian: - case EListStyleType::kUpperLatin: - case EListStyleType::kUpperRoman: - case EListStyleType::kUrdu: - return ListStyleCategory::kLanguage; - default: - NOTREACHED(); - return ListStyleCategory::kLanguage; - } +bool LayoutListMarker::IsInside() const { + const LayoutListItem* list_item = ListItem(); + const ComputedStyle& parent_style = list_item->StyleRef(); + return parent_style.ListStylePosition() == EListStylePosition::kInside || + (IsA<HTMLLIElement>(list_item->GetNode()) && + !parent_style.IsInsideListElement()); } LayoutRect LayoutListMarker::GetRelativeMarkerRect() const { @@ -440,14 +312,14 @@ LayoutRect LayoutListMarker::GetRelativeMarkerRect() const { return LayoutRect(LayoutPoint(), ImageBulletSize()); LayoutRect relative_rect; - ListStyleCategory category = GetListStyleCategory(); + ListMarker::ListStyleCategory category = GetListStyleCategory(); switch (category) { - case ListStyleCategory::kNone: + case ListMarker::ListStyleCategory::kNone: return LayoutRect(); - case ListStyleCategory::kSymbol: - return RelativeSymbolMarkerRect(StyleRef(), Size().Width()); - case ListStyleCategory::kLanguage: - case ListStyleCategory::kStaticString: { + case ListMarker::ListStyleCategory::kSymbol: + return ListMarker::RelativeSymbolMarkerRect(StyleRef(), Size().Width()); + case ListMarker::ListStyleCategory::kLanguage: + case ListMarker::ListStyleCategory::kStaticString: { const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont(); DCHECK(font_data); if (!font_data) @@ -467,27 +339,4 @@ LayoutRect LayoutListMarker::GetRelativeMarkerRect() const { return relative_rect; } -LayoutRect LayoutListMarker::RelativeSymbolMarkerRect( - const ComputedStyle& style, - LayoutUnit width) { - LayoutRect relative_rect; - const SimpleFontData* font_data = style.GetFont().PrimaryFont(); - DCHECK(font_data); - if (!font_data) - return LayoutRect(); - - // TODO(wkorman): Review and clean up/document the calculations below. - // http://crbug.com/543193 - const FontMetrics& font_metrics = font_data->GetFontMetrics(); - int ascent = font_metrics.Ascent(); - int bullet_width = (ascent * 2 / 3 + 1) / 2; - relative_rect = LayoutRect(1, 3 * (ascent - ascent * 2 / 3) / 2, bullet_width, - bullet_width); - if (!style.IsHorizontalWritingMode()) { - relative_rect = relative_rect.TransposedRect(); - relative_rect.SetX(width - relative_rect.X() - relative_rect.Width()); - } - return relative_rect; -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_list_marker.h b/chromium/third_party/blink/renderer/core/layout/layout_list_marker.h index 1fee564ee6e..5d65fd3b0fa 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_list_marker.h @@ -26,13 +26,15 @@ #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/list_marker.h" namespace blink { class LayoutListItem; -// This class holds code shared among legacy classes for list markers. -class CORE_EXPORT LayoutListMarker : public LayoutBox { +// Used to layout a list item's marker with 'content: normal'. +// The LayoutListMarker always has to be a child of a LayoutListItem. +class CORE_EXPORT LayoutListMarker final : public LayoutBox { public: explicit LayoutListMarker(Element*); ~LayoutListMarker() override; @@ -43,35 +45,21 @@ class CORE_EXPORT LayoutListMarker : public LayoutBox { // Marker text with suffix, e.g. "1. ", for use in accessibility. String TextAlternative() const; - // A reduced set of list style categories allowing for more concise expression - // of list style specific logic. - enum class ListStyleCategory { kNone, kSymbol, kLanguage, kStaticString }; + ListMarker::ListStyleCategory GetListStyleCategory() const; - // Returns the list's style as one of a reduced high level categorical set of - // styles. - ListStyleCategory GetListStyleCategory() const; - static ListStyleCategory GetListStyleCategory(EListStyleType); + bool IsInside() const; void UpdateMarginsAndContent(); - // Compute inline margins for 'list-style-position: inside' and 'outside'. - static std::pair<LayoutUnit, LayoutUnit> InlineMarginsForInside( - const ComputedStyle&, - bool is_image); - static std::pair<LayoutUnit, LayoutUnit> InlineMarginsForOutside( - const ComputedStyle&, - bool is_image, - LayoutUnit marker_inline_size); - LayoutRect GetRelativeMarkerRect() const; - static LayoutRect RelativeSymbolMarkerRect(const ComputedStyle&, LayoutUnit); - static LayoutUnit WidthOfSymbol(const ComputedStyle&); bool IsImage() const override; const StyleImage* GetImage() const { return image_.Get(); } const LayoutListItem* ListItem() const; LayoutSize ImageBulletSize() const; + const char* GetName() const override { return "LayoutListMarker"; } + LayoutUnit LineOffset() const { return line_offset_; } protected: @@ -81,6 +69,10 @@ class CORE_EXPORT LayoutListMarker : public LayoutBox { MinMaxSizes ComputeIntrinsicLogicalWidths() const override; MinMaxSizes PreferredLogicalWidths() const override; + bool IsOfType(LayoutObjectType type) const override { + return type == kLayoutObjectListMarker || LayoutBox::IsOfType(type); + } + void Paint(const PaintInfo&) const override; void UpdateLayout() override; @@ -101,7 +93,7 @@ class CORE_EXPORT LayoutListMarker : public LayoutBox { bool IsText() const { return !IsImage(); } - LayoutUnit GetWidthOfText(ListStyleCategory) const; + LayoutUnit GetWidthOfText(ListMarker::ListStyleCategory) const; void UpdateMargins(LayoutUnit marker_inline_size); void UpdateContent(); @@ -114,7 +106,8 @@ class CORE_EXPORT LayoutListMarker : public LayoutBox { LayoutUnit line_offset_; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutListMarker, IsListMarker()); +DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutListMarker, + IsListMarkerForNormalContent()); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.cc b/chromium/third_party/blink/renderer/core/layout/layout_list_marker_image.cc index 0ff22412114..ae1b5fb8774 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_list_marker_image.cc @@ -1,8 +1,8 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2020 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_list_marker_image.h" +#include "third_party/blink/renderer/core/layout/layout_list_marker_image.h" #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" @@ -10,35 +10,37 @@ namespace blink { -LayoutNGListMarkerImage::LayoutNGListMarkerImage(Element* element) +LayoutListMarkerImage::LayoutListMarkerImage(Element* element) : LayoutImage(element) {} -LayoutNGListMarkerImage* LayoutNGListMarkerImage::CreateAnonymous( +LayoutListMarkerImage* LayoutListMarkerImage::CreateAnonymous( Document* document) { - LayoutNGListMarkerImage* object = new LayoutNGListMarkerImage(nullptr); + LayoutListMarkerImage* object = new LayoutListMarkerImage(nullptr); object->SetDocumentForAnonymous(document); return object; } -bool LayoutNGListMarkerImage::IsOfType(LayoutObjectType type) const { - return type == kLayoutObjectNGListMarkerImage || LayoutImage::IsOfType(type); +bool LayoutListMarkerImage::IsOfType(LayoutObjectType type) const { + return type == kLayoutObjectListMarkerImage || LayoutImage::IsOfType(type); } -// Because ImageResource() is always LayoutImageResourceStyleImage. So we could -// use StyleImage::ImageSize to determine the concrete object size with -// default object size(ascent/2 x ascent/2). -void LayoutNGListMarkerImage::ComputeIntrinsicSizingInfoByDefaultSize( - IntrinsicSizingInfo& intrinsic_sizing_info) const { +LayoutSize LayoutListMarkerImage::DefaultSize() const { const SimpleFontData* font_data = Style()->GetFont().PrimaryFont(); DCHECK(font_data); if (!font_data) - return; - + return LayoutSize(kDefaultWidth, kDefaultHeight); LayoutUnit bullet_width = font_data->GetFontMetrics().Ascent() / LayoutUnit(2); - LayoutSize default_object_size(bullet_width, bullet_width); + return LayoutSize(bullet_width, bullet_width); +} + +// Because ImageResource() is always LayoutImageResourceStyleImage. So we could +// use StyleImage::ImageSize to determine the concrete object size with +// default object size(ascent/2 x ascent/2). +void LayoutListMarkerImage::ComputeIntrinsicSizingInfoByDefaultSize( + IntrinsicSizingInfo& intrinsic_sizing_info) const { FloatSize concrete_size = ImageResource()->ImageSizeWithDefaultSize( - Style()->EffectiveZoom(), default_object_size); + Style()->EffectiveZoom(), DefaultSize()); concrete_size.Scale(ImageDevicePixelRatio()); LayoutSize image_size(RoundedLayoutSize(concrete_size)); @@ -48,7 +50,7 @@ void LayoutNGListMarkerImage::ComputeIntrinsicSizingInfoByDefaultSize( intrinsic_sizing_info.has_height = true; } -void LayoutNGListMarkerImage::ComputeIntrinsicSizingInfo( +void LayoutListMarkerImage::ComputeIntrinsicSizingInfo( IntrinsicSizingInfo& intrinsic_sizing_info) const { LayoutImage::ComputeIntrinsicSizingInfo(intrinsic_sizing_info); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_list_marker_image.h b/chromium/third_party/blink/renderer/core/layout/layout_list_marker_image.h new file mode 100644 index 00000000000..878cde826a1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/layout_list_marker_image.h @@ -0,0 +1,36 @@ +// Copyright 2020 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_LAYOUT_LIST_MARKER_IMAGE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_LIST_MARKER_IMAGE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/layout_image.h" + +namespace blink { + +class Document; + +class CORE_EXPORT LayoutListMarkerImage final : public LayoutImage { + public: + explicit LayoutListMarkerImage(Element*); + static LayoutListMarkerImage* CreateAnonymous(Document*); + + bool IsLayoutNGObject() const override { + return IsLayoutNGObjectForListMarkerImage(); + } + LayoutSize DefaultSize() const; + + private: + bool IsOfType(LayoutObjectType) const override; + + void ComputeIntrinsicSizingInfoByDefaultSize(IntrinsicSizingInfo&) const; + void ComputeIntrinsicSizingInfo(IntrinsicSizingInfo&) const final; +}; + +DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutListMarkerImage, IsListMarkerImage()); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_LIST_MARKER_IMAGE_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc b/chromium/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc index 19754331b32..fee52b9ac31 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc @@ -108,7 +108,7 @@ static inline bool CanContainSpannerInParentFragmentationContext( if (!block_flow) return false; return !block_flow->CreatesNewFormattingContext() && - !block_flow->StyleRef().CanContainFixedPositionObjects(false) && + !block_flow->CanContainFixedPositionObjects() && block_flow->GetPaginationBreakability() != LayoutBox::kForbidBreaks && !IsMultiColumnContainer(*block_flow); } @@ -778,11 +778,11 @@ void LayoutMultiColumnFlowThread::CalculateColumnCountAndWidth( LayoutUnit LayoutMultiColumnFlowThread::ColumnGap(const ComputedStyle& style, LayoutUnit available_width) { - if (style.ColumnGap().IsNormal()) { - // "1em" is recommended as the normal gap setting. Matches <p> margins. - return LayoutUnit(style.GetFontDescription().ComputedSize()); - } - return ValueForLength(style.ColumnGap().GetLength(), available_width); + if (const base::Optional<Length>& column_gap = style.ColumnGap()) + return ValueForLength(*column_gap, available_width); + + // "1em" is recommended as the normal gap setting. Matches <p> margins. + return LayoutUnit(style.GetFontDescription().ComputedSize()); } void LayoutMultiColumnFlowThread::CreateAndInsertMultiColumnSet( @@ -1165,6 +1165,7 @@ void LayoutMultiColumnFlowThread::FlowThreadDescendantWillBeRemoved( } static inline bool NeedsToReinsertIntoFlowThread( + const LayoutBox& box, const ComputedStyle& old_style, const ComputedStyle& new_style) { // If we've become (or are about to become) a container for absolutely @@ -1172,13 +1173,14 @@ static inline bool NeedsToReinsertIntoFlowThread( // re-evaluate the need for column sets. There may be out-of-flow descendants // further down that become part of the flow thread, or cease to be part of // the flow thread, because of this change. - if (old_style.CanContainFixedPositionObjects(false) != - new_style.CanContainFixedPositionObjects(false)) + if (box.ComputeIsFixedContainer(&old_style) != + box.ComputeIsFixedContainer(&new_style)) return true; return old_style.GetPosition() != new_style.GetPosition(); } -static inline bool NeedsToRemoveFromFlowThread(const ComputedStyle& old_style, +static inline bool NeedsToRemoveFromFlowThread(const LayoutBox& box, + const ComputedStyle& old_style, const ComputedStyle& new_style) { // This function is called BEFORE computed style update. If an in-flow // descendant goes out-of-flow, we may have to remove column sets and spanner @@ -1192,7 +1194,7 @@ static inline bool NeedsToRemoveFromFlowThread(const ComputedStyle& old_style, // been updated. return (new_style.HasOutOfFlowPosition() && !old_style.HasOutOfFlowPosition()) || - NeedsToReinsertIntoFlowThread(old_style, new_style); + NeedsToReinsertIntoFlowThread(box, old_style, new_style); } static inline bool NeedsToInsertIntoFlowThread( @@ -1218,7 +1220,7 @@ static inline bool NeedsToInsertIntoFlowThread( if (containing_flow_thread == flow_thread) return true; } - return NeedsToReinsertIntoFlowThread(old_style, new_style); + return NeedsToReinsertIntoFlowThread(*flow_thread, old_style, new_style); } void LayoutMultiColumnFlowThread::FlowThreadDescendantStyleWillChange( @@ -1226,7 +1228,8 @@ void LayoutMultiColumnFlowThread::FlowThreadDescendantStyleWillChange( StyleDifference diff, const ComputedStyle& new_style) { toggle_spanners_if_needed_ = false; - if (NeedsToRemoveFromFlowThread(descendant->StyleRef(), new_style)) { + if (NeedsToRemoveFromFlowThread(*descendant, descendant->StyleRef(), + new_style)) { FlowThreadDescendantWillBeRemoved(descendant); return; } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_multi_column_set.cc b/chromium/third_party/blink/renderer/core/layout/layout_multi_column_set.cc index 05575c71fc9..5c1b83304f1 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_multi_column_set.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_multi_column_set.cc @@ -488,13 +488,13 @@ PositionWithAffinity LayoutMultiColumnSet::PositionForPoint( LayoutUnit LayoutMultiColumnSet::ColumnGap() const { LayoutBlockFlow* parent_block = MultiColumnBlockFlow(); - if (parent_block->StyleRef().ColumnGap().IsNormal()) { - // "1em" is recommended as the normal gap setting. Matches <p> margins. - return LayoutUnit( - parent_block->StyleRef().GetFontDescription().ComputedPixelSize()); - } - return ValueForLength(parent_block->StyleRef().ColumnGap().GetLength(), - AvailableLogicalWidth()); + if (const base::Optional<Length>& column_gap = + parent_block->StyleRef().ColumnGap()) + return ValueForLength(*column_gap, AvailableLogicalWidth()); + + // "1em" is recommended as the normal gap setting. Matches <p> margins. + return LayoutUnit( + parent_block->StyleRef().GetFontDescription().ComputedPixelSize()); } unsigned LayoutMultiColumnSet::ActualColumnCount() const { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_object.cc b/chromium/third_party/blink/renderer/core/layout/layout_object.cc index 688121f8ab6..3827e90b420 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_object.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_object.cc @@ -80,10 +80,6 @@ #include "third_party/blink/renderer/core/layout/layout_list_marker.h" #include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h" #include "third_party/blink/renderer/core/layout/layout_object_factory.h" -#include "third_party/blink/renderer/core/layout/layout_table_caption.h" -#include "third_party/blink/renderer/core/layout/layout_table_cell.h" -#include "third_party/blink/renderer/core/layout/layout_table_col.h" -#include "third_party/blink/renderer/core/layout/layout_table_row.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h" #include "third_party/blink/renderer/core/layout/layout_theme.h" #include "third_party/blink/renderer/core/layout/layout_view.h" @@ -245,24 +241,16 @@ LayoutObject* LayoutObject::CreateObject(Element* element, return LayoutObjectFactory::CreateBlockFlow(*element, style, legacy); case EDisplay::kTable: case EDisplay::kInlineTable: - UseCounter::Count(element->GetDocument(), - WebFeature::kLegacyLayoutByTable); - return new LayoutTable(element); + return LayoutObjectFactory::CreateTable(*element, style, legacy); case EDisplay::kTableRowGroup: case EDisplay::kTableHeaderGroup: case EDisplay::kTableFooterGroup: - UseCounter::Count(element->GetDocument(), - WebFeature::kLegacyLayoutByTable); - return new LayoutTableSection(element); + return LayoutObjectFactory::CreateTableSection(*element, style, legacy); case EDisplay::kTableRow: - UseCounter::Count(element->GetDocument(), - WebFeature::kLegacyLayoutByTable); - return new LayoutTableRow(element); + return LayoutObjectFactory::CreateTableRow(*element, style, legacy); case EDisplay::kTableColumnGroup: case EDisplay::kTableColumn: - UseCounter::Count(element->GetDocument(), - WebFeature::kLegacyLayoutByTable); - return new LayoutTableCol(element); + return LayoutObjectFactory::CreateTableColumn(*element, style, legacy); case EDisplay::kTableCell: return LayoutObjectFactory::CreateTableCell(*element, style, legacy); case EDisplay::kTableCaption: @@ -431,7 +419,7 @@ void LayoutObject::AddChild(LayoutObject* new_child, !after_child->IsBeforeContent()) { table = after_child; } else { - table = LayoutTable::CreateAnonymousWithParent(this); + table = LayoutObjectFactory::CreateAnonymousTableWithParent(*this); children->InsertChildNode(this, table, before_child); } table->AddChild(new_child); @@ -845,7 +833,6 @@ LayoutBox* LayoutObject::EnclosingBox() const { } LayoutBlockFlow* LayoutObject::RootInlineFormattingContext() const { - DCHECK(IsInline()); for (LayoutObject* parent = Parent(); parent; parent = parent->Parent()) { if (auto* block_flow = DynamicTo<LayoutBlockFlow>(parent)) { // Skip |LayoutFlowThread| because it is skipped when finding the first @@ -859,7 +846,6 @@ LayoutBlockFlow* LayoutObject::RootInlineFormattingContext() const { } LayoutBlockFlow* LayoutObject::FragmentItemsContainer() const { - DCHECK(IsInline()); for (LayoutObject* parent = Parent(); parent; parent = parent->Parent()) { if (auto* block_flow = DynamicTo<LayoutBlockFlow>(parent)) return block_flow; @@ -937,7 +923,10 @@ static inline bool ObjectIsRelayoutBoundary(const LayoutObject* object) { // Positioned objects always have self-painting layers and are safe to use as // relayout boundaries. bool is_svg_root = object->IsSVGRoot(); - if (!object->IsPositioned() && !is_svg_root) + bool has_self_painting_layer = + object->HasLayer() && + ToLayoutBoxModelObject(object)->HasSelfPaintingLayer(); + if (!has_self_painting_layer && !is_svg_root) return false; // LayoutInline can't be relayout roots since LayoutBlockFlow is responsible @@ -989,11 +978,12 @@ static inline bool ObjectIsRelayoutBoundary(const LayoutObject* object) { // In LayoutNG, if box has any OOF descendants, they are propagated to // parent. Therefore, we must mark parent chain for layout. - if (layout_box->GetCachedLayoutResult() && - layout_box->GetCachedLayoutResult() - ->PhysicalFragment() - .HasOutOfFlowPositionedDescendants()) - return false; + if (const NGLayoutResult* layouot_result = + layout_box->GetCachedLayoutResult()) { + if (layouot_result->PhysicalFragment() + .HasOutOfFlowPositionedDescendants()) + return false; + } } if (object->IsTextControl()) @@ -1290,11 +1280,6 @@ inline void LayoutObject::InvalidateContainerIntrinsicLogicalWidths() { LayoutObject* LayoutObject::ContainerForAbsolutePosition( AncestorSkipInfo* skip_info) const { return FindAncestorByPredicate(this, skip_info, [](LayoutObject* candidate) { - if (!candidate->StyleRef().CanContainAbsolutePositionObjects() && - candidate->ShouldApplyLayoutContainment()) { - UseCounter::Count(candidate->GetDocument(), - WebFeature::kCSSContainLayoutPositionedDescendants); - } return candidate->CanContainAbsolutePositionObjects(); }); } @@ -1303,12 +1288,6 @@ LayoutObject* LayoutObject::ContainerForFixedPosition( AncestorSkipInfo* skip_info) const { DCHECK(!IsText()); return FindAncestorByPredicate(this, skip_info, [](LayoutObject* candidate) { - if (!candidate->StyleRef().CanContainFixedPositionObjects( - candidate->IsDocumentElement()) && - candidate->ShouldApplyLayoutContainment()) { - UseCounter::Count(candidate->GetDocument(), - WebFeature::kCSSContainLayoutPositionedDescendants); - } return candidate->CanContainFixedPositionObjects(); }); } @@ -1358,6 +1337,13 @@ LayoutBlock* LayoutObject::ContainingBlock(AncestorSkipInfo* skip_info) const { return DynamicTo<LayoutBlock>(object); } +LayoutObject* LayoutObject::NonAnonymousAncestor() const { + LayoutObject* ancestor = Parent(); + while (ancestor && ancestor->IsAnonymous()) + ancestor = ancestor->Parent(); + return ancestor; +} + LayoutBlock* LayoutObject::FindNonAnonymousContainingBlock( LayoutObject* container, AncestorSkipInfo* skip_info) { @@ -1397,6 +1383,11 @@ bool LayoutObject::ComputeIsFixedContainer(const ComputedStyle* style) const { if (IsA<LayoutView>(this) || IsSVGForeignObject() || IsTextControl()) return true; // https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants + + if (RuntimeEnabledFeatures::TransformInteropEnabled() && + style->TransformStyle3D() == ETransformStyle3D::kPreserve3d) + return true; + if (style->HasTransformRelatedProperty()) { if (!IsInline() || IsAtomicInlineLevel()) return true; @@ -2051,10 +2042,18 @@ StyleDifference LayoutObject::AdjustStyleDifference( (IsText() && !IsBR() && ToLayoutText(this)->HasInlineFragments()) || (IsSVG() && StyleRef().SvgStyle().IsFillColorCurrentColor()) || (IsSVG() && StyleRef().SvgStyle().IsStrokeColorCurrentColor()) || - IsListMarker() || IsDetailsMarker() || IsMathML()) + IsListMarkerForNormalContent() || IsDetailsMarker() || IsMathML()) diff.SetNeedsPaintInvalidation(); } + // TODO(1088373): Pixel_WebGLHighToLowPower fails without this. This isn't the + // right way to ensure GPU switching. Investigate and do it in the right way. + if (RuntimeEnabledFeatures::CSSReducedFontLoadingInvalidationsEnabled() && + !diff.NeedsPaintInvalidation() && IsLayoutView() && Style() && + !Style()->GetFont().IsFallbackValid()) { + diff.SetNeedsPaintInvalidation(); + } + // The answer to layerTypeRequired() for plugins, iframes, and canvas can // change without the actual style changing, since it depends on whether we // decide to composite these elements. When the/ layer status of one of these @@ -2383,8 +2382,9 @@ void LayoutObject::StyleWillChange(StyleDifference diff, bool visibility_changed = style_->Visibility() != new_style.Visibility(); // If our z-index changes value or our visibility changes, // we need to dirty our stacking context's z-order list. - if (visibility_changed || style_->ZIndex() != new_style.ZIndex() || - style_->IsStackingContext() != new_style.IsStackingContext()) { + if (visibility_changed || + style_->EffectiveZIndex() != new_style.EffectiveZIndex() || + IsStackingContext(*style_) != IsStackingContext(new_style)) { GetDocument().SetAnnotatedRegionsDirty(true); if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { if (GetNode()) @@ -2540,6 +2540,20 @@ void LayoutObject::StyleDidChange(StyleDifference diff, } } + if (HasHiddenBackface()) { + bool preserve_3d = + (Parent() && Parent()->StyleRef().UsedTransformStyle3D() == + ETransformStyle3D::kPreserve3d); + if (style_->HasTransform() || preserve_3d) { + UseCounter::Count(GetDocument(), + WebFeature::kHiddenBackfaceWithPossible3D); + } + if (preserve_3d) { + UseCounter::Count(GetDocument(), + WebFeature::kHiddenBackfaceWithPreserve3D); + } + } + // First assume the outline will be affected. It may be updated when we know // it's not affected. bool has_outline = style_->HasOutline(); @@ -2980,8 +2994,13 @@ void LayoutObject::GetTransformFromContainer( transform.PostTranslate(offset_in_container.left.ToFloat(), offset_in_container.top.ToFloat()); - if (container_object && container_object->HasLayer() && - container_object->StyleRef().HasPerspective()) { + bool has_perspective = container_object && container_object->HasLayer() && + container_object->StyleRef().HasPerspective(); + if (has_perspective && RuntimeEnabledFeatures::TransformInteropEnabled() && + container_object != NonAnonymousAncestor()) + has_perspective = false; + + if (has_perspective) { // Perspective on the container affects us, so we have to factor it in here. DCHECK(container_object->HasLayer()); FloatPoint perspective_origin = @@ -3468,9 +3487,26 @@ void LayoutObject::DestroyAndCleanupAnonymousWrappers() { if (destroy_root_parent->IsLayoutFlowThread()) break; - if (destroy_root->PreviousSibling() || destroy_root->NextSibling()) - break; // Need to keep the anonymous parent, since it won't become empty - // by the removal of this LayoutObject. + // We need to keep the anonymous parent, if it won't become empty by the + // removal of this LayoutObject. + if (destroy_root->PreviousSibling()) + break; + if (const LayoutObject* sibling = destroy_root->NextSibling()) { + if (destroy_root->GetNode()) { + // When there are inline continuations, there may be multiple layout + // objects generated from the same node, and those are special. They + // will be removed as part of destroying |this|, in + // LayoutInline::WillBeDestroyed(). So if that's all we have left, we + // need to realize now that the anonymous containing block will become + // empty. So we have to destroy it. + while (sibling && sibling->GetNode() == destroy_root->GetNode()) + sibling = sibling->NextSibling(); + } + if (sibling) + break; + DCHECK(destroy_root->IsLayoutInline()); + DCHECK(ToLayoutInline(destroy_root)->Continuation()); + } } destroy_root->Destroy(); @@ -3709,7 +3745,7 @@ bool LayoutObject::WillRenderImage() { return false; // We will not render a new image when ExecutionContext is paused - if (GetDocument().IsContextPaused()) + if (GetDocument().GetExecutionContext()->IsContextPaused()) return false; // Suspend animations when the page is not visible. @@ -3728,6 +3764,18 @@ bool LayoutObject::GetImageAnimationPolicy(ImageAnimationPolicy& policy) { return true; } +bool LayoutObject::IsInsideListMarker() const { + return (IsListMarkerForNormalContent() && + ToLayoutListMarker(this)->IsInside()) || + IsInsideListMarkerForCustomContent(); +} + +bool LayoutObject::IsOutsideListMarker() const { + return (IsListMarkerForNormalContent() && + !ToLayoutListMarker(this)->IsInside()) || + IsOutsideListMarkerForCustomContent(); +} + int LayoutObject::CaretMinOffset() const { return 0; } @@ -4093,6 +4141,20 @@ bool LayoutObject::PaintInvalidationStateIsDirty() const { } #endif +void LayoutObject::ClearPaintFlags() { + DCHECK_EQ(GetDocument().Lifecycle().GetState(), + DocumentLifecycle::kInPrePaint); + ClearPaintInvalidationFlags(); + bitfields_.SetNeedsPaintPropertyUpdate(false); + bitfields_.SetEffectiveAllowedTouchActionChanged(false); + + if (!PrePaintBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)) { + bitfields_.SetDescendantNeedsPaintPropertyUpdate(false); + bitfields_.SetDescendantEffectiveAllowedTouchActionChanged(false); + bitfields_.ResetSubtreePaintPropertyUpdateReasons(); + } +} + bool LayoutObject::IsAllowedToModifyLayoutTreeStructure(Document& document) { return document.Lifecycle().StateAllowsLayoutTreeMutations(); } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_object.h b/chromium/third_party/blink/renderer/core/layout/layout_object.h index f1d44dd67ce..5d1593e4471 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_object.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_object.h @@ -551,6 +551,28 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, ShouldApplySizeContainment(); } + inline bool IsStackingContext() const { + return IsStackingContext(StyleRef()); + } + inline bool IsStackingContext(const ComputedStyle& style) const { + // This is an inlined version of the following: + // `IsStackingContextWithoutContainment() || + // ShouldApplyLayoutContainment() || + // ShouldApplyPaintContainment()` + // The reason it is inlined is that the containment checks share + // common logic, which is extracted here to avoid repeated computation. + return style.IsStackingContextWithoutContainment() || + ((style.ContainsLayout() || style.ContainsPaint()) && + (!IsInline() || IsAtomicInlineLevel()) && !IsRubyText() && + (!IsTablePart() || IsLayoutBlockFlow())); + } + + inline bool IsStacked() const { return IsStacked(StyleRef()); } + inline bool IsStacked(const ComputedStyle& style) const { + return style.GetPosition() != EPosition::kStatic || + IsStackingContext(style); + } + void NotifyPriorityScrollAnchorStatusChanged(); private: @@ -652,7 +674,7 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, } bool IsFrame() const { return IsOfType(kLayoutObjectFrame); } bool IsFrameSet() const { return IsOfType(kLayoutObjectFrameSet); } - bool IsInsideListMarker() const { + bool IsInsideListMarkerForCustomContent() const { return IsOfType(kLayoutObjectInsideListMarker); } bool IsLayoutNGBlockFlow() const { @@ -666,9 +688,6 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, bool IsLayoutNGInsideListMarker() const { return IsOfType(kLayoutObjectNGInsideListMarker); } - bool IsLayoutNGListMarkerImage() const { - return IsOfType(kLayoutObjectNGListMarkerImage); - } bool IsLayoutNGOutsideListMarker() const { return IsOfType(kLayoutObjectNGOutsideListMarker); } @@ -681,10 +700,16 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, return IsOfType(kLayoutObjectLayoutNGTableCol); } bool IsListItem() const { return IsOfType(kLayoutObjectListItem); } + bool IsListMarkerForNormalContent() const { + return IsOfType(kLayoutObjectListMarker); + } + bool IsListMarkerImage() const { + return IsOfType(kLayoutObjectListMarkerImage); + } bool IsMathML() const { return IsOfType(kLayoutObjectMathML); } bool IsMathMLRoot() const { return IsOfType(kLayoutObjectMathMLRoot); } bool IsMedia() const { return IsOfType(kLayoutObjectMedia); } - bool IsOutsideListMarker() const { + bool IsOutsideListMarkerForCustomContent() const { return IsOfType(kLayoutObjectOutsideListMarker); } bool IsProgress() const { return IsOfType(kLayoutObjectProgress); } @@ -938,8 +963,7 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, (StyleRef().Display() == EDisplay::kBlock || StyleRef().Display() == EDisplay::kWebkitBox) && StyleRef().StyleType() == kPseudoIdNone && IsLayoutBlock() && - !IsListMarker() && !IsLayoutFlowThread() && - !IsLayoutMultiColumnSet(); + !IsLayoutFlowThread() && !IsLayoutMultiColumnSet(); } // If node has been split into continuations, it returns the first layout // object generated for the node. @@ -1155,6 +1179,8 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, bool ShouldClipOverflow() const { return bitfields_.ShouldClipOverflow(); } bool HasClipRelatedProperty() const; + // Not returning StyleRef().HasTransformRelatedProperty() because some objects + // ignore the transform-related styles (e.g. LayoutInline, LayoutSVGBlock). bool HasTransformRelatedProperty() const { return bitfields_.HasTransformRelatedProperty(); } @@ -1170,8 +1196,8 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, // Returns |true| if any property that renders using filter operations is // used (including, but not limited to, 'filter' and 'box-reflect'). - // Not calling style()->hasFilterInducingProperty because some objects force - // to ignore reflection style (e.g. LayoutInline). + // Not calling StyleRef().HasFilterInducingProperty() because some objects + // ignore reflection style (e.g. LayoutInline, LayoutSVGBlock). bool HasFilterInducingProperty() const { return StyleRef().HasNonInitialFilter() || HasReflection(); } @@ -1354,8 +1380,6 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, // Returns true if style would make this object a fixed container. // This value gets cached by bitfields_.can_contain_fixed_position_objects_. - // TODO(pdr): Should this function be unified with - // ComputedStyle::CanContainFixedPositionObjects? bool ComputeIsFixedContainer(const ComputedStyle* style) const; virtual LayoutObject* HoverAncestor() const { return Parent(); } @@ -1640,6 +1664,10 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, LayoutObject* container, AncestorSkipInfo* = nullptr); + // Returns the nearest anceestor in the layout tree that is not anonymous, + // or null if there is none. + LayoutObject* NonAnonymousAncestor() const; + const LayoutBlock* InclusiveContainingBlock() const; bool CanContainAbsolutePositionObjects() const { @@ -1963,6 +1991,8 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, !IsLayoutNGOutsideListMarker() && !IsOutsideListMarker(); } + // Not returning StyleRef().BoxReflect() because some objects ignore the + // reflection style (e.g. LayoutInline, LayoutSVGBlock). bool HasReflection() const { return bitfields_.HasReflection(); } // The current selection state for an object. For blocks, the state refers to @@ -2034,17 +2064,40 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, return IsListItem() || IsLayoutNGListItem(); } - // There are 3 types of list marker. LayoutNG creates different types for - // inside and outside; outside is derived from LayoutBlockFlow, and inside - // from LayoutInline. Legacy is derived from LayoutBox. + // There 5 different types of list markers: + // * LayoutListMarker (LayoutBox): for both outside and inside markers with + // 'content: normal', in legacy layout. + // * LayoutInsideListMarker (LayoutInline): for non-normal inside markers in + // legacy layout. + // * LayoutOutsideListMarker (LayoutBlockFlow): for non-normal outside markers + // in legacy layout. + // * LayoutNGInsideListMarker (LayoutInline): for inside markers in LayoutNG. + // * LayoutNGOutsideListMarker (LayoutNGBlockFlowMixin<LayoutBlockFlow>): + // for outside markers in LayoutNG. + + // Legacy marker with inside position, normal or not. + bool IsInsideListMarker() const; + // Legacy marker with outside position, normal or not. + bool IsOutsideListMarker() const; + // Any kind of legacy list marker. bool IsListMarker() const { - return IsOutsideListMarker() || IsInsideListMarker(); + return IsListMarkerForNormalContent() || + IsInsideListMarkerForCustomContent() || + IsOutsideListMarkerForCustomContent(); + } + // Any kind of LayoutBox list marker. + bool IsBoxListMarkerIncludingNG() const { + return IsListMarkerForNormalContent() || + IsOutsideListMarkerForCustomContent() || + IsLayoutNGOutsideListMarker(); } - bool IsListMarkerIncludingNGOutside() const { - return IsListMarker() || IsLayoutNGOutsideListMarker(); + // Any kind of LayoutNG list marker. + bool IsLayoutNGListMarker() const { + return IsLayoutNGInsideListMarker() || IsLayoutNGOutsideListMarker(); } - bool IsListMarkerIncludingNGOutsideAndInside() const { - return IsListMarkerIncludingNGOutside() || IsLayoutNGInsideListMarker(); + // Any kind of list marker. + bool IsListMarkerIncludingAll() const { + return IsListMarker() || IsLayoutNGListMarker(); } virtual bool IsCombineText() const { return false; } @@ -2103,9 +2156,8 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, TransformationMatrix&) const; bool CreatesGroup() const { - return StyleRef().HasOpacity() || HasMask() || HasClipPath() || - HasFilterInducingProperty() || HasNonInitialBackdropFilter() || - StyleRef().HasBlendMode(); + // See |HasReflection()| for why |StyleRef().BoxReflect()| is not used. + return StyleRef().HasGroupingProperty(HasReflection()); } Vector<PhysicalRect> OutlineRects(const PhysicalOffset& additional_offset, @@ -2236,8 +2288,6 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, // setNeedsRepaint before calling this function. virtual void InvalidateDisplayItemClients(PaintInvalidationReason) const; - virtual bool HasNonCompositedScrollbars() const { return false; } - // Called before setting style for existing/new anonymous child. Override to // set custom styles for the child. For new anonymous child, |child| is null. virtual void UpdateAnonymousChildStyle(const LayoutObject* child, @@ -2306,21 +2356,7 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, public: // Convenience mutator that clears paint invalidation flags and this object // and its descendants' needs-paint-property-update flags. - void ClearPaintFlags() { - DCHECK_EQ(layout_object_.GetDocument().Lifecycle().GetState(), - DocumentLifecycle::kInPrePaint); - layout_object_.ClearPaintInvalidationFlags(); - layout_object_.bitfields_.SetNeedsPaintPropertyUpdate(false); - layout_object_.bitfields_.SetEffectiveAllowedTouchActionChanged(false); - - if (!layout_object_.PrePaintBlockedByDisplayLock( - DisplayLockLifecycleTarget::kChildren)) { - layout_object_.bitfields_.SetDescendantNeedsPaintPropertyUpdate(false); - layout_object_.bitfields_ - .SetDescendantEffectiveAllowedTouchActionChanged(false); - layout_object_.bitfields_.ResetSubtreePaintPropertyUpdateReasons(); - } - } + void ClearPaintFlags() { layout_object_.ClearPaintFlags(); } void SetShouldCheckForPaintInvalidation() { // This method is only intended to be called when visiting this object // during pre-paint, and as such it should only mark itself, and not the @@ -2579,6 +2615,20 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, return element->GetDisplayLockContext(); } + void SetDocumentForAnonymous(Document* document) { + DCHECK(IsAnonymous()); + node_ = document; + } + + bool IsLayoutNGObjectForListMarkerImage() const { + DCHECK(IsListMarkerImage()); + return bitfields_.IsLayoutNGObjectForListMarkerImage(); + } + void SetIsLayoutNGObjectForListMarkerImage(bool b) { + DCHECK(IsListMarkerImage()); + bitfields_.SetIsLayoutNGObjectForListMarkerImage(b); + } + protected: enum LayoutObjectType { kLayoutObjectBr, @@ -2594,6 +2644,8 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, kLayoutObjectLayoutTableCol, kLayoutObjectLayoutNGTableCol, kLayoutObjectListItem, + kLayoutObjectListMarker, + kLayoutObjectListMarkerImage, kLayoutObjectMathML, kLayoutObjectMathMLRoot, kLayoutObjectMedia, @@ -2605,7 +2657,6 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, kLayoutObjectNGListItem, kLayoutObjectNGInsideListMarker, kLayoutObjectNGOutsideListMarker, - kLayoutObjectNGListMarkerImage, kLayoutObjectNGProgress, kLayoutObjectNGText, kLayoutObjectOutsideListMarker, @@ -2721,11 +2772,6 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, virtual void InsertedIntoTree(); virtual void WillBeRemovedFromTree(); - void SetDocumentForAnonymous(Document* document) { - DCHECK(IsAnonymous()); - node_ = document; - } - #if DCHECK_IS_ON() virtual bool PaintInvalidationStateIsDirty() const; #endif @@ -2735,6 +2781,7 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, DCHECK(!NeedsLayout() || LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)); } + virtual void ClearPaintFlags(); void SetIsBackgroundAttachmentFixedObject(bool); @@ -3153,8 +3200,9 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, // control clips or contain: paint. ADD_BOOLEAN_BITFIELD(should_clip_overflow_, ShouldClipOverflow); - // This boolean is the cached value from - // ComputedStyle::hasTransformRelatedProperty. + // The cached value from ComputedStyle::HasTransformRelatedProperty for + // objects that do not ignore transform-related styles (e.g. not + // LayoutInline, LayoutSVGBlock). ADD_BOOLEAN_BITFIELD(has_transform_related_property_, HasTransformRelatedProperty); ADD_BOOLEAN_BITFIELD(has_reflection_, HasReflection); @@ -3265,6 +3313,10 @@ class CORE_EXPORT LayoutObject : public ImageResourceObserver, // True at start of |Destroy()| before calling |WillBeDestroyed()|. ADD_BOOLEAN_BITFIELD(being_destroyed_, BeingDestroyed); + // From LayoutListMarkerImage + ADD_BOOLEAN_BITFIELD(is_layout_ng_object_for_list_marker_image, + IsLayoutNGObjectForListMarkerImage); + private: // This is the cached 'position' value of this object // (see ComputedStyle::position). diff --git a/chromium/third_party/blink/renderer/core/layout/layout_object_factory.cc b/chromium/third_party/blink/renderer/core/layout/layout_object_factory.cc index eb9bd180d32..2e79c23ae79 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_object_factory.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_object_factory.cc @@ -15,25 +15,34 @@ #include "third_party/blink/renderer/core/layout/layout_inside_list_marker.h" #include "third_party/blink/renderer/core/layout/layout_list_item.h" #include "third_party/blink/renderer/core/layout/layout_outside_list_marker.h" +#include "third_party/blink/renderer/core/layout/layout_table.h" #include "third_party/blink/renderer/core/layout/layout_table_caption.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" +#include "third_party/blink/renderer/core/layout/layout_table_col.h" +#include "third_party/blink/renderer/core/layout/layout_table_row.h" +#include "third_party/blink/renderer/core/layout/layout_table_section.h" #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/layout_view.h" #include "third_party/blink/renderer/core/layout/ng/flex/layout_ng_flexible_box.h" +#include "third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h" #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h" #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h" -#include "third_party/blink/renderer/core/layout/ng/layout_ng_grid.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_progress.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_ruby_as_block.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_item.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h" #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_caption.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h" #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell_legacy.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" @@ -141,10 +150,20 @@ LayoutObject* LayoutObjectFactory::CreateListMarker(Node& node, (IsA<HTMLLIElement>(parent) && !parent_style->IsInsideListElement()); if (is_inside) { return CreateObject<LayoutObject, LayoutNGInsideListMarker, - LayoutInsideListMarker>(node, style, legacy); + LayoutListMarker>(node, style, legacy); } return CreateObject<LayoutObject, LayoutNGOutsideListMarker, - LayoutOutsideListMarker>(node, style, legacy); + LayoutListMarker>(node, style, legacy); +} + +LayoutBlock* LayoutObjectFactory::CreateTable(Node& node, + const ComputedStyle& style, + LegacyLayout legacy) { + bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled(); + if (disable_ng_for_type) + UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByTable); + return CreateObject<LayoutBlock, LayoutNGTable, LayoutTable>( + node, style, legacy, disable_ng_for_type); } LayoutTableCaption* LayoutObjectFactory::CreateTableCaption( @@ -155,12 +174,47 @@ LayoutTableCaption* LayoutObjectFactory::CreateTableCaption( legacy); } -LayoutTableCell* LayoutObjectFactory::CreateTableCell( +LayoutBlockFlow* LayoutObjectFactory::CreateTableCell( Node& node, const ComputedStyle& style, LegacyLayout legacy) { - return CreateObject<LayoutTableCell, LayoutNGTableCellLegacy>(node, style, - legacy); + if (RuntimeEnabledFeatures::LayoutNGTableEnabled()) { + return CreateObject<LayoutBlockFlow, LayoutNGTableCell, LayoutTableCell>( + node, style, legacy); + } else { + return CreateObject<LayoutBlockFlow, LayoutNGTableCellLegacy, + LayoutTableCell>(node, style, legacy); + } +} + +LayoutBox* LayoutObjectFactory::CreateTableColumn(Node& node, + const ComputedStyle& style, + LegacyLayout legacy) { + bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled(); + if (disable_ng_for_type) + UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByTable); + return CreateObject<LayoutBox, LayoutNGTableColumn, LayoutTableCol>( + node, style, legacy, disable_ng_for_type); +} + +LayoutBox* LayoutObjectFactory::CreateTableRow(Node& node, + const ComputedStyle& style, + LegacyLayout legacy) { + bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled(); + if (disable_ng_for_type) + UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByTable); + return CreateObject<LayoutBox, LayoutNGTableRow, LayoutTableRow>( + node, style, legacy, disable_ng_for_type); +} + +LayoutBox* LayoutObjectFactory::CreateTableSection(Node& node, + const ComputedStyle& style, + LegacyLayout legacy) { + bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled(); + if (disable_ng_for_type) + UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByTable); + return CreateObject<LayoutBox, LayoutNGTableSection, LayoutTableSection>( + node, style, legacy, disable_ng_for_type); } LayoutBlock* LayoutObjectFactory::CreateFieldset(Node& node, @@ -229,4 +283,62 @@ LayoutRubyAsBlock* LayoutObjectFactory::CreateRubyAsBlock( legacy); } +LayoutBox* LayoutObjectFactory::CreateAnonymousTableWithParent( + const LayoutObject& parent) { + scoped_refptr<ComputedStyle> new_style = + ComputedStyle::CreateAnonymousStyleWithDisplay( + parent.StyleRef(), + parent.IsLayoutInline() ? EDisplay::kInlineTable : EDisplay::kTable); + LegacyLayout legacy = + parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto; + + LayoutBlock* new_table = + CreateTable(parent.GetDocument(), *new_style, legacy); + new_table->SetDocumentForAnonymous(&parent.GetDocument()); + new_table->SetStyle(std::move(new_style)); + return new_table; +} + +LayoutBox* LayoutObjectFactory::CreateAnonymousTableSectionWithParent( + const LayoutObject& parent) { + scoped_refptr<ComputedStyle> new_style = + ComputedStyle::CreateAnonymousStyleWithDisplay(parent.StyleRef(), + EDisplay::kTableRowGroup); + LegacyLayout legacy = + parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto; + + LayoutBox* new_section = + CreateTableSection(parent.GetDocument(), *new_style, legacy); + new_section->SetDocumentForAnonymous(&parent.GetDocument()); + new_section->SetStyle(std::move(new_style)); + return new_section; +} + +LayoutBox* LayoutObjectFactory::CreateAnonymousTableRowWithParent( + const LayoutObject& parent) { + scoped_refptr<ComputedStyle> new_style = + ComputedStyle::CreateAnonymousStyleWithDisplay(parent.StyleRef(), + EDisplay::kTableRow); + LegacyLayout legacy = + parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto; + LayoutBox* new_row = CreateTableRow(parent.GetDocument(), *new_style, legacy); + new_row->SetDocumentForAnonymous(&parent.GetDocument()); + new_row->SetStyle(std::move(new_style)); + return new_row; +} + +LayoutBlockFlow* LayoutObjectFactory::CreateAnonymousTableCellWithParent( + const LayoutObject& parent) { + scoped_refptr<ComputedStyle> new_style = + ComputedStyle::CreateAnonymousStyleWithDisplay(parent.StyleRef(), + EDisplay::kTableCell); + LegacyLayout legacy = + parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto; + LayoutBlockFlow* new_cell = + CreateTableCell(parent.GetDocument(), *new_style, legacy); + new_cell->SetDocumentForAnonymous(&parent.GetDocument()); + new_cell->SetStyle(std::move(new_style)); + return new_cell; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_object_factory.h b/chromium/third_party/blink/renderer/core/layout/layout_object_factory.h index 1cc47ff245c..831a1beba07 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_object_factory.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_object_factory.h @@ -15,11 +15,11 @@ class ComputedStyle; class LayoutBlock; class LayoutBlockFlow; class LayoutObject; +class LayoutBox; enum class LegacyLayout; class LayoutProgress; class LayoutRubyAsBlock; class LayoutTableCaption; -class LayoutTableCell; class LayoutText; class LayoutTextFragment; class Node; @@ -50,12 +50,21 @@ class LayoutObjectFactory { static LayoutObject* CreateListMarker(Node&, const ComputedStyle&, LegacyLayout); + static LayoutBlock* CreateTable(Node&, const ComputedStyle&, LegacyLayout); static LayoutTableCaption* CreateTableCaption(Node&, const ComputedStyle&, LegacyLayout); - static LayoutTableCell* CreateTableCell(Node&, + static LayoutBlockFlow* CreateTableCell(Node&, const ComputedStyle&, LegacyLayout); + static LayoutBox* CreateTableColumn(Node&, + const ComputedStyle&, + LegacyLayout); + + static LayoutBox* CreateTableRow(Node&, const ComputedStyle&, LegacyLayout); + static LayoutBox* CreateTableSection(Node&, + const ComputedStyle&, + LegacyLayout); static LayoutBlock* CreateFieldset(Node&, const ComputedStyle&, LegacyLayout); static LayoutBlockFlow* CreateFileUploadControl(Node& node, const ComputedStyle& style, @@ -72,6 +81,19 @@ class LayoutObjectFactory { static LayoutRubyAsBlock* CreateRubyAsBlock(Node* node, const ComputedStyle& style, LegacyLayout legacy); + + // Anonoymous creation methods + + static LayoutBox* CreateAnonymousTableWithParent(const LayoutObject& parent); + + static LayoutBox* CreateAnonymousTableSectionWithParent( + const LayoutObject& parent); + + static LayoutBox* CreateAnonymousTableRowWithParent( + const LayoutObject& parent); + + static LayoutBlockFlow* CreateAnonymousTableCellWithParent( + const LayoutObject& parent); }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_object_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_object_test.cc index 5d06f5326ac..1b1799977a0 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_object_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_object_test.cc @@ -86,13 +86,140 @@ TEST_F(LayoutObjectTest, DisplayInlineBlockCreateObject) { EXPECT_TRUE(layout_object->IsInline()); } +TEST_F(LayoutObjectTest, BackdropFilterAsGroupingProperty) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style> div { transform-style: preserve-3d; } </style> + <div id=target1 style="backdrop-filter: blur(2px)"></div> + <div id=target2 style="will-change: backdrop-filter"></div> + <div id=target3 style="position: relative"></div> + )HTML"); + EXPECT_TRUE(GetLayoutObjectByElementId("target1") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target1")->StyleRef().Preserves3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target2")->StyleRef().Preserves3D()); + + EXPECT_FALSE(GetLayoutObjectByElementId("target3") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target3")->StyleRef().Preserves3D()); +} + +TEST_F(LayoutObjectTest, BlendModeAsGroupingProperty) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style> div { transform-style: preserve-3d; } </style> + <div id=target1 style="mix-blend-mode: multiply"></div> + <div id=target2 style="position: relative"></div> + )HTML"); + EXPECT_TRUE(GetLayoutObjectByElementId("target1") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target1")->StyleRef().Preserves3D()); + + EXPECT_FALSE(GetLayoutObjectByElementId("target2") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2")->StyleRef().Preserves3D()); +} + +TEST_F(LayoutObjectTest, CSSClipAsGroupingProperty) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style> div { transform-style: preserve-3d; } </style> + <div id=target1 style="clip: rect(1px, 2px, 3px, 4px)"></div> + <div id=target2 style="position: absolute; clip: rect(1px, 2px, 3px, 4px)"> + </div> + <div id=target3 style="position: relative"></div> + )HTML"); + EXPECT_FALSE(GetLayoutObjectByElementId("target1") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target1")->StyleRef().Preserves3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target2")->StyleRef().Preserves3D()); + + EXPECT_FALSE(GetLayoutObjectByElementId("target3") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target3")->StyleRef().Preserves3D()); +} + +TEST_F(LayoutObjectTest, ClipPathAsGroupingProperty) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style> div { transform-style: preserve-3d; } </style> + <div id=target1 style="clip-path: circle(40%)"></div> + <div id=target2 style="position: relative"></div> + )HTML"); + EXPECT_TRUE(GetLayoutObjectByElementId("target1") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target1")->StyleRef().Preserves3D()); + + EXPECT_FALSE(GetLayoutObjectByElementId("target2") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2")->StyleRef().Preserves3D()); +} + +TEST_F(LayoutObjectTest, IsolationAsGroupingProperty) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style> div { transform-style: preserve-3d; } </style> + <div id=target1 style="isolation: isolate"></div> + <div id=target2 style="position: relative"></div> + )HTML"); + EXPECT_TRUE(GetLayoutObjectByElementId("target1") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target1")->StyleRef().Preserves3D()); + + EXPECT_FALSE(GetLayoutObjectByElementId("target2") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2")->StyleRef().Preserves3D()); +} + +TEST_F(LayoutObjectTest, MaskAsGroupingProperty) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style> div { transform-style: preserve-3d; } </style> + <div id=target1 style="-webkit-mask:linear-gradient(black,transparent)"> + </div> + <div id=target2 style="position: relative"></div> + )HTML"); + EXPECT_TRUE(GetLayoutObjectByElementId("target1") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_FALSE(GetLayoutObjectByElementId("target1")->StyleRef().Preserves3D()); + + EXPECT_FALSE(GetLayoutObjectByElementId("target2") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2")->StyleRef().Preserves3D()); +} + TEST_F(LayoutObjectTest, UseCountBackdropFilterAsGroupingProperty) { SetBodyInnerHTML(R"HTML( <style> div { transform-style: preserve-3d; } </style> <div id=target style="backdrop-filter: blur(2px)"></div> )HTML"); - EXPECT_FALSE( - GetLayoutObjectByElementId("target")->StyleRef().HasGroupingProperty()); + EXPECT_FALSE(GetLayoutObjectByElementId("target") + ->StyleRef() + .HasGroupingPropertyForUsedTransformStyle3D()); EXPECT_TRUE(GetDocument().IsUseCounted( WebFeature::kAdditionalGroupingPropertiesForCompat)); } @@ -169,6 +296,20 @@ TEST_F( EXPECT_EQ(PhysicalOffset(2, 10), offset); } +TEST_F(LayoutObjectTest, ContainingBlockFixedPosUnderFlattened3DWithInterop) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <div id=container style='transform-style: preserve-3d; opacity: 0.9'> + <div id=target style='position:fixed'></div> + </div> + )HTML"); + + LayoutObject* target = GetLayoutObjectByElementId("target"); + LayoutObject* container = GetLayoutObjectByElementId("container"); + EXPECT_EQ(container, target->Container()); +} + TEST_F(LayoutObjectTest, ContainingBlockFixedLayoutObjectInTransformedDiv) { SetBodyInnerHTML(R"HTML( <div style='transform:translateX(0px)'> @@ -1117,4 +1258,69 @@ TEST_F(LayoutObjectTest, NeedsLayoutOverflowRecalc) { EXPECT_FALSE(other->NeedsLayoutOverflowRecalc()); } +TEST_F(LayoutObjectTest, ContainValueIsRelayoutBoundary) { + SetBodyInnerHTML(R"HTML( + <div id='target1' style='contain:layout'></div> + <div id='target2' style='contain:layout size'></div> + <div id='target3' style='contain:paint'></div> + <div id='target4' style='contain:size'></div> + <div id='target5' style='contain:content'></div> + <div id='target6' style='contain:strict'></div> + )HTML"); + EXPECT_FALSE(GetLayoutObjectByElementId("target1")->IsRelayoutBoundary()); + EXPECT_TRUE(GetLayoutObjectByElementId("target2")->IsRelayoutBoundary()); + EXPECT_FALSE(GetLayoutObjectByElementId("target3")->IsRelayoutBoundary()); + EXPECT_FALSE(GetLayoutObjectByElementId("target4")->IsRelayoutBoundary()); + EXPECT_FALSE(GetLayoutObjectByElementId("target5")->IsRelayoutBoundary()); + EXPECT_TRUE(GetLayoutObjectByElementId("target6")->IsRelayoutBoundary()); +} + +TEST_F(LayoutObjectTest, PerspectiveIsNotParent) { + ScopedTransformInteropForTest enabled(true); + + GetDocument().SetBaseURLOverride(KURL("http://test.com")); + SetBodyInnerHTML(R"HTML( + <style>body { margin:0; }</style> + <div id='ancestor' style='perspective: 100px'> + <div> + <div id='child' style='width: 10px; height: 10px; transform: rotateY(45deg); + position: absolute'></div> + </div> + </div> + )HTML"); + + auto* ancestor = + ToLayoutBox(GetDocument().getElementById("ancestor")->GetLayoutObject()); + auto* child = + ToLayoutBox(GetDocument().getElementById("child")->GetLayoutObject()); + + TransformationMatrix transform; + child->GetTransformFromContainer(ancestor, PhysicalOffset(), transform); + TransformationMatrix::DecomposedType decomposed; + EXPECT_TRUE(transform.Decompose(decomposed)); + EXPECT_EQ(0, decomposed.perspective_z); +} + +TEST_F(LayoutObjectTest, PerspectiveWithAnonymousTable) { + ScopedTransformInteropForTest enabled(true); + + SetBodyInnerHTML(R"HTML( + <style>body { margin:0; }</style> + <div id='ancestor' style='display: table; perspective: 100px; width: 100px; height: 100px;'> + <div id='child' style='display: table-cell; width: 100px; height: 100px; transform: rotateY(45deg); + position: absolute'></div> + </table> + )HTML"); + + LayoutObject* child = GetLayoutObjectByElementId("child"); + LayoutBoxModelObject* ancestor = + ToLayoutBoxModelObject(GetLayoutObjectByElementId("ancestor")); + + TransformationMatrix transform; + child->GetTransformFromContainer(ancestor, PhysicalOffset(), transform); + TransformationMatrix::DecomposedType decomposed; + EXPECT_TRUE(transform.Decompose(decomposed)); + EXPECT_EQ(-0.01, decomposed.perspective_z); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.cc index 41956979cb9..2d175ec302e 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.cc @@ -7,7 +7,7 @@ namespace blink { LayoutOutsideListMarker::LayoutOutsideListMarker(Element* element) - : LayoutListMarker(element) {} + : LayoutBlockFlow(element) {} LayoutOutsideListMarker::~LayoutOutsideListMarker() = default; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.h b/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.h index 20ff17c14a8..4893132849e 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_outside_list_marker.h @@ -6,27 +6,34 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_OUTSIDE_LIST_MARKER_H_ #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/layout_list_marker.h" +#include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" namespace blink { // Used to layout the list item's outside marker. // The LayoutOutsideListMarker always has to be a child of a LayoutListItem. -class CORE_EXPORT LayoutOutsideListMarker final : public LayoutListMarker { +class CORE_EXPORT LayoutOutsideListMarker final : public LayoutBlockFlow { public: explicit LayoutOutsideListMarker(Element*); ~LayoutOutsideListMarker() override; const char* GetName() const override { return "LayoutOutsideListMarker"; } + const ListMarker& Marker() const { return list_marker_; } + ListMarker& Marker() { return list_marker_; } + private: bool IsOfType(LayoutObjectType type) const override { return type == kLayoutObjectOutsideListMarker || - LayoutListMarker::IsOfType(type); + LayoutBlockFlow::IsOfType(type); } + + ListMarker list_marker_; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutOutsideListMarker, IsOutsideListMarker()); +DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutOutsideListMarker, + IsOutsideListMarkerForCustomContent()); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/layout_quote.cc b/chromium/third_party/blink/renderer/core/layout/layout_quote.cc index 19e6bc05abf..7d50dce6526 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_quote.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_quote.cc @@ -238,8 +238,24 @@ const QuotesData* QuotesDataForLanguage(const AtomicString& lang) { std::string lowercase_lang = lang.LowerASCII().Utf8(); Language key = {lowercase_lang.c_str(), 0, 0, 0, 0, nullptr}; Language* match = std::lower_bound(g_languages, languages_end, key); - if (match == languages_end || strcmp(match->lang, key.lang)) - return nullptr; + + if (match == languages_end) + --match; + + if (strcmp(match->lang, key.lang)) { + // No exact match, try to find without subtags. + std::size_t hyphen_offset = lowercase_lang.find('-'); + if (hyphen_offset == std::string::npos) + return nullptr; + + std::string locale = lowercase_lang.substr(0, hyphen_offset); + while (match != g_languages && strcmp(match->lang, locale.c_str()) > 0) { + --match; + } + + if (strcmp(match->lang, locale.c_str())) + return nullptr; + } if (!match->data) { auto data = QuotesData::Create(match->open1, match->close1, match->open2, diff --git a/chromium/third_party/blink/renderer/core/layout/layout_replaced.cc b/chromium/third_party/blink/renderer/core/layout/layout_replaced.cc index e5b86b519aa..b8b149fb585 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_replaced.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_replaced.cc @@ -148,7 +148,7 @@ bool LayoutReplaced::NeedsPreferredWidthsRecalculation() const { return HasRelativeLogicalHeight() && StyleRef().LogicalWidth().IsAuto(); } -static inline bool LayoutObjectHasAspectRatio( +static inline bool LayoutObjectHasIntrinsicAspectRatio( const LayoutObject* layout_object) { DCHECK(layout_object); return layout_object->IsImage() || layout_object->IsCanvas() || @@ -695,16 +695,21 @@ void LayoutReplaced::ComputeIntrinsicSizingInfo( intrinsic_sizing_info.size = FloatSize(IntrinsicLogicalWidth().ToFloat(), IntrinsicLogicalHeight().ToFloat()); + if (const base::Optional<IntSize>& aspect_ratio = StyleRef().AspectRatio()) { + intrinsic_sizing_info.aspect_ratio.SetWidth(aspect_ratio->Width()); + intrinsic_sizing_info.aspect_ratio.SetHeight(aspect_ratio->Height()); + return; + } + // Figure out if we need to compute an intrinsic ratio. - if (!LayoutObjectHasAspectRatio(this)) + if (!LayoutObjectHasIntrinsicAspectRatio(this)) return; if (!intrinsic_sizing_info.size.IsEmpty()) intrinsic_sizing_info.aspect_ratio = intrinsic_sizing_info.size; auto* elem = DynamicTo<Element>(GetNode()); - if (RuntimeEnabledFeatures::AspectRatioFromWidthAndHeightEnabled() && elem && - IsA<HTMLImageElement>(elem) && + if (elem && IsA<HTMLImageElement>(elem) && intrinsic_sizing_info.aspect_ratio.IsEmpty() && elem->FastHasAttribute(html_names::kWidthAttr) && elem->FastHasAttribute(html_names::kHeightAttr)) { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_ruby_run.cc b/chromium/third_party/blink/renderer/core/layout/layout_ruby_run.cc index 9b86bda50b0..99a16581dcd 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_ruby_run.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_ruby_run.cc @@ -246,8 +246,10 @@ void LayoutRubyRun::UpdateLayout() { last_line_ruby_text_bottom = root_box->LogicalBottomLayoutOverflow(); } - if (StyleRef().IsFlippedLinesWritingMode() == - (StyleRef().GetRubyPosition() == RubyPosition::kAfter)) { + RubyPosition block_start_position = StyleRef().IsFlippedLinesWritingMode() + ? RubyPosition::kAfter + : RubyPosition::kBefore; + if (StyleRef().GetRubyPosition() == block_start_position) { LayoutUnit first_line_top; if (LayoutRubyBase* rb = RubyBase()) { RootInlineBox* root_box = rb->FirstRootBox(); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.cc b/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.cc index 730b2a35e1c..7c304fdfed0 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.cc @@ -42,6 +42,16 @@ bool LayoutRubyText::IsChildAllowed(LayoutObject* child, return child->IsInline(); } +void LayoutRubyText::StyleDidChange(StyleDifference diff, + const ComputedStyle* old_style) { + if (StyleRef().GetTextAlign() != + ComputedStyleInitialValues::InitialTextAlign()) { + UseCounter::Count(GetDocument(), + WebFeature::kRubyTextWithNonDefaultTextAlign); + } + LayoutBlockFlow::StyleDidChange(diff, old_style); +} + ETextAlign LayoutRubyText::TextAlignmentForLine( bool ends_with_soft_break) const { ETextAlign text_align = StyleRef().GetTextAlign(); @@ -60,9 +70,10 @@ void LayoutRubyText::AdjustInlineDirectionLineBounds( LayoutUnit& logical_width) const { ETextAlign text_align = StyleRef().GetTextAlign(); // FIXME: This check is bogus since user can set the initial value. - if (text_align != ComputedStyleInitialValues::InitialTextAlign()) + if (text_align != ComputedStyleInitialValues::InitialTextAlign()) { return LayoutBlockFlow::AdjustInlineDirectionLineBounds( expansion_opportunity_count, logical_left, logical_width); + } int max_preferred_logical_width = PreferredLogicalWidths().max_size.ToInt(); if (max_preferred_logical_width >= logical_width) diff --git a/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.h b/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.h index 3684bc00e6b..c2f43d613d7 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_ruby_text.h @@ -48,6 +48,9 @@ class LayoutRubyText : public LayoutBlockFlow { bool IsChildAllowed(LayoutObject*, const ComputedStyle&) const override; + void StyleDidChange(StyleDifference diff, + const ComputedStyle* old_style) override; + bool CreatesNewFormattingContext() const final { // Ruby text objects are pushed around after layout, to become flush with // the associated ruby base. As such, we cannot let floats leak out from diff --git a/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.cc b/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.cc index dc374a71017..161f755a135 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.cc @@ -154,6 +154,11 @@ void LayoutShiftTracker::ObjectShifted( if (source.IsSVG()) return; + if (Element* element = DynamicTo<Element>(source.GetNode())) { + if (element->IsSliderThumbElement()) + return; + } + const auto root_state = PropertyTreeStateFor(*source.View()); FloatClipRect clip_rect = @@ -563,7 +568,7 @@ void LayoutShiftTracker::SetLayoutShiftRects(const Vector<IntRect>& int_rects) { } } -void LayoutShiftTracker::Trace(Visitor* visitor) { +void LayoutShiftTracker::Trace(Visitor* visitor) const { visitor->Trace(frame_view_); } @@ -620,7 +625,7 @@ void ReattachHook::NotifyAttach(const Node& node) { fragment.SetVisualRect(visual_rect); } -void ReattachHook::Trace(Visitor* visitor) { +void ReattachHook::Trace(Visitor* visitor) const { visitor->Trace(visual_rects_); } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.h b/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.h index fcff32159d9..79ad83d2a56 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_shift_tracker.h @@ -60,14 +60,14 @@ class CORE_EXPORT LayoutShiftTracker final base::TimeTicks MostRecentInputTimestamp() { return most_recent_input_timestamp_; } - void Trace(Visitor* visitor); + void Trace(Visitor* visitor) const; // Saves and restores visual rects on layout objects when a layout tree is // rebuilt by Node::ReattachLayoutTree. class ReattachHook : public GarbageCollected<ReattachHook> { public: ReattachHook() : scope_(nullptr) {} - void Trace(Visitor*); + void Trace(Visitor*) const; class Scope { public: diff --git a/chromium/third_party/blink/renderer/core/layout/layout_state.cc b/chromium/third_party/blink/renderer/core/layout/layout_state.cc index c7258a54f38..4ed2149ee86 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_state.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_state.cc @@ -57,10 +57,10 @@ LayoutState::LayoutState(LayoutBox& layout_object, height_offset_for_table_footers_ = next_->HeightOffsetForTableFooters(); layout_object.View()->PushLayoutState(*this); - if (const AtomicString& named_page = layout_object.StyleRef().Page()) - page_name_ = named_page; + if (const AtomicString& page_name = layout_object.StyleRef().Page()) + input_page_name_ = page_name; else - page_name_ = next_->page_name_; + input_page_name_ = next_->input_page_name_; if (layout_object.IsLayoutFlowThread()) { // Entering a new pagination context. diff --git a/chromium/third_party/blink/renderer/core/layout/layout_state.h b/chromium/third_party/blink/renderer/core/layout/layout_state.h index 190e5ba2450..0e0572b125a 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_state.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_state.h @@ -91,7 +91,11 @@ class LayoutState { height_offset_for_table_footers_ = offset; } - const AtomicString& PageName() const { return page_name_; } + // The input page name is the name specified by the element itself, if any. If + // the element doesn't specify one, but an ancestor does, return that. + // Otherwise it's an empty string. This is the page name that will be used on + // all descendants if none of them override it. + const AtomicString& InputPageName() const { return input_page_name_; } const LayoutSize& PaginationOffset() const { return pagination_offset_; } bool ContainingBlockLogicalWidthChanged() const { @@ -131,7 +135,7 @@ class LayoutState { // paginated layout. LayoutUnit height_offset_for_table_footers_; - AtomicString page_name_; + AtomicString input_page_name_; LayoutObject& layout_object_; DISALLOW_COPY_AND_ASSIGN(LayoutState); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table.cc b/chromium/third_party/blink/renderer/core/layout/layout_table.cc index 2b41ff4843e..4d25549e08d 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_table.cc @@ -32,6 +32,7 @@ #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/layout/hit_test_result.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_table_caption.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" #include "third_party/blink/renderer/core/layout/layout_table_col.h" @@ -231,8 +232,8 @@ void LayoutTable::AddChild(LayoutObject* child, LayoutObject* before_child) { NeedsTableSection(before_child)) before_child = nullptr; - LayoutTableSection* section = - LayoutTableSection::CreateAnonymousWithParent(this); + LayoutBox* section = + LayoutObjectFactory::CreateAnonymousTableSectionWithParent(*this); AddChild(section, before_child); section->AddChild(child); } @@ -315,7 +316,7 @@ void LayoutTable::UpdateLogicalWidth() { // might not even get there. UpdateCachedIntrinsicLogicalWidthsIfNeeded(); - if (IsFlexItemIncludingDeprecatedAndNG() || IsGridItem()) { + if (IsGridItem()) { // TODO(jfernandez): Investigate whether the grid layout algorithm provides // all the logic needed and that we're not skipping anything essential due // to the early return here. @@ -388,6 +389,9 @@ void LayoutTable::UpdateLogicalWidth() { std::min(available_content_logical_width, max_width).Floor())); } + if (HasOverrideLogicalWidth()) + SetLogicalWidth(std::max(LogicalWidth(), OverrideLogicalWidth())); + // Ensure we aren't bigger than our max-width style. const Length& style_max_logical_width = StyleRef().LogicalMaxWidth(); if ((style_max_logical_width.IsSpecified() && @@ -565,6 +569,7 @@ LayoutUnit LayoutTable::LogicalHeightFromStyle() const { !logical_max_height_length.IsNegative() && !logical_max_height_length.IsMinContent() && !logical_max_height_length.IsMaxContent() && + !logical_max_height_length.IsMinIntrinsic() && !logical_max_height_length.IsFitContent())) { LayoutUnit computed_max_logical_height = ConvertStyleLogicalHeightToComputedHeight(logical_max_height_length); @@ -575,6 +580,7 @@ LayoutUnit LayoutTable::LogicalHeightFromStyle() const { Length logical_min_height_length = StyleRef().LogicalMinHeight(); if (logical_min_height_length.IsMinContent() || logical_min_height_length.IsMaxContent() || + logical_min_height_length.IsMinIntrinsic() || logical_min_height_length.IsFitContent()) logical_min_height_length = Length::Auto(); @@ -1657,16 +1663,9 @@ bool LayoutTable::NodeAtPoint(HitTestResult& result, return false; } -LayoutTable* LayoutTable::CreateAnonymousWithParent( - const LayoutObject* parent) { - scoped_refptr<ComputedStyle> new_style = - ComputedStyle::CreateAnonymousStyleWithDisplay( - parent->StyleRef(), - parent->IsLayoutInline() ? EDisplay::kInlineTable : EDisplay::kTable); - LayoutTable* new_table = new LayoutTable(nullptr); - new_table->SetDocumentForAnonymous(&parent->GetDocument()); - new_table->SetStyle(std::move(new_style)); - return new_table; +LayoutBox* LayoutTable::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableWithParent(*parent); } void LayoutTable::EnsureIsReadyForPaintInvalidation() { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table.h b/chromium/third_party/blink/renderer/core/layout/layout_table.h index 53978ca750e..2bc9a2f65b5 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_table.h @@ -48,11 +48,11 @@ enum TableHeightChangingValue { kTableHeightNotChanging, kTableHeightChanging }; // LayoutTable is the LayoutObject associated with // display: table or inline-table. // -// LayoutTable is the master coordinator for determining the overall table -// structure. The reason is that LayoutTableSection children have a local -// view over what their structure is but don't account for other -// LayoutTableSection. Thus LayoutTable helps keep consistency across -// LayoutTableSection. See e.g. |m_effectiveColumns| below. +// LayoutTable is the coordinator for determining the overall table structure. +// The reason is that LayoutTableSection children have a local view over what +// their structure is but don't account for other LayoutTableSection. Thus +// LayoutTable helps keep consistency across LayoutTableSection. See e.g. +// |m_effectiveColumns| below. // // LayoutTable expects only 3 types of children: // - zero or more LayoutTableCol @@ -378,11 +378,8 @@ class CORE_EXPORT LayoutTable final : public LayoutBlock, RecalcSections(); } - static LayoutTable* CreateAnonymousWithParent(const LayoutObject*); LayoutBox* CreateAnonymousBoxWithSameTypeAs( - const LayoutObject* parent) const override { - return CreateAnonymousWithParent(parent); - } + const LayoutObject* parent) const override; void AddCaption(const LayoutTableCaption*); void RemoveCaption(const LayoutTableCaption*); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_cell.cc b/chromium/third_party/blink/renderer/core/layout/layout_table_cell.cc index 7d041319196..5e51751b0b4 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_cell.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_cell.cc @@ -1154,23 +1154,16 @@ LayoutTableCell* LayoutTableCell::CreateAnonymous( Document* document, scoped_refptr<ComputedStyle> style, LegacyLayout legacy) { - LayoutTableCell* layout_object = + LayoutBlockFlow* layout_object = LayoutObjectFactory::CreateTableCell(*document, *style, legacy); layout_object->SetDocumentForAnonymous(document); layout_object->SetStyle(std::move(style)); - return layout_object; + return To<LayoutTableCell>(layout_object); } -LayoutTableCell* LayoutTableCell::CreateAnonymousWithParent( - const LayoutObject* parent) { - scoped_refptr<ComputedStyle> new_style = - ComputedStyle::CreateAnonymousStyleWithDisplay(parent->StyleRef(), - EDisplay::kTableCell); - LegacyLayout legacy = - parent->ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto; - LayoutTableCell* new_cell = LayoutTableCell::CreateAnonymous( - &parent->GetDocument(), std::move(new_style), legacy); - return new_cell; +LayoutBox* LayoutTableCell::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableCellWithParent(*parent); } bool LayoutTableCell::BackgroundIsKnownToBeOpaqueInRect( diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_cell.h b/chromium/third_party/blink/renderer/core/layout/layout_table_cell.h index b2f2dd196e5..9a5454b5177 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_cell.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_cell.h @@ -241,11 +241,9 @@ class CORE_EXPORT LayoutTableCell : public LayoutBlockFlow, static LayoutTableCell* CreateAnonymous(Document*, scoped_refptr<ComputedStyle>, LegacyLayout); - static LayoutTableCell* CreateAnonymousWithParent(const LayoutObject*); + LayoutBox* CreateAnonymousBoxWithSameTypeAs( - const LayoutObject* parent) const override { - return CreateAnonymousWithParent(parent); - } + const LayoutObject* parent) const override; // The table's style determines cell order and cell adjacency in the table. // Collapsed borders also use in table's inline and block directions. @@ -378,7 +376,9 @@ class CORE_EXPORT LayoutTableCell : public LayoutBlockFlow, protected: bool IsOfType(LayoutObjectType type) const override { - return type == kLayoutObjectTableCell || LayoutBlockFlow::IsOfType(type); + return type == kLayoutObjectTableCell || + type == kLayoutObjectTableCellLegacy || + LayoutBlockFlow::IsOfType(type); } private: @@ -551,7 +551,7 @@ inline LayoutTableCell* LayoutTableRow::LastCell() const { template <> struct DowncastTraits<LayoutTableCell> { static bool AllowFrom(const LayoutObject& object) { - return object.IsTableCell(); + return object.IsTableCellLegacy(); } }; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_cell_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_table_cell_test.cc index 0826849fc2e..8ebdfc8baf1 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_cell_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_cell_test.cc @@ -30,8 +30,12 @@ namespace blink { -class LayoutTableCellDeathTest : public RenderingTest { +class LayoutTableCellDeathTest : public RenderingTest, + public ScopedLayoutNGTableForTest { protected: + // These tests test Legacy behavior only. + LayoutTableCellDeathTest() : ScopedLayoutNGTableForTest(false) {} + void SetUp() override { RenderingTest::SetUp(); auto style = ComputedStyle::Create(); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_row.cc b/chromium/third_party/blink/renderer/core/layout/layout_table_row.cc index 8dbea5af6e8..5cb289c9343 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_row.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_row.cc @@ -29,6 +29,7 @@ #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/layout/hit_test_result.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_state.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" #include "third_party/blink/renderer/core/layout/layout_view.h" @@ -154,7 +155,8 @@ void LayoutTableRow::AddChild(LayoutObject* child, LayoutObject* before_child) { return; } - LayoutTableCell* cell = LayoutTableCell::CreateAnonymousWithParent(this); + LayoutBlockFlow* cell = + LayoutObjectFactory::CreateAnonymousTableCellWithParent(*this); AddChild(cell, before_child); cell->AddChild(child); return; @@ -284,15 +286,9 @@ LayoutTableRow* LayoutTableRow::CreateAnonymous(Document* document) { return layout_object; } -LayoutTableRow* LayoutTableRow::CreateAnonymousWithParent( - const LayoutObject* parent) { - LayoutTableRow* new_row = - LayoutTableRow::CreateAnonymous(&parent->GetDocument()); - scoped_refptr<ComputedStyle> new_style = - ComputedStyle::CreateAnonymousStyleWithDisplay(parent->StyleRef(), - EDisplay::kTableRow); - new_row->SetStyle(std::move(new_style)); - return new_row; +LayoutBox* LayoutTableRow::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableRowWithParent(*parent); } void LayoutTableRow::ComputeLayoutOverflow() { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_row.h b/chromium/third_party/blink/renderer/core/layout/layout_table_row.h index 82feffcf220..eba5cbb37c0 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_row.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_row.h @@ -84,11 +84,8 @@ class CORE_EXPORT LayoutTableRow final : public LayoutTableBoxComponent, } static LayoutTableRow* CreateAnonymous(Document*); - static LayoutTableRow* CreateAnonymousWithParent(const LayoutObject*); LayoutBox* CreateAnonymousBoxWithSameTypeAs( - const LayoutObject* parent) const override { - return CreateAnonymousWithParent(parent); - } + const LayoutObject* parent) const override; void SetRowIndex(unsigned row_index) { CHECK_LE(row_index, kMaxRowIndex); diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_section.cc b/chromium/third_party/blink/renderer/core/layout/layout_table_section.cc index c67288cb774..9ad149a9ea2 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_section.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_section.cc @@ -31,6 +31,7 @@ #include "third_party/blink/renderer/core/frame/web_feature.h" #include "third_party/blink/renderer/core/layout/hit_test_result.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" #include "third_party/blink/renderer/core/layout/layout_table_col.h" #include "third_party/blink/renderer/core/layout/layout_table_row.h" @@ -173,7 +174,8 @@ void LayoutTableSection::AddChild(LayoutObject* child, return; } - LayoutObject* row = LayoutTableRow::CreateAnonymousWithParent(this); + LayoutObject* row = + LayoutObjectFactory::CreateAnonymousTableRowWithParent(*this); AddChild(row, before_child); row->AddChild(child); return; @@ -1868,15 +1870,9 @@ bool LayoutTableSection::NodeAtPoint(HitTestResult& result, return false; } -LayoutTableSection* LayoutTableSection::CreateAnonymousWithParent( - const LayoutObject* parent) { - scoped_refptr<ComputedStyle> new_style = - ComputedStyle::CreateAnonymousStyleWithDisplay(parent->StyleRef(), - EDisplay::kTableRowGroup); - LayoutTableSection* new_section = new LayoutTableSection(nullptr); - new_section->SetDocumentForAnonymous(&parent->GetDocument()); - new_section->SetStyle(std::move(new_style)); - return new_section; +LayoutBox* LayoutTableSection::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableSectionWithParent(*parent); } void LayoutTableSection::SetLogicalPositionForCell( diff --git a/chromium/third_party/blink/renderer/core/layout/layout_table_section.h b/chromium/third_party/blink/renderer/core/layout/layout_table_section.h index 2e3495ce6e3..4731fd7b088 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_table_section.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_table_section.h @@ -232,11 +232,8 @@ class CORE_EXPORT LayoutTableSection final // information. int DistributeExtraLogicalHeightToRows(int extra_logical_height); - static LayoutTableSection* CreateAnonymousWithParent(const LayoutObject*); LayoutBox* CreateAnonymousBoxWithSameTypeAs( - const LayoutObject* parent) const override { - return CreateAnonymousWithParent(parent); - } + const LayoutObject* parent) const override; void Paint(const PaintInfo&) const override; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_text.cc b/chromium/third_party/blink/renderer/core/layout/layout_text.cc index 6e6add82e13..b418493f056 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_text.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_text.cc @@ -1757,69 +1757,71 @@ void LayoutText::SetTextWithOffset(scoped_refptr<StringImpl> text, int delta = new_len - old_len; unsigned end = len ? offset + len - 1 : offset; - RootInlineBox* first_root_box = nullptr; - RootInlineBox* last_root_box = nullptr; - bool dirtied_lines = false; - // Dirty all text boxes that include characters in between offset and - // offset+len. - for (InlineTextBox* curr : TextBoxes()) { - // FIXME: This shouldn't rely on the end of a dirty line box. See - // https://bugs.webkit.org/show_bug.cgi?id=97264 - // Text run is entirely before the affected range. - if (curr->end() < offset) - continue; + if (!IsInLayoutNGInlineFormattingContext()) { + RootInlineBox* first_root_box = nullptr; + RootInlineBox* last_root_box = nullptr; + + // Dirty all text boxes that include characters in between offset and + // offset+len. + for (InlineTextBox* curr : TextBoxes()) { + // FIXME: This shouldn't rely on the end of a dirty line box. See + // https://bugs.webkit.org/show_bug.cgi?id=97264 + // Text run is entirely before the affected range. + if (curr->end() < offset) + continue; - // Text run is entirely after the affected range. - if (curr->Start() > end) { - curr->OffsetRun(delta); - RootInlineBox* root = &curr->Root(); - if (!first_root_box) { - first_root_box = root; - // The affected area was in between two runs. Go ahead and mark the root - // box of the run after the affected area as dirty. - first_root_box->MarkDirty(); + // Text run is entirely after the affected range. + if (curr->Start() > end) { + curr->OffsetRun(delta); + RootInlineBox* root = &curr->Root(); + if (!first_root_box) { + first_root_box = root; + // The affected area was in between two runs. Go ahead and mark the + // root box of the run after the affected area as dirty. + first_root_box->MarkDirty(); + dirtied_lines = true; + } + last_root_box = root; + } else if (curr->end() >= offset && curr->end() <= end) { + // Text run overlaps with the left end of the affected range. + curr->DirtyLineBoxes(); + dirtied_lines = true; + } else if (curr->Start() <= offset && curr->end() >= end) { + // Text run subsumes the affected range. + curr->DirtyLineBoxes(); + dirtied_lines = true; + } else if (curr->Start() <= end && curr->end() >= end) { + // Text run overlaps with right end of the affected range. + curr->DirtyLineBoxes(); dirtied_lines = true; } - last_root_box = root; - } else if (curr->end() >= offset && curr->end() <= end) { - // Text run overlaps with the left end of the affected range. - curr->DirtyLineBoxes(); - dirtied_lines = true; - } else if (curr->Start() <= offset && curr->end() >= end) { - // Text run subsumes the affected range. - curr->DirtyLineBoxes(); - dirtied_lines = true; - } else if (curr->Start() <= end && curr->end() >= end) { - // Text run overlaps with right end of the affected range. - curr->DirtyLineBoxes(); - dirtied_lines = true; } - } - // Now we have to walk all of the clean lines and adjust their cached line - // break information to reflect our updated offsets. - if (last_root_box) - last_root_box = last_root_box->NextRootBox(); - if (first_root_box) { - RootInlineBox* prev = first_root_box->PrevRootBox(); - if (prev) - first_root_box = prev; - } else if (LastTextBox()) { - DCHECK(!last_root_box); - first_root_box = &LastTextBox()->Root(); - first_root_box->MarkDirty(); - dirtied_lines = true; - } - for (RootInlineBox* curr = first_root_box; curr && curr != last_root_box; - curr = curr->NextRootBox()) { - if (curr->LineBreakObj().IsEqual(this) && curr->LineBreakPos() > end) - curr->SetLineBreakPos(clampTo<int>(curr->LineBreakPos() + delta)); + // Now we have to walk all of the clean lines and adjust their cached line + // break information to reflect our updated offsets. + if (last_root_box) + last_root_box = last_root_box->NextRootBox(); + if (first_root_box) { + RootInlineBox* prev = first_root_box->PrevRootBox(); + if (prev) + first_root_box = prev; + } else if (LastTextBox()) { + DCHECK(!last_root_box); + first_root_box = &LastTextBox()->Root(); + first_root_box->MarkDirty(); + dirtied_lines = true; + } + for (RootInlineBox* curr = first_root_box; curr && curr != last_root_box; + curr = curr->NextRootBox()) { + if (curr->LineBreakObj().IsEqual(this) && curr->LineBreakPos() > end) + curr->SetLineBreakPos(clampTo<int>(curr->LineBreakPos() + delta)); + } } // If the text node is empty, dirty the line where new text will be inserted. - if (!FirstTextBox() && Parent()) { + if (!HasInlineFragments() && Parent()) { Parent()->DirtyLinesFromChangedChild(this); dirtied_lines = true; } @@ -2492,24 +2494,22 @@ void LayoutText::InvalidateDisplayItemClients( PaintInvalidationReason invalidation_reason) const { ObjectPaintInvalidator paint_invalidator(*this); - if (RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled() && - !RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - auto fragments = NGPaintFragment::InlineFragmentsFor(this); - if (fragments.IsInLayoutNGInlineFormattingContext()) { - for (NGPaintFragment* fragment : fragments) { - paint_invalidator.InvalidateDisplayItemClient(*fragment, - invalidation_reason); + if (IsInLayoutNGInlineFormattingContext()) { + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + NGInlineCursor cursor; + for (cursor.MoveTo(*this); cursor; + cursor.MoveToNextForSameLayoutObject()) { + paint_invalidator.InvalidateDisplayItemClient( + *cursor.Current().GetDisplayItemClient(), invalidation_reason); } return; } - } - - if (IsInLayoutNGInlineFormattingContext()) { +#if DCHECK_IS_ON() NGInlineCursor cursor; - for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) { - paint_invalidator.InvalidateDisplayItemClient( - *cursor.Current().GetDisplayItemClient(), invalidation_reason); - } + for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) + DCHECK_EQ(cursor.Current().GetDisplayItemClient(), this); +#endif + paint_invalidator.InvalidateDisplayItemClient(*this, invalidation_reason); return; } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_text_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_text_test.cc index ec907acd7fa..62de1f9355e 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_text_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_text_test.cc @@ -66,6 +66,30 @@ class LayoutTextTest : public RenderingTest { target ? target->GetLayoutObject() : FindFirstLayoutText(); return layout_object->LocalSelectionVisualRect(); } + + std::string GetSnapCode(const LayoutText& layout_text, + const std::string& caret_text) { + return GetSnapCode(layout_text, caret_text.find('|')); + } + + std::string GetSnapCode(const char* id, const std::string& caret_text) { + return GetSnapCode(*GetLayoutTextById(id), caret_text); + } + + std::string GetSnapCode(const std::string& caret_text) { + return GetSnapCode(*GetBasicText(), caret_text); + } + + std::string GetSnapCode(const LayoutText& layout_text, unsigned offset) { + std::string result(3, '_'); + // Note:: |IsBeforeNonCollapsedCharacter()| and |ContainsCaretOffset()| + // accept out-of-bound offset but |IsAfterNonCollapsedCharacter()| doesn't. + result[0] = layout_text.IsBeforeNonCollapsedCharacter(offset) ? 'B' : '-'; + result[1] = layout_text.ContainsCaretOffset(offset) ? 'C' : '-'; + if (offset <= layout_text.TextLength()) + result[2] = layout_text.IsAfterNonCollapsedCharacter(offset) ? 'A' : '-'; + return result; + } }; const char kTacoText[] = "Los Compadres Taco Truck"; @@ -81,6 +105,15 @@ class ParameterizedLayoutTextTest : public testing::WithParamInterface<bool>, bool LayoutNGEnabled() const { return RuntimeEnabledFeatures::LayoutNGEnabled(); } + + // TODO(yosin): Once we release EditingNG, this function is used for + // specifying legacy specific behavior. + const char* ValueWithLegacy(const char* ng_text, + const char* legacy_text, + const char* reason) { + DCHECK_NE(*reason, 0); + return LayoutNGEnabled() ? ng_text : legacy_text; + } }; INSTANTIATE_TEST_SUITE_P(All, ParameterizedLayoutTextTest, testing::Bool()); @@ -315,52 +348,269 @@ TEST_P(ParameterizedLayoutTextTest, ResolvedTextLength) { TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffset) { // This test records the behavior introduced in crrev.com/e3eb4e SetBasicBody(" foo bar "); - EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(0)); // "| foo bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // " |foo bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // " f|oo bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // " fo|o bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(4)); // " foo| bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(5)); // " foo | bar " - EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(6)); // " foo | bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // " foo |bar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(8)); // " foo b|ar " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(9)); // " foo ba|r " - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(10)); // " foo bar| " - EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(11)); // " foo bar |" - EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(12)); // out of range + // text_content = "foo bar" + // offset mapping unit: + // [0] = C DOM:0-1 TC:0-0 + // [1] = I DOM:1-5 TC:0-4 "foo " + // [2] = C DOM:5-7 TC:4-4 + // [3] = I DOM:7-10 TC:4-7 "bar" + // [4] = C DOM:10-11 TC:7-7 + EXPECT_EQ("---", GetSnapCode("| foo bar ")); + EXPECT_EQ("BC-", GetSnapCode(" |foo bar ")); + EXPECT_EQ("BCA", GetSnapCode(" f|oo bar ")); + EXPECT_EQ("BCA", GetSnapCode(" fo|o bar ")); + EXPECT_EQ("BCA", GetSnapCode(" foo| bar ")); + EXPECT_EQ("-CA", GetSnapCode(" foo | bar ")); + EXPECT_EQ("---", GetSnapCode(" foo | bar ")); + EXPECT_EQ("BC-", GetSnapCode(" foo |bar ")); + EXPECT_EQ("BCA", GetSnapCode(" foo b|ar ")); + EXPECT_EQ("BCA", GetSnapCode(" foo ba|r ")); + EXPECT_EQ("-CA", GetSnapCode(" foo bar| ")); + EXPECT_EQ("---", GetSnapCode(" foo bar |")); + EXPECT_EQ("--_", GetSnapCode(*GetBasicText(), 12)); // out of range } TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffsetInPre) { // These tests record the behavior introduced in crrev.com/e3eb4e + InsertStyleElement("#target {white-space: pre; }"); + + SetBasicBody("foo bar"); + EXPECT_EQ("BC-", GetSnapCode("|foo bar")); + EXPECT_EQ("BCA", GetSnapCode("f|oo bar")); + EXPECT_EQ("BCA", GetSnapCode("fo|o bar")); + EXPECT_EQ("BCA", GetSnapCode("foo| bar")); + EXPECT_EQ("BCA", GetSnapCode("foo | bar")); + EXPECT_EQ("BCA", GetSnapCode("foo | bar")); + EXPECT_EQ("BCA", GetSnapCode("foo |bar")); + EXPECT_EQ("BCA", GetSnapCode("foo b|ar")); + EXPECT_EQ("BCA", GetSnapCode("foo ba|r")); + EXPECT_EQ("-CA", GetSnapCode("foo bar|")); + + SetBasicBody("abc\n"); + // text_content = "abc\n" + // offset mapping unit: + // [0] I DOM:0-4 TC:0-4 "abc\n" + EXPECT_EQ("BC-", GetSnapCode("|abc\n")); + EXPECT_EQ("BCA", GetSnapCode("a|bc\n")); + EXPECT_EQ("BCA", GetSnapCode("ab|c\n")); + EXPECT_EQ("BCA", GetSnapCode("abc|\n")); + EXPECT_EQ("--A", GetSnapCode("abc\n|")); + + SetBasicBody("foo\nbar"); + EXPECT_EQ("BC-", GetSnapCode("|foo\nbar")); + EXPECT_EQ("BCA", GetSnapCode("f|oo\nbar")); + EXPECT_EQ("BCA", GetSnapCode("fo|o\nbar")); + EXPECT_EQ("BCA", GetSnapCode("foo|\nbar")); + EXPECT_EQ("BCA", GetSnapCode("foo\n|bar")); + EXPECT_EQ("BCA", GetSnapCode("foo\nb|ar")); + EXPECT_EQ("BCA", GetSnapCode("foo\nba|r")); + EXPECT_EQ("-CA", GetSnapCode("foo\nbar|")); +} - SetBodyInnerHTML("<pre id='target'>foo bar</pre>"); - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(0)); // "|foo bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // "f|oo bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // "fo|o bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // "foo| bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(4)); // "foo | bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(5)); // "foo | bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(6)); // "foo |bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // "foo b|ar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(8)); // "foo ba|r" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(9)); // "foo bar|" - - SetBodyInnerHTML("<pre id='target'>foo\n</pre>"); - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(0)); // "|foo\n" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // "f|oo\n" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // "fo|o\n" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // "foo|\n" - EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(4)); // "foo\n|" - - SetBodyInnerHTML("<pre id='target'>foo\nbar</pre>"); - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(0)); // "|foo\nbar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // "f|oo\nbar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // "fo|o\nbar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // "foo|\nbar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(4)); // "foo\n|bar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(5)); // "foo\nb|ar" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(6)); // "foo\nba|r" - EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // "foo\nbar|" +TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffsetInPreLine) { + InsertStyleElement("#target {white-space: pre-line; }"); + + SetBasicBody("ab \n cd"); + // text_content = "ab\ncd" + // offset mapping unit: + // [0] I DOM:0-2 TC:0-2 "ab" + // [1] C DOM:2-3 TC:2-2 + // [2] I DOM:3-4 TC:2-3 "\n" + // [3] C DOM:4-5 TC:3-3 + // [4] I DOM:5-7 TC:3-5 "cd" + EXPECT_EQ("BC-", GetSnapCode("|ab \n cd")); + EXPECT_EQ("BCA", GetSnapCode("a|b \n cd")); + EXPECT_EQ(ValueWithLegacy("-CA", "BCA", "before collapsed trailing space"), + GetSnapCode("ab| \n cd")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after first trailing space"), + GetSnapCode("ab |\n cd")); + EXPECT_EQ(ValueWithLegacy("--A", "B-A", "before collapsed leading space"), + GetSnapCode("ab \n| cd")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after collapsed leading space"), + GetSnapCode("ab \n |cd")); + + SetBasicBody("ab \n cd"); + // text_content = "ab\ncd" + // offset mapping unit: + // [0] I DOM:0-2 TC:0-2 "ab" + // [1] C DOM:2-4 TC:2-2 + // [2] I DOM:4-5 TC:2-3 "\n" + // [3] C DOM:5-7 TC:3-3 + // [4] I DOM:7-9 TC:3-5 "cd" + EXPECT_EQ("BC-", GetSnapCode("|ab \n cd")); + EXPECT_EQ("BCA", GetSnapCode("a|b \n cd")); + EXPECT_EQ(ValueWithLegacy("-CA", "BCA", "before collapsed trailing space"), + GetSnapCode("ab| \n cd")); + EXPECT_EQ(ValueWithLegacy("---", "-CA", "after first trailing space"), + GetSnapCode("ab | \n cd")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after collapsed trailing space"), + GetSnapCode("ab |\n cd")); + EXPECT_EQ(ValueWithLegacy("--A", "B-A", "before collapsed leading space"), + GetSnapCode("ab \n| cd")); + EXPECT_EQ(ValueWithLegacy("---", "--A", "after collapsed leading space"), + GetSnapCode("ab \n | cd")); + EXPECT_EQ("BC-", GetSnapCode("ab \n |cd")); + EXPECT_EQ("BCA", GetSnapCode("ab \n c|d")); + EXPECT_EQ("-CA", GetSnapCode("ab \n cd|")); + + SetBasicBody("a\n\nb"); + EXPECT_EQ("BC-", GetSnapCode("|a\n\nb")); + EXPECT_EQ("BCA", GetSnapCode("a|\n\nb")); + EXPECT_EQ("BCA", GetSnapCode("a\n|\nb")); + EXPECT_EQ("BCA", GetSnapCode("a\n\n|b")); + EXPECT_EQ("-CA", GetSnapCode("a\n\nb|")); + + SetBasicBody("a \n \n b"); + // text_content = "a\n\nb" + // offset mapping unit: + // [0] = I DOM:0-1 TC:0-1 "a" + // [1] = C DOM:1-2 TC:1-1 + // [2] = I DOM:2-3 TC:1-2 "\n" + // [3] = C DOM:3-4 TC:2-2 + // [4] = I DOM:4-5 TC:2-3 "\n" + // [5] = C DOM:5-6 TC:3-3 + // [6] = I DOM:6-7 TC:3-4 "b" + EXPECT_EQ("BC-", GetSnapCode("|a \n \n b")); + EXPECT_EQ(ValueWithLegacy("-CA", "BCA", "before collapsed trailing space"), + GetSnapCode("a| \n \n b")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after first trailing space"), + GetSnapCode("a |\n \n b")); + EXPECT_EQ(ValueWithLegacy("--A", "B-A", "before leading collapsed space"), + GetSnapCode("a \n| \n b")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after first trailing space"), + GetSnapCode("a \n |\n b")); + EXPECT_EQ(ValueWithLegacy("--A", "B-A", "before collapsed leading space"), + GetSnapCode("a \n \n| b")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after collapsed leading space"), + GetSnapCode("a \n \n |b")); + EXPECT_EQ("-CA", GetSnapCode("a \n \n b|")); + + SetBasicBody("a \n \n b"); + // text_content = "a\n\nb" + // offset mapping unit: + // [0] = I DOM:0-1 TC:0-1 "a" + // [1] = C DOM:1-2 TC:1-1 + // [2] = I DOM:2-3 TC:1-2 "\n" + // [3] = C DOM:3-5 TC:2-2 + // [4] = I DOM:5-6 TC:2-3 "\n" + // [5] = C DOM:6-7 TC:3-3 + // [6] = I DOM:7-8 TC:3-4 "b" + EXPECT_EQ("BC-", GetSnapCode("|a \n \n b")); + EXPECT_EQ(ValueWithLegacy("-CA", "BCA", "before collapsed trailing space"), + GetSnapCode("a| \n \n b")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after first trailing space"), + GetSnapCode("a |\n \n b")); + EXPECT_EQ(ValueWithLegacy("--A", "B-A", "before collapsed leading space"), + GetSnapCode("a \n| \n b")); + EXPECT_EQ(ValueWithLegacy("---", "--A", + "after first trailing and in leading space"), + GetSnapCode("a \n | \n b")); + EXPECT_EQ("BC-", GetSnapCode("a \n |\n b")); + EXPECT_EQ(ValueWithLegacy("--A", "B-A", "before collapsed leading space"), + GetSnapCode("a \n \n| b")); + EXPECT_EQ(ValueWithLegacy("BC-", "BCA", "after collapsed leading space"), + GetSnapCode("a \n \n |b")); + EXPECT_EQ("-CA", GetSnapCode("a \n \n b|")); +} + +TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffsetWithTrailingSpace) { + SetBodyInnerHTML("<div id=target>ab<br>cd</div>"); + const LayoutText& text_ab = *GetLayoutTextById("target"); + const LayoutText& layout_br = *ToLayoutText(text_ab.NextSibling()); + const LayoutText& text_cd = *ToLayoutText(layout_br.NextSibling()); + + EXPECT_EQ("BC-", GetSnapCode(text_ab, "|ab<br>")); + EXPECT_EQ("BCA", GetSnapCode(text_ab, "a|b<br>")); + EXPECT_EQ("-CA", GetSnapCode(text_ab, "ab|<br>")); + EXPECT_EQ("BC-", GetSnapCode(layout_br, 0)); + EXPECT_EQ("--A", GetSnapCode(layout_br, 1)); + EXPECT_EQ("BC-", GetSnapCode(text_cd, "|cd")); + EXPECT_EQ("BCA", GetSnapCode(text_cd, "c|d")); + EXPECT_EQ("-CA", GetSnapCode(text_cd, "cd|")); +} + +TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffsetWithTrailingSpace1) { + SetBodyInnerHTML("<div id=target>ab <br> cd</div>"); + const LayoutText& text_ab = *GetLayoutTextById("target"); + const LayoutText& layout_br = *ToLayoutText(text_ab.NextSibling()); + const LayoutText& text_cd = *ToLayoutText(layout_br.NextSibling()); + + // text_content = "ab\ncd" + // offset mapping unit: + // [0] I DOM:0-2 TC:0-2 "ab" + // [1] C DOM:2-3 TC:2-2 + // [2] I DOM:0-1 TC:2-3 "\n" <br> + // [3] C DOM:0-1 TC:3-3 + // [4] I DOM:1-3 TC:3-5 "cd" + EXPECT_EQ("BC-", GetSnapCode(text_ab, "|ab <br>")); + EXPECT_EQ("BCA", GetSnapCode(text_ab, "a|b <br>")); + EXPECT_EQ(ValueWithLegacy("-CA", "BCA", "before after first trailing space"), + GetSnapCode(text_ab, "ab| <br>")); + EXPECT_EQ(ValueWithLegacy("---", "-CA", "after first trailing space"), + GetSnapCode(text_ab, "ab |<br>")); + EXPECT_EQ("BC-", GetSnapCode(layout_br, 0)); + EXPECT_EQ("--A", GetSnapCode(layout_br, 1)); + EXPECT_EQ("---", GetSnapCode(text_cd, "| cd")); + EXPECT_EQ("BC-", GetSnapCode(text_cd, " |cd")); + EXPECT_EQ("BCA", GetSnapCode(text_cd, " c|d")); + EXPECT_EQ("-CA", GetSnapCode(text_cd, " cd|")); +} + +TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffsetWithTrailingSpace2) { + SetBodyInnerHTML("<div id=target>ab <br> cd</div>"); + const LayoutText& text_ab = *GetLayoutTextById("target"); + const LayoutText& layout_br = *ToLayoutText(text_ab.NextSibling()); + const LayoutText& text_cd = *ToLayoutText(layout_br.NextSibling()); + + // text_content = "ab\ncd" + // offset mapping unit: + // [0] I DOM:0-2 TC:0-2 "ab" + // [1] C DOM:2-4 TC:2-2 + // [2] I DOM:0-1 TC:2-3 "\n" <br> + // [3] C DOM:0-2 TC:3-3 + // [4] I DOM:2-4 TC:3-5 "cd" + EXPECT_EQ("BC-", GetSnapCode(text_ab, "|ab <br>")); + EXPECT_EQ("BCA", GetSnapCode(text_ab, "a|b <br>")); + EXPECT_EQ(ValueWithLegacy("-CA", "BCA", "after first trailing space"), + GetSnapCode(text_ab, "ab| <br>")); + EXPECT_EQ(ValueWithLegacy("---", "-CA", "after first trailing space"), + GetSnapCode(text_ab, "ab | <br>")); + EXPECT_EQ("---", GetSnapCode(text_ab, "ab |<br>")); + EXPECT_EQ(ValueWithLegacy("BC-", "---", "before <br>"), + GetSnapCode(layout_br, 0)); + EXPECT_EQ(ValueWithLegacy("--A", "---", "after <br>"), + GetSnapCode(layout_br, 1)); + EXPECT_EQ("---", GetSnapCode(text_cd, "| cd")); + EXPECT_EQ("---", GetSnapCode(text_cd, " | cd")); + EXPECT_EQ("BC-", GetSnapCode(text_cd, " |cd")); + EXPECT_EQ("BCA", GetSnapCode(text_cd, " c|d")); + EXPECT_EQ("-CA", GetSnapCode(text_cd, " cd|")); +} + +TEST_P(ParameterizedLayoutTextTest, ContainsCaretOffsetWithTrailingSpace3) { + SetBodyInnerHTML("<div id=target>a<br> <br>b<br></div>"); + const LayoutText& text_a = *GetLayoutTextById("target"); + const LayoutText& layout_br1 = *ToLayoutText(text_a.NextSibling()); + const LayoutText& text_space = *ToLayoutText(layout_br1.NextSibling()); + EXPECT_EQ(1u, text_space.TextLength()); + const LayoutText& layout_br2 = *ToLayoutText(text_space.NextSibling()); + const LayoutText& text_b = *ToLayoutText(layout_br2.NextSibling()); + // Note: the last <br> doesn't have layout object. + + // text_content = "a\n \nb" + // offset mapping unit: + // [0] I DOM:0-1 TC:0-1 "a" + EXPECT_EQ("BC-", GetSnapCode(text_a, "|a<br>")); + EXPECT_EQ("-CA", GetSnapCode(text_a, "a|<br>")); + EXPECT_EQ("-CA", GetSnapCode(text_a, "a|<br>")); + EXPECT_EQ("BC-", GetSnapCode(layout_br1, 0)); + EXPECT_EQ("--A", GetSnapCode(layout_br1, 1)); + EXPECT_EQ("BC-", GetSnapCode(text_space, 0)); + EXPECT_EQ("--A", GetSnapCode(text_space, 1)); + EXPECT_EQ("BC-", GetSnapCode(layout_br2, 0)); + EXPECT_EQ("-CA", GetSnapCode(layout_br2, 1)); + EXPECT_EQ("BC-", GetSnapCode(text_b, "|b<br>")); + EXPECT_EQ("--A", GetSnapCode(text_b, "b|<br>")); } TEST_P(ParameterizedLayoutTextTest, GetTextBoxInfoWithCollapsedWhiteSpace) { @@ -529,110 +779,152 @@ TEST_P(ParameterizedLayoutTextTest, IsBeforeAfterNonCollapsedCharacterNoLineWrap) { // Basic tests SetBasicBody("foo"); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter(0)); // "|foo" - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter(3)); // "foo|" - - // Return false at node end/start, respectively - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(3)); // "foo|" - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(0)); // "|foo" + EXPECT_EQ("BC-", GetSnapCode("|foo")); + EXPECT_EQ("BCA", GetSnapCode("f|oo")); + EXPECT_EQ("BCA", GetSnapCode("fo|o")); + EXPECT_EQ("-CA", GetSnapCode("foo|")); // Consecutive spaces are collapsed into one SetBasicBody("f bar"); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter(1)); // "f| bar" - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(2)); // "f | bar" - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(3)); // "f | bar" - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter(2)); // "f | bar" - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(3)); // "f | bar" - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(4)); // "f |bar" + EXPECT_EQ("BC-", GetSnapCode("|f bar")); + EXPECT_EQ("BCA", GetSnapCode("f| bar")); + EXPECT_EQ("-CA", GetSnapCode("f | bar")); + EXPECT_EQ("---", GetSnapCode("f | bar")); + EXPECT_EQ("BC-", GetSnapCode("f |bar")); + EXPECT_EQ("BCA", GetSnapCode("f b|ar")); + EXPECT_EQ("BCA", GetSnapCode("f ba|r")); + EXPECT_EQ("-CA", GetSnapCode("f bar|")); // Leading spaces in a block are collapsed SetBasicBody(" foo"); - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(0)); // "| foo" - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(1)); // " | foo" - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(1)); // " | foo" - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(2)); // " |foo" + EXPECT_EQ("---", GetSnapCode("| foo")); + EXPECT_EQ("---", GetSnapCode(" | foo")); + EXPECT_EQ("BC-", GetSnapCode(" |foo")); + EXPECT_EQ("BCA", GetSnapCode(" f|oo")); + EXPECT_EQ("BCA", GetSnapCode(" fo|o")); + EXPECT_EQ("-CA", GetSnapCode(" foo|")); // Trailing spaces in a block are collapsed SetBasicBody("foo "); - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(3)); // "foo| " - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(4)); // "foo | " - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(4)); // "foo | " - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(5)); // "foo |" + EXPECT_EQ("BC-", GetSnapCode("|foo ")); + EXPECT_EQ("BCA", GetSnapCode("f|oo ")); + EXPECT_EQ("BCA", GetSnapCode("fo|o ")); + EXPECT_EQ("-CA", GetSnapCode("foo| ")); + EXPECT_EQ("---", GetSnapCode("foo | ")); + EXPECT_EQ("---", GetSnapCode("foo |")); // Non-collapsed space at node end SetBasicBody("foo <span>bar</span>"); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter( - 3)); // "foo| <span>bar</span>" - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter( - 4)); // "foo |<span>bar</span>" + EXPECT_EQ("BC-", GetSnapCode("|foo ")); + EXPECT_EQ("BCA", GetSnapCode("f|oo ")); + EXPECT_EQ("BCA", GetSnapCode("fo|o ")); + EXPECT_EQ("BCA", GetSnapCode("foo| ")); + EXPECT_EQ("-CA", GetSnapCode("foo |")); // Non-collapsed space at node start SetBasicBody("foo<span id=bar> bar</span>"); - EXPECT_TRUE(GetLayoutTextById("bar")->IsBeforeNonCollapsedCharacter( - 0)); // "foo<span>| bar</span>" - EXPECT_TRUE(GetLayoutTextById("bar")->IsAfterNonCollapsedCharacter( - 1)); // "foo<span> |bar</span>" + EXPECT_EQ("BC-", GetSnapCode("bar", "| bar")); + EXPECT_EQ("BCA", GetSnapCode("bar", " |bar")); + EXPECT_EQ("BCA", GetSnapCode("bar", " b|ar")); + EXPECT_EQ("BCA", GetSnapCode("bar", " ba|r")); + EXPECT_EQ("-CA", GetSnapCode("bar", " bar|")); // Consecutive spaces across nodes SetBasicBody("foo <span id=bar> bar</span>"); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter( - 3)); // "foo| <span> bar</span>" - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter( - 4)); // "foo |<span> bar</span>" - EXPECT_FALSE(GetLayoutTextById("bar")->IsBeforeNonCollapsedCharacter( - 0)); // foo <span>| bar</span> - EXPECT_FALSE(GetLayoutTextById("bar")->IsAfterNonCollapsedCharacter( - 1)); // foo <span> |bar</span> + // text_content = "foo bar" + // [0] I DOM:0-4 TC:0-4 "foo " + // [1] C DOM:0-1 TC:4-4 " bar" + // [2] I DOM:1-4 TC:4-7 " bar" + EXPECT_EQ("BC-", GetSnapCode("|foo ")); + EXPECT_EQ("BCA", GetSnapCode("f|oo ")); + EXPECT_EQ("BCA", GetSnapCode("fo|o ")); + EXPECT_EQ("BCA", GetSnapCode("foo| ")); + EXPECT_EQ("-CA", GetSnapCode("foo |")); + EXPECT_EQ("---", GetSnapCode("bar", "| bar")); + EXPECT_EQ("BC-", GetSnapCode("bar", " |bar")); + EXPECT_EQ("BCA", GetSnapCode("bar", " b|ar")); + EXPECT_EQ("BCA", GetSnapCode("bar", " ba|r")); + EXPECT_EQ("-CA", GetSnapCode("bar", " bar|")); // Non-collapsed whitespace text node SetBasicBody("foo<span id=space> </span>bar"); - EXPECT_TRUE(GetLayoutTextById("space")->IsBeforeNonCollapsedCharacter(0)); - EXPECT_TRUE(GetLayoutTextById("space")->IsAfterNonCollapsedCharacter(1)); + EXPECT_EQ("BC-", GetSnapCode("space", "| ")); + EXPECT_EQ("-CA", GetSnapCode("space", " |")); // Collapsed whitespace text node SetBasicBody("foo <span id=space> </span>bar"); - EXPECT_FALSE(GetLayoutTextById("space")->IsBeforeNonCollapsedCharacter(0)); - EXPECT_FALSE(GetLayoutTextById("space")->IsAfterNonCollapsedCharacter(1)); + EXPECT_EQ("---", GetSnapCode("space", "| ")); + EXPECT_EQ("---", GetSnapCode("space", " |")); } TEST_P(ParameterizedLayoutTextTest, IsBeforeAfterNonCollapsedLineWrapSpace) { LoadAhem(); - // Line wrapping inside node - SetAhemBody("xx xx", 2); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter(2)); // "xx| xx" - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter(3)); // "xx |xx" + // Note: Because we can place a caret before soft line wrap, "ab| cd", + // |GetSnapCode()| should return "BC-" for both NG and legacy. - // Legacy layout fails in the remaining test cases - if (!LayoutNGEnabled()) - return; + // Line wrapping inside node + SetAhemBody("ab cd", 2); + // text_content = "ab cd" + // [0] I DOM:0-3 TC:0-3 "ab " + // [1] C DOM:3-4 TC:3-3 " " + // [2] I DOM:4-6 TC:3-5 "cd" + EXPECT_EQ("BC-", GetSnapCode("|ab cd")); + EXPECT_EQ("BCA", GetSnapCode("a|b cd")); + EXPECT_EQ("BCA", GetSnapCode("ab| cd")); + EXPECT_EQ(ValueWithLegacy("-CA", "--A", "after soft line wrap"), + GetSnapCode("ab | cd")); + EXPECT_EQ("BC-", GetSnapCode("ab |cd")); + EXPECT_EQ("BCA", GetSnapCode("ab c|d")); + EXPECT_EQ("-CA", GetSnapCode("ab cd|")); // Line wrapping at node start - SetAhemBody("xx<span id=span> xx</span>", 2); - EXPECT_TRUE(GetLayoutTextById("span")->IsBeforeNonCollapsedCharacter( - 0)); // "xx<span>| xx</span>" - EXPECT_TRUE(GetLayoutTextById("span")->IsAfterNonCollapsedCharacter( - 1)); // "xx<span>| xx</span>" + // text_content = "xx" + // [0] I DOM:0-2 TC:0-2 "xx" + // [1] I DOM:0-1 TC:2-3 " " + // [2] C DOM:1-2 TC:3-3 " " + // [3] I DOM:2-3 TC:3-5 "xx" + SetAhemBody("ab<span id=span> cd</span>", 2); + EXPECT_EQ(ValueWithLegacy("BC-", "---", "before soft line wrap"), + GetSnapCode("span", "| cd")); + EXPECT_EQ(ValueWithLegacy("-CA", "---", "after soft line wrap"), + GetSnapCode("span", " | cd")); + EXPECT_EQ("BC-", GetSnapCode("span", " |cd")); + EXPECT_EQ("BCA", GetSnapCode("span", " c|d")); + EXPECT_EQ("-CA", GetSnapCode("span", " cd|")); // Line wrapping at node end - SetAhemBody("xx <span>xx</span>", 2); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter( - 2)); // "xx| <span>xx</span>" - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter( - 3)); // "xx |<span>xx</span>" + SetAhemBody("ab <span>cd</span>", 2); + // text_content = "ab cd" + // [0] I DOM:0-3 TC:0-3 "ab " + // [1] C DOM:3-4 TC:3-3 " " + // [2] I DOM:0-2 TC:3-5 "cd" + EXPECT_EQ("BC-", GetSnapCode("|ab ")); + EXPECT_EQ("BCA", GetSnapCode("a|b ")); + EXPECT_EQ(ValueWithLegacy("BCA", "-CA", "before soft line wrap"), + GetSnapCode("ab| ")); + EXPECT_EQ(ValueWithLegacy("-CA", "---", "after soft line wrap"), + GetSnapCode("ab | ")); + EXPECT_EQ("---", GetSnapCode("ab |")); // Entire node as line wrapping - SetAhemBody("xx<span id=space> </span>xx", 2); - EXPECT_TRUE(GetLayoutTextById("space")->IsBeforeNonCollapsedCharacter(0)); - EXPECT_TRUE(GetLayoutTextById("space")->IsAfterNonCollapsedCharacter(1)); + SetAhemBody("ab<span id=space> </span>cd", 2); + // text_content = "ab cd" + // [0] I DOM:0-2 TC:0-2 "ab" + // [1] I DOM:0-1 TC:2-3 " " + // [2] C DOM:1-2 TC:3-3 " " + // [3] I DOM:0-2 TC:3-5 "cd" + EXPECT_EQ(ValueWithLegacy("BC-", "---", "before soft line wrap"), + GetSnapCode("space", "| ")); + EXPECT_EQ(ValueWithLegacy("-CA", "---", "after soft line wrap"), + GetSnapCode("space", " | ")); + EXPECT_EQ("---", GetSnapCode("space", " |")); } TEST_P(ParameterizedLayoutTextTest, IsBeforeAfterNonCollapsedCharacterBR) { SetBasicBody("<br>"); - EXPECT_TRUE(GetBasicText()->IsBeforeNonCollapsedCharacter(0)); - EXPECT_FALSE(GetBasicText()->IsBeforeNonCollapsedCharacter(1)); - EXPECT_FALSE(GetBasicText()->IsAfterNonCollapsedCharacter(0)); - EXPECT_TRUE(GetBasicText()->IsAfterNonCollapsedCharacter(1)); + EXPECT_EQ("BC-", GetSnapCode(*GetBasicText(), 0)); + EXPECT_EQ("--A", GetSnapCode(*GetBasicText(), 1)); } TEST_P(ParameterizedLayoutTextTest, AbsoluteQuads) { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_theme_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_theme_test.cc index 7461a3457ca..f9a19671467 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_theme_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_theme_test.cc @@ -88,7 +88,7 @@ TEST_F(LayoutThemeTest, SystemColorWithColorScheme) { <style> #dark { color: buttonface; - color-scheme: dark; + color-scheme: light dark; } </style> <div id="dark"></div> diff --git a/chromium/third_party/blink/renderer/core/layout/layout_theme_win.cc b/chromium/third_party/blink/renderer/core/layout/layout_theme_win.cc index ff108f6f396..e447ce6d5df 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_theme_win.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_theme_win.cc @@ -22,10 +22,6 @@ LayoutTheme& LayoutTheme::NativeTheme() { Color LayoutThemeWin::SystemColor(CSSValueID css_value_id, WebColorScheme color_scheme) const { - if (!RuntimeEnabledFeatures::UseWindowsSystemColorsEnabled()) { - return LayoutThemeDefault::SystemColor(css_value_id, color_scheme); - } - blink::WebThemeEngine::SystemThemeColor theme_color; switch (css_value_id) { case CSSValueID::kActivetext: diff --git a/chromium/third_party/blink/renderer/core/layout/layout_tree_as_text.cc b/chromium/third_party/blink/renderer/core/layout/layout_tree_as_text.cc index 28cc1cbd7c3..4b2b012761b 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_tree_as_text.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_tree_as_text.cc @@ -46,10 +46,11 @@ #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/line/inline_text_box.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline.h" @@ -282,7 +283,7 @@ void LayoutTreeAsText::WriteLayoutObject(WTF::TextStream& ts, } } - if (o.IsListMarker()) { + if (o.IsListMarkerForNormalContent()) { String text = ToLayoutListMarker(o).GetText(); if (!text.IsEmpty()) { if (text.length() != 1) { diff --git a/chromium/third_party/blink/renderer/core/layout/layout_video.cc b/chromium/third_party/blink/renderer/core/layout/layout_video.cc index 5a71ef5b8ce..53e0582f6a1 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_video.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_video.cc @@ -43,7 +43,7 @@ LayoutSize LayoutVideo::DefaultSize() { } void LayoutVideo::IntrinsicSizeChanged() { - if (VideoElement()->ShouldDisplayPosterImage()) + if (VideoElement()->IsShowPosterFlagSet()) LayoutMedia::IntrinsicSizeChanged(); UpdateIntrinsicSize(/* is_in_layout */ false); } @@ -96,7 +96,7 @@ LayoutSize LayoutVideo::CalculateIntrinsicSize() { return LayoutSize(size); } - if (video->ShouldDisplayPosterImage() && !cached_image_size_.IsEmpty() && + if (video->IsShowPosterFlagSet() && !cached_image_size_.IsEmpty() && !ImageResource()->ErrorOccurred()) return cached_image_size_; @@ -120,8 +120,13 @@ void LayoutVideo::ImageChanged(WrappedImagePtr new_image, UpdateIntrinsicSize(/* is_in_layout */ false); } -bool LayoutVideo::ShouldDisplayVideo() const { - return !VideoElement()->ShouldDisplayPosterImage(); +LayoutVideo::DisplayMode LayoutVideo::GetDisplayMode() const { + if (!VideoElement()->IsShowPosterFlagSet() || + VideoElement()->PosterImageURL().IsEmpty()) { + return kVideo; + } else { + return kPoster; + } } void LayoutVideo::PaintReplaced(const PaintInfo& paint_info, @@ -142,7 +147,6 @@ void LayoutVideo::UpdateFromElement() { LayoutMedia::UpdateFromElement(); UpdatePlayer(/* is_in_layout */ false); - // If the DisplayMode of the video changed, then we need to paint. SetShouldDoFullPaintInvalidation(); } @@ -174,7 +178,7 @@ LayoutUnit LayoutVideo::MinimumReplacedHeight() const { } PhysicalRect LayoutVideo::ReplacedContentRect() const { - if (ShouldDisplayVideo()) { + if (GetDisplayMode() == kVideo) { // Video codecs may need to restart from an I-frame when the output is // resized. Round size in advance to avoid 1px snap difference. return PreSnappedRectForPersistentSizing(ComputeObjectFit()); @@ -193,7 +197,7 @@ CompositingReasons LayoutVideo::AdditionalCompositingReasons() const { if (element->IsFullscreen() && element->UsesOverlayFullscreenVideo()) return CompositingReason::kVideo; - if (ShouldDisplayVideo() && SupportsAcceleratedRendering()) + if (GetDisplayMode() == kVideo && SupportsAcceleratedRendering()) return CompositingReason::kVideo; return CompositingReason::kNone; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_video.h b/chromium/third_party/blink/renderer/core/layout/layout_video.h index ed6ce6aa006..9d1b0bba2b3 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_video.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_video.h @@ -35,7 +35,7 @@ class HTMLVideoElement; class LayoutVideo final : public LayoutMedia { public: - LayoutVideo(HTMLVideoElement*); + explicit LayoutVideo(HTMLVideoElement*); ~LayoutVideo() override; static LayoutSize DefaultSize(); @@ -44,7 +44,9 @@ class LayoutVideo final : public LayoutMedia { bool SupportsAcceleratedRendering() const; - bool ShouldDisplayVideo() const; + enum DisplayMode { kPoster, kVideo }; + DisplayMode GetDisplayMode() const; + HTMLVideoElement* VideoElement() const; const char* GetName() const override { return "LayoutVideo"; } diff --git a/chromium/third_party/blink/renderer/core/layout/layout_view.cc b/chromium/third_party/blink/renderer/core/layout/layout_view.cc index 67af94038fa..b002e563a62 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_view.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_view.cc @@ -24,6 +24,7 @@ #include <inttypes.h> #include "build/build_config.h" +#include "third_party/blink/public/common/features.h" #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/web_screen_info.h" @@ -345,6 +346,15 @@ void LayoutView::UpdateLayout() { LayoutBlockFlow::UpdateLayout(); + if (named_pages_mapper_) { + // If a start page name got propagated all the way up to the root, that will + // be the name for the first page. Usually we insert names into the mapper + // as part of inserting forced breaks, but in this case there'll be no + // break, since we're at the first page. + if (const AtomicString first_page_name = StartPageName()) + named_pages_mapper_->NameFirstPage(first_page_name); + } + #if DCHECK_IS_ON() CheckLayoutState(); #endif @@ -831,11 +841,9 @@ IntervalArena* LayoutView::GetIntervalArena() { } bool LayoutView::BackgroundIsKnownToBeOpaqueInRect(const PhysicalRect&) const { - // FIXME: Remove this main frame check. Same concept applies to subframes too. - if (!GetFrame()->IsMainFrame()) - return false; - - return frame_view_->HasOpaqueBackground(); + // The base background color applies to the main frame only. + return GetFrame()->IsMainFrame() && + !frame_view_->BaseBackgroundColor().HasAlpha(); } FloatSize LayoutView::ViewportSizeForViewportUnits() const { @@ -894,6 +902,18 @@ bool LayoutView::UpdateLogicalWidthAndColumnWidth() { return relayout_children || ShouldUsePrintingLayout(); } +CompositingReasons LayoutView::AdditionalCompositingReasons() const { + // TODO(lfg): Audit for portals + const LocalFrame& frame = frame_view_->GetFrame(); + if (frame.OwnerLayoutObject() && + base::FeatureList::IsEnabled( + blink::features::kCompositeCrossOriginIframes) && + frame.IsCrossOriginToParentFrame()) { + return CompositingReason::kIFrame; + } + return CompositingReason::kNone; +} + void LayoutView::UpdateCounters() { if (!needs_counter_update_) return; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_view.h b/chromium/third_party/blink/renderer/core/layout/layout_view.h index 7a36102b35d..584e4fe9be3 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_view.h +++ b/chromium/third_party/blink/renderer/core/layout/layout_view.h @@ -181,6 +181,9 @@ class CORE_EXPORT LayoutView final : public LayoutBlockFlow { LayoutState* GetLayoutState() const { return layout_state_; } + bool CanHaveAdditionalCompositingReasons() const override { return true; } + CompositingReasons AdditionalCompositingReasons() const override; + void UpdateHitTestResult(HitTestResult&, const PhysicalOffset&) const override; diff --git a/chromium/third_party/blink/renderer/core/layout/layout_view_test.cc b/chromium/third_party/blink/renderer/core/layout/layout_view_test.cc index e41c96484d1..ce2abaea5ea 100644 --- a/chromium/third_party/blink/renderer/core/layout/layout_view_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/layout_view_test.cc @@ -135,10 +135,6 @@ class LayoutViewHitTestTest : public testing::WithParamInterface<HitTestConfig>, protected: bool LayoutNG() { return RuntimeEnabledFeatures::LayoutNGEnabled(); } bool IsAndroidOrWindowsEditingBehavior() { - // TODO(crbug.com/971414): For now LayoutNG always uses Android/Windows - // behavior for ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(). - if (LayoutNG()) - return true; return GetParam().editing_behavior == kEditingAndroidBehavior || GetParam().editing_behavior == kEditingWindowsBehavior; } diff --git a/chromium/third_party/blink/renderer/core/layout/line/inline_box.cc b/chromium/third_party/blink/renderer/core/layout/line/inline_box.cc index fbb94e4db67..0ebeb3d14e8 100644 --- a/chromium/third_party/blink/renderer/core/layout/line/inline_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/line/inline_box.cc @@ -102,8 +102,9 @@ IntRect InlineBox::PartialInvalidationVisualRect() const { } DOMNodeId InlineBox::OwnerNodeId() const { - return GetLineLayoutItem().GetNode() - ? DOMNodeIds::IdForNode(GetLineLayoutItem().GetNode()) + return GetLineLayoutItem().GetNodeForOwnerNodeId() + ? DOMNodeIds::IdForNode( + GetLineLayoutItem().GetNodeForOwnerNodeId()) : kInvalidDOMNodeId; } diff --git a/chromium/third_party/blink/renderer/core/layout/line/inline_flow_box.cc b/chromium/third_party/blink/renderer/core/layout/line/inline_flow_box.cc index 909f16a6fed..d71e63106c9 100644 --- a/chromium/third_party/blink/renderer/core/layout/line/inline_flow_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/line/inline_flow_box.cc @@ -827,9 +827,12 @@ void InlineFlowBox::PlaceBoxesInBlockDirection( // being part of the overall lineTop/lineBottom. // Really this is a workaround hack for the fact that ruby should have // been done as line layout and not done using inline-block. - if (GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode() == - (curr->GetLineLayoutItem().StyleRef().GetRubyPosition() == - RubyPosition::kAfter)) + RubyPosition block_start_position = + GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode() + ? RubyPosition::kAfter + : RubyPosition::kBefore; + if (curr->GetLineLayoutItem().StyleRef().GetRubyPosition() == + block_start_position) has_annotations_before = true; else has_annotations_after = true; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.cc b/chromium/third_party/blink/renderer/core/layout/list_marker.cc index b2533d42d7a..3aa26b4e03f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/list_marker.cc @@ -2,36 +2,87 @@ // 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/list_marker.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" #include "third_party/blink/renderer/core/layout/layout_image_resource_style_image.h" +#include "third_party/blink/renderer/core/layout/layout_inside_list_marker.h" +#include "third_party/blink/renderer/core/layout/layout_list_item.h" +#include "third_party/blink/renderer/core/layout/layout_list_marker_image.h" +#include "third_party/blink/renderer/core/layout/layout_outside_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_image.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" namespace blink { +const int kCMarkerPaddingPx = 7; + +// TODO(glebl): Move to core/html/resources/html.css after +// Blink starts to support ::marker crbug.com/457718 +// Recommended UA margin for list markers. +const int kCUAMarkerMarginEm = 1; + ListMarker::ListMarker() : marker_text_type_(kNotText) {} -const ListMarker* ListMarker::Get(const LayoutObject* object) { - if (!object) - return nullptr; - if (object->IsLayoutNGOutsideListMarker()) - return &ToLayoutNGOutsideListMarker(object)->Marker(); - if (object->IsLayoutNGInsideListMarker()) - return &ToLayoutNGInsideListMarker(object)->Marker(); +const ListMarker* ListMarker::Get(const LayoutObject* marker) { + if (auto* outside_marker = ToLayoutOutsideListMarkerOrNull(marker)) + return &outside_marker->Marker(); + if (auto* inside_marker = ToLayoutInsideListMarkerOrNull(marker)) + return &inside_marker->Marker(); + if (auto* ng_outside_marker = ToLayoutNGOutsideListMarkerOrNull(marker)) + return &ng_outside_marker->Marker(); + if (auto* ng_inside_marker = ToLayoutNGInsideListMarkerOrNull(marker)) + return &ng_inside_marker->Marker(); return nullptr; } -ListMarker* ListMarker::Get(LayoutObject* object) { +ListMarker* ListMarker::Get(LayoutObject* marker) { return const_cast<ListMarker*>( - ListMarker::Get(static_cast<const LayoutObject*>(object))); + ListMarker::Get(static_cast<const LayoutObject*>(marker))); +} + +LayoutObject* ListMarker::MarkerFromListItem(const LayoutObject* list_item) { + if (auto* legacy_list_item = ToLayoutListItemOrNull(list_item)) + return legacy_list_item->Marker(); + if (auto* ng_list_item = ToLayoutNGListItemOrNull(list_item)) + return ng_list_item->Marker(); + return nullptr; +} + +LayoutObject* ListMarker::ListItem(const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); + LayoutObject* list_item = marker.GetNode()->parentNode()->GetLayoutObject(); + DCHECK(list_item); + DCHECK(list_item->IsListItemIncludingNG()); + return list_item; +} + +LayoutBlockFlow* ListMarker::ListItemBlockFlow( + const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); + LayoutObject* list_item = ListItem(marker); + if (auto* legacy_list_item = ToLayoutListItemOrNull(list_item)) + return legacy_list_item; + if (auto* ng_list_item = ToLayoutNGListItemOrNull(list_item)) + return ng_list_item; + NOTREACHED(); + return nullptr; +} + +int ListMarker::ListItemValue(const LayoutObject& list_item) const { + if (auto* legacy_list_item = ToLayoutListItemOrNull(list_item)) + return legacy_list_item->Value(); + if (auto* ng_list_item = ToLayoutNGListItemOrNull(list_item)) + return ng_list_item->Value(); + NOTREACHED(); + return 0; } // If the value of ListStyleType changed, we need to the marker text has been // updated. void ListMarker::ListStyleTypeChanged(LayoutObject& marker) { + DCHECK_EQ(Get(&marker), this); if (marker_text_type_ == kNotText || marker_text_type_ == kUnresolved) return; @@ -41,6 +92,7 @@ void ListMarker::ListStyleTypeChanged(LayoutObject& marker) { } void ListMarker::OrdinalValueChanged(LayoutObject& marker) { + DCHECK_EQ(Get(&marker), this); if (marker_text_type_ == kOrdinalValue) { marker_text_type_ = kUnresolved; marker.SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( @@ -49,6 +101,7 @@ void ListMarker::OrdinalValueChanged(LayoutObject& marker) { } void ListMarker::UpdateMarkerText(LayoutObject& marker, LayoutText* text) { + DCHECK_EQ(Get(&marker), this); DCHECK(text); DCHECK_EQ(marker_text_type_, kUnresolved); StringBuilder marker_text_builder; @@ -59,93 +112,39 @@ void ListMarker::UpdateMarkerText(LayoutObject& marker, LayoutText* text) { } void ListMarker::UpdateMarkerText(LayoutObject& marker) { + DCHECK_EQ(Get(&marker), this); UpdateMarkerText(marker, ToLayoutText(marker.SlowFirstChild())); } -LayoutNGListItem* ListMarker::ListItem(const LayoutObject& marker) { - return ToLayoutNGListItem(marker.GetNode()->parentNode()->GetLayoutObject()); -} - ListMarker::MarkerTextType ListMarker::MarkerText( const LayoutObject& marker, StringBuilder* text, MarkerTextFormat format) const { + DCHECK_EQ(Get(&marker), this); + if (!marker.StyleRef().ContentBehavesAsNormal()) + return kNotText; if (IsMarkerImage(marker)) { if (format == kWithSuffix) text->Append(' '); return kNotText; } - LayoutNGListItem* list_item = ListItem(marker); + LayoutObject* list_item = ListItem(marker); const ComputedStyle& style = list_item->StyleRef(); - switch (style.ListStyleType()) { - case EListStyleType::kNone: + switch (GetListStyleCategory(style.ListStyleType())) { + case ListStyleCategory::kNone: return kNotText; - case EListStyleType::kString: { + case ListStyleCategory::kStaticString: text->Append(style.ListStyleStringValue()); return kStatic; - } - case EListStyleType::kDisc: - case EListStyleType::kCircle: - case EListStyleType::kSquare: + case ListStyleCategory::kSymbol: // value is ignored for these types text->Append(list_marker_text::GetText(style.ListStyleType(), 0)); if (format == kWithSuffix) text->Append(' '); return kSymbolValue; - case EListStyleType::kArabicIndic: - case EListStyleType::kArmenian: - case EListStyleType::kBengali: - case EListStyleType::kCambodian: - case EListStyleType::kCjkIdeographic: - case EListStyleType::kCjkEarthlyBranch: - case EListStyleType::kCjkHeavenlyStem: - case EListStyleType::kDecimalLeadingZero: - case EListStyleType::kDecimal: - case EListStyleType::kDevanagari: - case EListStyleType::kEthiopicHalehame: - case EListStyleType::kEthiopicHalehameAm: - case EListStyleType::kEthiopicHalehameTiEr: - case EListStyleType::kEthiopicHalehameTiEt: - case EListStyleType::kGeorgian: - case EListStyleType::kGujarati: - case EListStyleType::kGurmukhi: - case EListStyleType::kHangul: - case EListStyleType::kHangulConsonant: - case EListStyleType::kHebrew: - case EListStyleType::kHiragana: - case EListStyleType::kHiraganaIroha: - case EListStyleType::kKannada: - case EListStyleType::kKatakana: - case EListStyleType::kKatakanaIroha: - case EListStyleType::kKhmer: - case EListStyleType::kKoreanHangulFormal: - case EListStyleType::kKoreanHanjaFormal: - case EListStyleType::kKoreanHanjaInformal: - case EListStyleType::kLao: - case EListStyleType::kLowerAlpha: - case EListStyleType::kLowerArmenian: - case EListStyleType::kLowerGreek: - case EListStyleType::kLowerLatin: - case EListStyleType::kLowerRoman: - case EListStyleType::kMalayalam: - case EListStyleType::kMongolian: - case EListStyleType::kMyanmar: - case EListStyleType::kOriya: - case EListStyleType::kPersian: - case EListStyleType::kSimpChineseFormal: - case EListStyleType::kSimpChineseInformal: - case EListStyleType::kTelugu: - case EListStyleType::kThai: - case EListStyleType::kTibetan: - case EListStyleType::kTradChineseFormal: - case EListStyleType::kTradChineseInformal: - case EListStyleType::kUpperAlpha: - case EListStyleType::kUpperArmenian: - case EListStyleType::kUpperLatin: - case EListStyleType::kUpperRoman: - case EListStyleType::kUrdu: { - int value = list_item->Value(); + case ListStyleCategory::kLanguage: { + int value = ListItemValue(*list_item); text->Append(list_marker_text::GetText(style.ListStyleType(), value)); if (format == kWithSuffix) { text->Append(list_marker_text::Suffix(style.ListStyleType(), value)); @@ -159,26 +158,28 @@ ListMarker::MarkerTextType ListMarker::MarkerText( } String ListMarker::MarkerTextWithSuffix(const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); StringBuilder text; MarkerText(marker, &text, kWithSuffix); return text.ToString(); } String ListMarker::MarkerTextWithoutSuffix(const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); StringBuilder text; MarkerText(marker, &text, kWithoutSuffix); return text.ToString(); } String ListMarker::TextAlternative(const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); // For accessibility, return the marker string in the logical order even in // RTL, reflecting speech order. return MarkerTextWithSuffix(marker); } void ListMarker::UpdateMarkerContentIfNeeded(LayoutObject& marker) { - LayoutNGListItem* list_item = ListItem(marker); - + DCHECK_EQ(Get(&marker), this); if (!marker.StyleRef().ContentBehavesAsNormal()) { marker_text_type_ = kNotText; return; @@ -188,8 +189,9 @@ void ListMarker::UpdateMarkerContentIfNeeded(LayoutObject& marker) { LayoutObject* child = marker.SlowFirstChild(); DCHECK(!child || !child->NextSibling()); + const ComputedStyle& style = ListItem(marker)->StyleRef(); if (IsMarkerImage(marker)) { - StyleImage* list_style_image = list_item->StyleRef().ListStyleImage(); + StyleImage* list_style_image = style.ListStyleImage(); if (child) { // If the url of `list-style-image` changed, create a new LayoutImage. if (!child->IsLayoutImage() || @@ -200,8 +202,10 @@ void ListMarker::UpdateMarkerContentIfNeeded(LayoutObject& marker) { } } if (!child) { - LayoutNGListMarkerImage* image = - LayoutNGListMarkerImage::CreateAnonymous(&marker.GetDocument()); + LayoutListMarkerImage* image = + LayoutListMarkerImage::CreateAnonymous(&marker.GetDocument()); + if (marker.IsLayoutNGListMarker()) + image->SetIsLayoutNGObjectForListMarkerImage(true); scoped_refptr<ComputedStyle> image_style = ComputedStyle::CreateAnonymousStyleWithDisplay(marker.StyleRef(), EDisplay::kInline); @@ -216,7 +220,7 @@ void ListMarker::UpdateMarkerContentIfNeeded(LayoutObject& marker) { return; } - if (list_item->StyleRef().ListStyleType() == EListStyleType::kNone) { + if (style.ListStyleType() == EListStyleType::kNone) { marker_text_type_ = kNotText; return; } @@ -248,9 +252,168 @@ void ListMarker::UpdateMarkerContentIfNeeded(LayoutObject& marker) { LayoutObject* ListMarker::SymbolMarkerLayoutText( const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); if (marker_text_type_ != kSymbolValue) return nullptr; return marker.SlowFirstChild(); } +bool ListMarker::IsMarkerImage(const LayoutObject& marker) const { + DCHECK_EQ(Get(&marker), this); + return marker.StyleRef().ContentBehavesAsNormal() && + ListItem(marker)->StyleRef().GeneratesMarkerImage(); +} + +LayoutUnit ListMarker::WidthOfSymbol(const ComputedStyle& style) { + const Font& font = style.GetFont(); + const SimpleFontData* font_data = font.PrimaryFont(); + DCHECK(font_data); + if (!font_data) + return LayoutUnit(); + return LayoutUnit((font_data->GetFontMetrics().Ascent() * 2 / 3 + 1) / 2 + 2); +} + +std::pair<LayoutUnit, LayoutUnit> ListMarker::InlineMarginsForInside( + const ComputedStyle& style, + bool is_image) { + if (!style.ContentBehavesAsNormal()) + return {}; + if (is_image) + return {LayoutUnit(), LayoutUnit(kCMarkerPaddingPx)}; + switch (GetListStyleCategory(style.ListStyleType())) { + case ListStyleCategory::kSymbol: + return {LayoutUnit(-1), + LayoutUnit(kCUAMarkerMarginEm * style.ComputedFontSize())}; + default: + break; + } + return {}; +} + +std::pair<LayoutUnit, LayoutUnit> ListMarker::InlineMarginsForOutside( + const ComputedStyle& style, + bool is_image, + LayoutUnit marker_inline_size) { + LayoutUnit margin_start; + LayoutUnit margin_end; + if (!style.ContentBehavesAsNormal()) { + margin_start = -marker_inline_size; + } else if (is_image) { + margin_start = -marker_inline_size - kCMarkerPaddingPx; + margin_end = LayoutUnit(kCMarkerPaddingPx); + } else { + switch (GetListStyleCategory(style.ListStyleType())) { + case ListStyleCategory::kNone: + break; + case ListStyleCategory::kSymbol: { + const SimpleFontData* font_data = style.GetFont().PrimaryFont(); + DCHECK(font_data); + if (!font_data) + return {}; + const FontMetrics& font_metrics = font_data->GetFontMetrics(); + int offset = font_metrics.Ascent() * 2 / 3; + margin_start = LayoutUnit(-offset - kCMarkerPaddingPx - 1); + margin_end = offset + kCMarkerPaddingPx + 1 - marker_inline_size; + break; + } + default: + margin_start = -marker_inline_size; + } + } + DCHECK_EQ(margin_start + margin_end, -marker_inline_size); + return {margin_start, margin_end}; +} + +LayoutRect ListMarker::RelativeSymbolMarkerRect(const ComputedStyle& style, + LayoutUnit width) { + LayoutRect relative_rect; + const SimpleFontData* font_data = style.GetFont().PrimaryFont(); + DCHECK(font_data); + if (!font_data) + return LayoutRect(); + + // TODO(wkorman): Review and clean up/document the calculations below. + // http://crbug.com/543193 + const FontMetrics& font_metrics = font_data->GetFontMetrics(); + int ascent = font_metrics.Ascent(); + int bullet_width = (ascent * 2 / 3 + 1) / 2; + relative_rect = LayoutRect(1, 3 * (ascent - ascent * 2 / 3) / 2, bullet_width, + bullet_width); + if (!style.IsHorizontalWritingMode()) { + relative_rect = relative_rect.TransposedRect(); + relative_rect.SetX(width - relative_rect.X() - relative_rect.Width()); + } + return relative_rect; +} + +ListMarker::ListStyleCategory ListMarker::GetListStyleCategory( + EListStyleType type) { + switch (type) { + case EListStyleType::kNone: + return ListStyleCategory::kNone; + case EListStyleType::kString: + return ListStyleCategory::kStaticString; + case EListStyleType::kDisc: + case EListStyleType::kCircle: + case EListStyleType::kSquare: + return ListStyleCategory::kSymbol; + case EListStyleType::kArabicIndic: + case EListStyleType::kArmenian: + case EListStyleType::kBengali: + case EListStyleType::kCambodian: + case EListStyleType::kCjkIdeographic: + case EListStyleType::kCjkEarthlyBranch: + case EListStyleType::kCjkHeavenlyStem: + case EListStyleType::kDecimalLeadingZero: + case EListStyleType::kDecimal: + case EListStyleType::kDevanagari: + case EListStyleType::kEthiopicHalehame: + case EListStyleType::kEthiopicHalehameAm: + case EListStyleType::kEthiopicHalehameTiEr: + case EListStyleType::kEthiopicHalehameTiEt: + case EListStyleType::kGeorgian: + case EListStyleType::kGujarati: + case EListStyleType::kGurmukhi: + case EListStyleType::kHangul: + case EListStyleType::kHangulConsonant: + case EListStyleType::kHebrew: + case EListStyleType::kHiragana: + case EListStyleType::kHiraganaIroha: + case EListStyleType::kKannada: + case EListStyleType::kKatakana: + case EListStyleType::kKatakanaIroha: + case EListStyleType::kKhmer: + case EListStyleType::kKoreanHangulFormal: + case EListStyleType::kKoreanHanjaFormal: + case EListStyleType::kKoreanHanjaInformal: + case EListStyleType::kLao: + case EListStyleType::kLowerAlpha: + case EListStyleType::kLowerArmenian: + case EListStyleType::kLowerGreek: + case EListStyleType::kLowerLatin: + case EListStyleType::kLowerRoman: + case EListStyleType::kMalayalam: + case EListStyleType::kMongolian: + case EListStyleType::kMyanmar: + case EListStyleType::kOriya: + case EListStyleType::kPersian: + case EListStyleType::kSimpChineseFormal: + case EListStyleType::kSimpChineseInformal: + case EListStyleType::kTelugu: + case EListStyleType::kThai: + case EListStyleType::kTibetan: + case EListStyleType::kTradChineseFormal: + case EListStyleType::kTradChineseInformal: + case EListStyleType::kUpperAlpha: + case EListStyleType::kUpperArmenian: + case EListStyleType::kUpperLatin: + case EListStyleType::kUpperRoman: + case EListStyleType::kUrdu: + return ListStyleCategory::kLanguage; + default: + NOTREACHED(); + return ListStyleCategory::kLanguage; + } +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.h b/chromium/third_party/blink/renderer/core/layout/list_marker.h index 0ecf1844689..c90e7af1c1b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/list_marker.h @@ -2,17 +2,22 @@ // 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_LIST_MARKER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LIST_MARKER_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LIST_MARKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LIST_MARKER_H_ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/layout_object.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" namespace blink { -// This class holds code shared among LayoutNG classes for list markers. +class LayoutListItem; +class LayoutNGListItem; +class LayoutText; + +// This class holds code shared among all classes for list markers, for both +// legacy layout and LayoutNG. class CORE_EXPORT ListMarker { + friend class LayoutListItem; friend class LayoutNGListItem; public: @@ -21,7 +26,10 @@ class CORE_EXPORT ListMarker { static const ListMarker* Get(const LayoutObject*); static ListMarker* Get(LayoutObject*); - static LayoutNGListItem* ListItem(const LayoutObject&); + static LayoutObject* MarkerFromListItem(const LayoutObject*); + + LayoutObject* ListItem(const LayoutObject&) const; + LayoutBlockFlow* ListItemBlockFlow(const LayoutObject&) const; String MarkerTextWithSuffix(const LayoutObject&) const; String MarkerTextWithoutSuffix(const LayoutObject&) const; @@ -29,20 +37,37 @@ class CORE_EXPORT ListMarker { // Marker text with suffix, e.g. "1. ", for use in accessibility. String TextAlternative(const LayoutObject&) const; - static bool IsMarkerImage(const LayoutObject& marker) { - return ListItem(marker)->StyleRef().GeneratesMarkerImage(); - } + bool IsMarkerImage(const LayoutObject&) const; void UpdateMarkerTextIfNeeded(LayoutObject& marker) { + DCHECK_EQ(Get(&marker), this); if (marker_text_type_ == kUnresolved) UpdateMarkerText(marker); } void UpdateMarkerContentIfNeeded(LayoutObject&); - void OrdinalValueChanged(LayoutObject&); - LayoutObject* SymbolMarkerLayoutText(const LayoutObject&) const; + // Compute inline margins for 'list-style-position: inside' and 'outside'. + static std::pair<LayoutUnit, LayoutUnit> InlineMarginsForInside( + const ComputedStyle&, + bool is_image); + static std::pair<LayoutUnit, LayoutUnit> InlineMarginsForOutside( + const ComputedStyle&, + bool is_image, + LayoutUnit marker_inline_size); + + static LayoutRect RelativeSymbolMarkerRect(const ComputedStyle&, LayoutUnit); + static LayoutUnit WidthOfSymbol(const ComputedStyle&); + + // A reduced set of list style categories allowing for more concise expression + // of list style specific logic. + enum class ListStyleCategory { kNone, kSymbol, kLanguage, kStaticString }; + + // Returns the list's style as one of a reduced high level categorical set of + // styles. + static ListStyleCategory GetListStyleCategory(EListStyleType); + private: enum MarkerTextFormat { kWithSuffix, kWithoutSuffix }; enum MarkerTextType { @@ -62,10 +87,13 @@ class CORE_EXPORT ListMarker { void UpdateMarkerText(LayoutObject&, LayoutText*); void ListStyleTypeChanged(LayoutObject&); + void OrdinalValueChanged(LayoutObject&); + + int ListItemValue(const LayoutObject&) const; unsigned marker_text_type_ : 3; // MarkerTextType }; } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LIST_MARKER_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LIST_MARKER_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/README.md b/chromium/third_party/blink/renderer/core/layout/ng/README.md index 09c24da1f24..961a60cb9d3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/README.md +++ b/chromium/third_party/blink/renderer/core/layout/ng/README.md @@ -113,7 +113,7 @@ Here is the instruction how to generate a new result. `chromium\src>for %file in (*.log) do DynamoRIO\tools\bin64\drcov2lcov.exe -input %file -output %file.info -src_filter layout/ng -src_skip_filter _test` * Merge all lcov files into one file `chromium\src>node lcov-result-merger\bin\lcov-result-merger.js *.info output.info` -* Generate the coverage html from the master lcov file +* Generate the coverage html from the lcov file `chromium\src>C:\Perl64\bin\perl.exe dynamorio.git\third_party\lcov\genhtml output.info -o output` ### Debugging, logging and testing ### diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc index 3a2d2beb0b1..43f6f426a4e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc @@ -353,12 +353,12 @@ CSSLayoutDefinition::Instance* CSSLayoutDefinition::CreateInstance() { return MakeGarbageCollected<Instance>(this, instance.V8Value()); } -void CSSLayoutDefinition::Instance::Trace(Visitor* visitor) { +void CSSLayoutDefinition::Instance::Trace(Visitor* visitor) const { visitor->Trace(definition_); visitor->Trace(instance_); } -void CSSLayoutDefinition::Trace(Visitor* visitor) { +void CSSLayoutDefinition::Trace(Visitor* visitor) const { visitor->Trace(constructor_); visitor->Trace(intrinsic_sizes_); visitor->Trace(layout_); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h index d2bef60636a..5596cc1d38f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h @@ -78,7 +78,7 @@ class CSSLayoutDefinition final : public GarbageCollected<CSSLayoutDefinition>, IntrinsicSizesResultOptions**, bool* child_depends_on_percentage_block_size); - void Trace(Visitor*); + void Trace(Visitor*) const; private: void ReportException(ExceptionState*); @@ -106,7 +106,7 @@ class CSSLayoutDefinition final : public GarbageCollected<CSSLayoutDefinition>, ScriptState* GetScriptState() const { return script_state_; } - virtual void Trace(Visitor* visitor); + virtual void Trace(Visitor* visitor) const; const char* NameInHeapSnapshot() const override { return "CSSLayoutDefinition"; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc index 7dd9c992f83..55bf4282702 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc @@ -21,7 +21,7 @@ const NGLayoutInputNode& CustomIntrinsicSizes::GetLayoutNode() const { return child_->GetLayoutNode(); } -void CustomIntrinsicSizes::Trace(Visitor* visitor) { +void CustomIntrinsicSizes::Trace(Visitor* visitor) const { visitor->Trace(child_); visitor->Trace(token_); ScriptWrappable::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h index 1bfa9cea738..7c81a6e61cb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h @@ -37,7 +37,7 @@ class CustomIntrinsicSizes : public ScriptWrappable { bool IsValid() const { return token_->IsValid(); } - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: Member<CustomLayoutChild> child_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc index a4ed4148eca..d7e43154fd7 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc @@ -86,7 +86,7 @@ ScriptPromise CustomLayoutChild::layoutNextFragment( return resolver->Promise(); } -void CustomLayoutChild::Trace(Visitor* visitor) { +void CustomLayoutChild::Trace(Visitor* visitor) const { visitor->Trace(style_map_); visitor->Trace(token_); ScriptWrappable::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h index 7943adc0ac3..2b1a0a51a42 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h @@ -46,7 +46,7 @@ class CustomLayoutChild : public ScriptWrappable { void SetCustomLayoutToken(CustomLayoutToken* token) { token_ = token; } - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: NGLayoutInputNode node_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc index 9ed31898a5c..28b8883540f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc @@ -47,7 +47,7 @@ ScriptValue CustomLayoutConstraints::data(ScriptState* script_state) const { layout_worklet_world_v8_data_.NewLocal(script_state->GetIsolate())); } -void CustomLayoutConstraints::Trace(Visitor* visitor) { +void CustomLayoutConstraints::Trace(Visitor* visitor) const { visitor->Trace(layout_worklet_world_v8_data_); ScriptWrappable::Trace(visitor); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h index 6a496623aab..c3dfb58244b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h @@ -32,7 +32,7 @@ class CustomLayoutConstraints : public ScriptWrappable { base::Optional<double> fixedBlockSize() const; ScriptValue data(ScriptState*) const; - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: double fixed_inline_size_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc index 1d724aa4f68..60cecbc6440 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc @@ -53,7 +53,7 @@ ScriptValue CustomLayoutFragment::data(ScriptState* script_state) const { layout_worklet_world_v8_data_.NewLocal(script_state->GetIsolate())); } -void CustomLayoutFragment::Trace(Visitor* visitor) { +void CustomLayoutFragment::Trace(Visitor* visitor) const { visitor->Trace(child_); visitor->Trace(token_); visitor->Trace(layout_worklet_world_v8_data_); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h index c9b5dde905d..13fc70ef9e2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h @@ -61,7 +61,7 @@ class CustomLayoutFragment : public ScriptWrappable { bool IsValid() const { return token_->IsValid(); } - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: Member<CustomLayoutChild> child_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h index 3cc70061f06..8071f4c7a53 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h @@ -22,7 +22,7 @@ typedef Vector<CustomLayoutWorkTask, 4> CustomLayoutWorkQueue; class CustomLayoutToken : public GarbageCollected<CustomLayoutToken> { public: CustomLayoutToken() : is_detached_(false) {} - void Trace(Visitor* visitor) {} + void Trace(Visitor* visitor) const {} bool IsValid() const; private: diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc index b24800c68ef..14ce056a557 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc @@ -150,7 +150,8 @@ void CustomLayoutWorkTask::RunIntrinsicSizesTask( DCHECK_EQ(type_, CustomLayoutWorkTask::TaskType::kIntrinsicSizes); DCHECK(resolver_); - MinMaxSizesInput input(child_percentage_resolution_block_size_for_min_max); + MinMaxSizesInput input(child_percentage_resolution_block_size_for_min_max, + MinMaxSizesType::kContent); MinMaxSizesResult result = ComputeMinAndMaxContentContribution(parent_style, child, input); resolver_->Resolve(MakeGarbageCollected<CustomIntrinsicSizes>( diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc index c68765b9107..40266002ee6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc @@ -33,7 +33,7 @@ bool DocumentLayoutDefinition::IsEqual(const CSSLayoutDefinition& other) { other.ChildCustomInvalidationProperties(); } -void DocumentLayoutDefinition::Trace(Visitor* visitor) { +void DocumentLayoutDefinition::Trace(Visitor* visitor) const { visitor->Trace(layout_definition_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h index 0c2d8bce246..2a2c1052afb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h @@ -37,7 +37,7 @@ class DocumentLayoutDefinition final return registered_definitions_count_; } - virtual void Trace(Visitor*); + virtual void Trace(Visitor*) const; private: bool IsEqual(const CSSLayoutDefinition&); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc index be7fd1d4847..7340fed04c8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc @@ -45,7 +45,7 @@ LayoutWorkletGlobalScopeProxy* LayoutWorklet::Proxy() { return LayoutWorkletGlobalScopeProxy::From(FindAvailableGlobalScope()); } -void LayoutWorklet::Trace(Visitor* visitor) { +void LayoutWorklet::Trace(Visitor* visitor) const { visitor->Trace(document_definition_map_); visitor->Trace(pending_layout_registry_); Worklet::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h index 7f852c563e2..20475e9697b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h @@ -47,7 +47,7 @@ class CORE_EXPORT LayoutWorklet : public Worklet, void AddPendingLayout(const AtomicString& name, Node*); LayoutWorkletGlobalScopeProxy* Proxy(); - void Trace(Visitor*) override; + void Trace(Visitor*) const override; protected: // TODO(ikilpatrick): Make selection of the global scope non-deterministic. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc index 51bb52ea69d..317d8921237 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc @@ -167,7 +167,7 @@ CSSLayoutDefinition* LayoutWorkletGlobalScope::FindDefinition( return layout_definitions_.at(name); } -void LayoutWorkletGlobalScope::Trace(Visitor* visitor) { +void LayoutWorkletGlobalScope::Trace(Visitor* visitor) const { visitor->Trace(layout_definitions_); visitor->Trace(pending_layout_registry_); WorkletGlobalScope::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h index c02359b83b2..9bb6d62a34c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h @@ -44,7 +44,7 @@ class CORE_EXPORT LayoutWorkletGlobalScope final : public WorkletGlobalScope { CSSLayoutDefinition* FindDefinition(const AtomicString& name); - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: // https://drafts.css-houdini.org/css-layout-api/#layout-definitions diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc index 4a161756a61..76f95c12604 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc @@ -92,7 +92,7 @@ CSSLayoutDefinition* LayoutWorkletGlobalScopeProxy::FindDefinition( return global_scope_->FindDefinition(name); } -void LayoutWorkletGlobalScopeProxy::Trace(Visitor* visitor) { +void LayoutWorkletGlobalScopeProxy::Trace(Visitor* visitor) const { visitor->Trace(global_scope_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h index de993758af3..97efbd1f814 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h @@ -46,7 +46,7 @@ class CORE_EXPORT LayoutWorkletGlobalScopeProxy LayoutWorkletGlobalScope* global_scope() const { return global_scope_.Get(); } - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: std::unique_ptr<MainThreadWorkletReportingProxy> reporting_proxy_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc index b2dbb2d6b09..05f5ff77648 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc @@ -21,12 +21,7 @@ namespace blink { NGCustomLayoutAlgorithm::NGCustomLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - params_(params), - border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding), - border_scrollbar_padding_(border_padding_ + - params.fragment_geometry.scrollbar) { + : NGLayoutAlgorithm(params), params_(params) { DCHECK(params.space.IsNewFormattingContext()); container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); @@ -58,7 +53,7 @@ MinMaxSizesResult NGCustomLayoutAlgorithm::ComputeMinMaxSizes( IntrinsicSizesResultOptions* intrinsic_sizes_result_options = nullptr; if (!instance->IntrinsicSizes( ConstraintSpace(), document, Node(), - container_builder_.InitialBorderBoxSize(), border_scrollbar_padding_, + container_builder_.InitialBorderBoxSize(), BorderScrollbarPadding(), input.percentage_resolution_block_size, &scope, &intrinsic_sizes_result_options, &depends_on_percentage_block_size)) { // TODO(ikilpatrick): Report this error to the developer. @@ -104,7 +99,7 @@ scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::Layout() { scoped_refptr<SerializedScriptValue> fragment_result_data; if (!instance->Layout(ConstraintSpace(), document, Node(), container_builder_.InitialBorderBoxSize(), - border_scrollbar_padding_, &scope, + BorderScrollbarPadding(), &scope, fragment_result_options, &fragment_result_data)) { // TODO(ikilpatrick): Report this error to the developer. return FallbackLayout(); @@ -156,10 +151,10 @@ scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::Layout() { // Compute the final block-size. LayoutUnit auto_block_size = std::max( - border_padding_.BlockSum(), + BorderScrollbarPadding().BlockSum(), LayoutUnit::FromDoubleRound(fragment_result_options->autoBlockSize())); LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, auto_block_size, + ConstraintSpace(), Style(), BorderPadding(), auto_block_size, container_builder_.InitialBorderBoxSize().inline_size); if (fragment_result_options->hasBaseline()) { @@ -170,7 +165,7 @@ scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::Layout() { container_builder_.SetCustomLayoutData(std::move(fragment_result_data)); container_builder_.SetIntrinsicBlockSize(auto_block_size); - container_builder_.SetBlockSize(block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); NGOutOfFlowLayoutPart( Node(), ConstraintSpace(), @@ -190,8 +185,7 @@ void NGCustomLayoutAlgorithm::AddAnyOutOfFlowPositionedChildren( DCHECK(child); while (*child && child->IsOutOfFlowPositioned()) { container_builder_.AddOutOfFlowChildCandidate( - To<NGBlockNode>(*child), {border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start}); + To<NGBlockNode>(*child), BorderScrollbarPadding().StartOffset()); *child = child->NextSibling(); } } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h index 2ba330632c7..950ce9da4c2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h @@ -29,8 +29,6 @@ class CORE_EXPORT NGCustomLayoutAlgorithm scoped_refptr<const NGLayoutResult> FallbackLayout(); const NGLayoutAlgorithmParams& params_; - const NGBoxStrut border_padding_; - const NGBoxStrut border_scrollbar_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc index 644f324ac9c..8c9b1e0f2c8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc @@ -42,7 +42,7 @@ void PendingLayoutRegistry::AddPendingLayout(const AtomicString& name, set->insert(node); } -void PendingLayoutRegistry::Trace(Visitor* visitor) { +void PendingLayoutRegistry::Trace(Visitor* visitor) const { visitor->Trace(pending_layouts_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h index 8afe379befd..f0c7eb34ce5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h @@ -23,7 +23,7 @@ class PendingLayoutRegistry : public GarbageCollected<PendingLayoutRegistry> { void NotifyLayoutReady(const AtomicString& name); void AddPendingLayout(const AtomicString& name, Node*); - void Trace(Visitor*); + void Trace(Visitor*) const; private: // This is a map of Nodes which are waiting for a CSSLayoutDefinition to be diff --git a/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc index 6de92e5be92..8a38aba09cc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc @@ -20,6 +20,7 @@ #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" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" +#include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink { @@ -27,10 +28,6 @@ namespace blink { 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().ResolvedIsColumnFlexDirection()), is_horizontal_flow_(FlexLayoutAlgorithm::IsHorizontalFlow(Style())), is_cross_size_definite_(IsContainerCrossSizeDefinite()) { @@ -39,10 +36,8 @@ NGFlexLayoutAlgorithm::NGFlexLayoutAlgorithm( container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); border_box_size_ = container_builder_.InitialBorderBoxSize(); - content_box_size_ = - ShrinkAvailableSize(border_box_size_, border_scrollbar_padding_); child_percentage_size_ = CalculateChildPercentageSize( - ConstraintSpace(), Node(), content_box_size_); + ConstraintSpace(), Node(), ChildAvailableSize()); algorithm_.emplace(&Style(), MainAxisContentExtent(LayoutUnit::Max()), child_percentage_size_, &Node().GetDocument()); @@ -57,20 +52,22 @@ bool NGFlexLayoutAlgorithm::MainAxisIsInlineAxis( LayoutUnit NGFlexLayoutAlgorithm::MainAxisContentExtent( LayoutUnit sum_hypothetical_main_size) const { if (Style().ResolvedIsColumnFlexDirection()) { - // Even though we only pass border_padding_ in the third parameter, the + // Even though we only pass border_padding in the third parameter, the // return value includes scrollbar, so subtract scrollbar to get content // size. - // We add border_scrollbar_padding to the fourth parameter because + // We add |border_scrollbar_padding| to the fourth parameter because // |content_size| needs to be the size of the border box. We've overloaded // the term "content". - return ComputeBlockSizeForFragment(ConstraintSpace(), Style(), - border_padding_, - sum_hypothetical_main_size + - border_scrollbar_padding_.BlockSum(), - border_box_size_.inline_size) - - border_scrollbar_padding_.BlockSum(); + const LayoutUnit border_scrollbar_padding = + BorderScrollbarPadding().BlockSum(); + return ComputeBlockSizeForFragment( + ConstraintSpace(), Style(), BorderPadding(), + sum_hypothetical_main_size.ClampNegativeToZero() + + border_scrollbar_padding, + border_box_size_.inline_size) - + border_scrollbar_padding; } - return content_box_size_.inline_size; + return ChildAvailableSize().inline_size; } namespace { @@ -103,15 +100,20 @@ AxisEdge CrossAxisStaticPositionEdge(const ComputedStyle& style, const ComputedStyle& child_style) { ItemPosition alignment = FlexLayoutAlgorithm::AlignmentForChild(style, child_style); - bool is_wrap_reverse = style.FlexWrap() == EFlexWrap::kWrapReverse; + // AlignmentForChild already accounted for wrap-reverse for kFlexStart and + // kFlexEnd, but not kStretch. kStretch is supposed to act like kFlexStart. + if (style.FlexWrap() == EFlexWrap::kWrapReverse && + alignment == ItemPosition::kStretch) { + return AxisEdge::kEnd; + } if (alignment == ItemPosition::kFlexEnd) - return is_wrap_reverse ? AxisEdge::kStart : AxisEdge::kEnd; + return AxisEdge::kEnd; if (alignment == ItemPosition::kCenter) return AxisEdge::kCenter; - return is_wrap_reverse ? AxisEdge::kEnd : AxisEdge::kStart; + return AxisEdge::kStart; } } // namespace @@ -129,18 +131,17 @@ void NGFlexLayoutAlgorithm::HandleOutOfFlowPositioned(NGBlockNode child) { InlineEdge inline_edge; BlockEdge block_edge; - LogicalOffset offset(border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start); + LogicalOffset offset = BorderScrollbarPadding().StartOffset(); // Determine the static-position based off the axis-edge. if (inline_axis_edge == AxisEdge::kStart) { inline_edge = InlineEdge::kInlineStart; } else if (inline_axis_edge == AxisEdge::kCenter) { inline_edge = InlineEdge::kInlineCenter; - offset.inline_offset += content_box_size_.inline_size / 2; + offset.inline_offset += ChildAvailableSize().inline_size / 2; } else { inline_edge = InlineEdge::kInlineEnd; - offset.inline_offset += content_box_size_.inline_size; + offset.inline_offset += ChildAvailableSize().inline_size; } // We may not know the final block-size of the fragment yet. This will be @@ -149,10 +150,10 @@ void NGFlexLayoutAlgorithm::HandleOutOfFlowPositioned(NGBlockNode child) { block_edge = BlockEdge::kBlockStart; } else if (block_axis_edge == AxisEdge::kCenter) { block_edge = BlockEdge::kBlockCenter; - offset.block_offset -= border_scrollbar_padding_.BlockSum() / 2; + offset.block_offset -= BorderScrollbarPadding().BlockSum() / 2; } else { block_edge = BlockEdge::kBlockEnd; - offset.block_offset -= border_scrollbar_padding_.BlockSum(); + offset.block_offset -= BorderScrollbarPadding().BlockSum(); } container_builder_.AddOutOfFlowChildCandidate(child, offset, inline_edge, @@ -329,16 +330,17 @@ double NGFlexLayoutAlgorithm::GetMainOverCrossAspectRatio( return ratio; } -namespace { - -LayoutUnit CalculateFixedCrossSize(LayoutUnit available_size, - const MinMaxSizes& cross_axis_min_max, - LayoutUnit margin_sum) { +LayoutUnit NGFlexLayoutAlgorithm::CalculateFixedCrossSize( + const MinMaxSizes& cross_axis_min_max, + const NGBoxStrut& margins) const { + if (!is_column_) + DCHECK_NE(ChildAvailableSize().block_size, kIndefiniteSize); + LayoutUnit available_size = is_column_ ? ChildAvailableSize().inline_size + : ChildAvailableSize().block_size; + LayoutUnit margin_sum = is_column_ ? margins.InlineSum() : margins.BlockSum(); return cross_axis_min_max.ClampSizeToMinAndMax(available_size - margin_sum); } -} // namespace - NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForIntrinsicBlockSize( const NGBlockNode& flex_item, const NGPhysicalBoxStrut& physical_margins = NGPhysicalBoxStrut(), @@ -354,22 +356,19 @@ NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForIntrinsicBlockSize( NGBoxStrut margins = physical_margins.ConvertToLogical( ConstraintSpace().GetWritingMode(), Style().Direction()); - LogicalSize child_available_size = content_box_size_; + LogicalSize child_available_size = ChildAvailableSize(); if (ShouldItemShrinkToFit(flex_item)) { space_builder.SetIsShrinkToFit(true); } else if (cross_axis_min_max.min_size != kIndefiniteSize && WillChildCrossSizeBeContainerCrossSize(flex_item)) { + LayoutUnit cross_size = + CalculateFixedCrossSize(cross_axis_min_max, margins); if (is_column_) { space_builder.SetIsFixedInlineSize(true); - child_available_size.inline_size = - CalculateFixedCrossSize(child_available_size.inline_size, - cross_axis_min_max, margins.InlineSum()); + child_available_size.inline_size = cross_size; } else { space_builder.SetIsFixedBlockSize(true); - DCHECK_NE(content_box_size_.block_size, kIndefiniteSize); - child_available_size.block_size = - CalculateFixedCrossSize(child_available_size.block_size, - cross_axis_min_max, margins.BlockSum()); + child_available_size.block_size = cross_size; } } @@ -402,7 +401,7 @@ NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForFlexBasis( // This space is only used for resolving lengths, not for layout. We only // need the available and percentage sizes. - space_builder.SetAvailableSize(content_box_size_); + space_builder.SetAvailableSize(ChildAvailableSize()); space_builder.SetPercentageResolutionSize(child_percentage_size_); space_builder.SetReplacedPercentageResolutionSize(child_percentage_size_); return space_builder.ToConstraintSpace(); @@ -439,20 +438,15 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { : physical_border_padding.HorizontalSum(); base::Optional<MinMaxSizesResult> min_max_sizes; - auto MinMaxSizesFunc = [&]() -> MinMaxSizesResult { + auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult { if (!min_max_sizes) { - NGConstraintSpace child_space = BuildSpaceForIntrinsicBlockSize(child); - if (child_style.OverflowBlockDirection() == EOverflow::kAuto) { - // Ensure this child has been laid out so its auto scrollbars are - // included in its intrinsic sizes. - child.Layout(child_space); - } // We want the child's intrinsic inline sizes in its writing mode, so // pass child's writing mode as the first parameter, which is nominally // |container_writing_mode|. - min_max_sizes = child.ComputeMinMaxSizes( - child_style.GetWritingMode(), - MinMaxSizesInput(content_box_size_.block_size), &child_space); + NGConstraintSpace child_space = BuildSpaceForIntrinsicBlockSize(child); + MinMaxSizesInput input(ChildAvailableSize().block_size, type); + min_max_sizes = child.ComputeMinMaxSizes(child_style.GetWritingMode(), + input, &child_space); } return *min_max_sizes; }; @@ -528,27 +522,25 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { // https://drafts.csswg.org/css-flexbox/#algo-main-item const Length& cross_axis_length = is_horizontal_flow_ ? child.Style().Height() : child.Style().Width(); - if (child.HasAspectRatio() && - (IsItemCrossAxisLengthDefinite(child, cross_axis_length))) { + // This check should use HasAspectRatio() instead of Style(). + // AspectRatio(), but to avoid introducing a behavior change we only + // do this for the aspect-ratio property for now until FlexNG ships. + bool use_cross_axis_for_aspect_ratio = + child.Style().AspectRatio() && + WillChildCrossSizeBeContainerCrossSize(child); + if (use_cross_axis_for_aspect_ratio || + (child.HasAspectRatio() && + (IsItemCrossAxisLengthDefinite(child, cross_axis_length)))) { // This is Part B of 9.2.3 // https://drafts.csswg.org/css-flexbox/#algo-main-item It requires that // the item has a definite cross size. - // - // But for determining the flex-basis of aspect ratio items, both legacy - // and FF both ignore part of the flex spec that has a more lenient - // definition of definite. - // https://drafts.csswg.org/css-flexbox/#definite says "If a single-line - // flex container has a definite cross size, the outer cross size of any - // stretched flex items is the flex container's inner cross size - // (clamped to the flex item's min and max cross size) and is considered - // definite". But when this happens, neither legacy nor firefox use the - // container's cross size to calculate the item's main size, they just - // fall to block E. E.g. Legacy and FF show a 16x100 green square - // instead of a 100x100 green square for - // https://jsfiddle.net/dgrogan/djh5wu0x/1/. I think it should be - // 100x100. LayoutUnit cross_size; - if (MainAxisIsInlineAxis(child)) { + if (use_cross_axis_for_aspect_ratio) { + NGBoxStrut margins = physical_child_margins.ConvertToLogical( + ConstraintSpace().GetWritingMode(), Style().Direction()); + cross_size = CalculateFixedCrossSize( + min_max_sizes_in_cross_axis_direction, margins); + } else if (MainAxisIsInlineAxis(child)) { cross_size = ResolveMainBlockLength( flex_basis_space, child_style, border_padding_in_child_writing_mode, cross_axis_length, @@ -575,12 +567,14 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { // is not exactly correct. // TODO(dgrogan): Replace with a variant of ComputeReplacedSize that // ignores min-width, width, max-width. - MinMaxSizesInput input(child_percentage_size_.block_size); + MinMaxSizesInput input(child_percentage_size_.block_size, + MinMaxSizesType::kContent); flex_base_border_box = ComputeMinAndMaxContentContribution(Style(), child, input) .sizes.max_size; } else { - flex_base_border_box = MinMaxSizesFunc().sizes.max_size; + flex_base_border_box = + MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.max_size; } } else { // Parts C, D, and E for what are usually column flex containers. @@ -627,14 +621,16 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { MinMaxSizes table_preferred_widths = ComputeMinAndMaxContentContribution( Style(), child, - MinMaxSizesInput(child_percentage_size_.block_size)) + MinMaxSizesInput(child_percentage_size_.block_size, + MinMaxSizesType::kIntrinsic)) .sizes; min_max_sizes_in_main_axis_direction.min_size = table_preferred_widths.min_size; } else { LayoutUnit content_size_suggestion; if (MainAxisIsInlineAxis(child)) { - content_size_suggestion = MinMaxSizesFunc().sizes.min_size; + content_size_suggestion = + MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.min_size; } else { LayoutUnit intrinsic_block_size; if (child.IsReplaced()) { @@ -737,12 +733,13 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { DCHECK_GE(min_max_sizes_in_main_axis_direction.min_size, 0); DCHECK_GE(min_max_sizes_in_main_axis_direction.max_size, 0); + NGBoxStrut scrollbars = ComputeScrollbarsForNonAnonymous(child); algorithm_ ->emplace_back(nullptr, child.Style(), flex_base_content_size, min_max_sizes_in_main_axis_direction, min_max_sizes_in_cross_axis_direction, main_axis_border_padding, cross_axis_border_padding, - physical_child_margins) + physical_child_margins, scrollbars) .ng_input_node = child; } } @@ -791,27 +788,49 @@ NGFlexLayoutAlgorithm::AdjustChildSizeForAspectRatioCrossAxisMinAndMax( } scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { + if (auto result = LayoutInternal()) + return result; + + // We may have aborted layout due to a child changing scrollbars, relayout + // with the new scrollbar information. + return RelayoutIgnoringChildScrollbarChanges(); +} + +scoped_refptr<const NGLayoutResult> +NGFlexLayoutAlgorithm::RelayoutIgnoringChildScrollbarChanges() { + DCHECK(!ignore_child_scrollbar_changes_); + // Freezing the scrollbars for the sub-tree shouldn't be strictly necessary, + // but we do this just in case we trigger an unstable layout. + PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars; + NGLayoutAlgorithmParams params( + Node(), container_builder_.InitialFragmentGeometry(), ConstraintSpace(), + BreakToken(), /* early_break */ nullptr); + NGFlexLayoutAlgorithm algorithm(params); + algorithm.ignore_child_scrollbar_changes_ = true; + return algorithm.Layout(); +} + +scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::LayoutInternal() { PaintLayerScrollableArea::DelayScrollOffsetClampScope delay_clamp_scope; ConstructAndAppendFlexItems(); LayoutUnit main_axis_start_offset; LayoutUnit main_axis_end_offset; - LayoutUnit cross_axis_offset = border_scrollbar_padding_.block_start; + LayoutUnit cross_axis_offset = BorderScrollbarPadding().block_start; if (is_column_) { const bool is_column_reverse = Style().ResolvedIsColumnReverseFlexDirection(); - main_axis_start_offset = is_column_reverse - ? LayoutUnit() - : border_scrollbar_padding_.block_start; + main_axis_start_offset = + is_column_reverse ? LayoutUnit() : BorderScrollbarPadding().block_start; main_axis_end_offset = - is_column_reverse ? LayoutUnit() : border_scrollbar_padding_.block_end; - cross_axis_offset = border_scrollbar_padding_.inline_start; + is_column_reverse ? LayoutUnit() : BorderScrollbarPadding().block_end; + cross_axis_offset = BorderScrollbarPadding().inline_start; } else if (Style().ResolvedIsRowReverseFlexDirection()) { - main_axis_start_offset = border_scrollbar_padding_.inline_end; - main_axis_end_offset = border_scrollbar_padding_.inline_start; + main_axis_start_offset = BorderScrollbarPadding().inline_end; + main_axis_end_offset = BorderScrollbarPadding().inline_start; } else { - main_axis_start_offset = border_scrollbar_padding_.inline_start; - main_axis_end_offset = border_scrollbar_padding_.inline_end; + main_axis_start_offset = BorderScrollbarPadding().inline_start; + main_axis_end_offset = BorderScrollbarPadding().inline_end; } FlexLine* line; while ( @@ -838,15 +857,14 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { NGBoxStrut margins = flex_item.physical_margins.ConvertToLogical( ConstraintSpace().GetWritingMode(), Style().Direction()); if (is_column_) { - available_size.inline_size = content_box_size_.inline_size; + available_size.inline_size = ChildAvailableSize().inline_size; available_size.block_size = flex_item.flexed_content_size + flex_item.main_axis_border_padding; space_builder.SetIsFixedBlockSize(true); if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node)) { space_builder.SetIsFixedInlineSize(true); available_size.inline_size = CalculateFixedCrossSize( - available_size.inline_size, flex_item.min_max_cross_sizes.value(), - margins.InlineSum()); + flex_item.min_max_cross_sizes.value(), margins); } // https://drafts.csswg.org/css-flexbox/#definite-sizes // If the flex container has a definite main size, a flex item's @@ -859,13 +877,12 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { } else { available_size.inline_size = flex_item.flexed_content_size + flex_item.main_axis_border_padding; - available_size.block_size = content_box_size_.block_size; + available_size.block_size = ChildAvailableSize().block_size; space_builder.SetIsFixedInlineSize(true); if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node)) { space_builder.SetIsFixedBlockSize(true); available_size.block_size = CalculateFixedCrossSize( - available_size.block_size, flex_item.min_max_cross_sizes.value(), - margins.BlockSum()); + flex_item.min_max_cross_sizes.value(), margins); } else if (DoesItemStretch(flex_item.ng_input_node)) { // If we are in a row flexbox, and we don't have a fixed block-size // (yet), use the "measure" cache slot. This will be the first @@ -913,20 +930,28 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { cross_axis_offset); } - LayoutUnit intrinsic_block_size = algorithm_->IntrinsicContentBlockSize() + - border_scrollbar_padding_.BlockSum(); + LayoutUnit intrinsic_block_size = BorderScrollbarPadding().BlockSum(); + + if (algorithm_->FlexLines().IsEmpty() && + To<LayoutBlock>(Node().GetLayoutBox())->HasLineIfEmpty()) { + intrinsic_block_size += Node().GetLayoutBox()->LogicalHeightForEmptyLine(); + } else { + intrinsic_block_size += algorithm_->IntrinsicContentBlockSize(); + } intrinsic_block_size = ClampIntrinsicBlockSize(ConstraintSpace(), Node(), - border_scrollbar_padding_, intrinsic_block_size); + BorderScrollbarPadding(), intrinsic_block_size); LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, intrinsic_block_size, + ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size, border_box_size_.inline_size); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); - container_builder_.SetBlockSize(block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); - GiveLinesAndItemsFinalPositionAndSize(); + bool success = GiveLinesAndItemsFinalPositionAndSize(); + if (!success) + return nullptr; NGOutOfFlowLayoutPart( Node(), ConstraintSpace(), @@ -973,16 +998,16 @@ void NGFlexLayoutAlgorithm::ApplyStretchAlignmentToChild(FlexItem& flex_item) { flex_item.ng_input_node.Layout(child_space, /* break_token */ nullptr); } -void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { +bool NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { Vector<FlexLine>& line_contexts = algorithm_->FlexLines(); const LayoutUnit cross_axis_start_edge = line_contexts.IsEmpty() ? LayoutUnit() : line_contexts[0].cross_axis_offset; LayoutUnit final_content_main_size = - container_builder_.InlineSize() - border_scrollbar_padding_.InlineSum(); - LayoutUnit final_content_cross_size = - container_builder_.BlockSize() - border_scrollbar_padding_.BlockSum(); + container_builder_.InlineSize() - BorderScrollbarPadding().InlineSum(); + LayoutUnit final_content_cross_size = container_builder_.FragmentBlockSize() - + BorderScrollbarPadding().BlockSum(); if (is_column_) std::swap(final_content_main_size, final_content_cross_size); @@ -1003,11 +1028,12 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { if (Style().ResolvedIsColumnReverseFlexDirection()) { algorithm_->LayoutColumnReverse(final_content_main_size, - border_scrollbar_padding_.block_start); + BorderScrollbarPadding().block_start); } base::Optional<LayoutUnit> fallback_baseline; + bool success = true; LayoutUnit overflow_block_size; for (FlexLine& line_context : line_contexts) { for (wtf_size_t child_number = 0; @@ -1049,16 +1075,30 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { overflow_block_size = std::max(overflow_block_size, location.Y() + fragment.BlockSize() + margin_block_end); + + // Detect if the flex-item had its scrollbar state change. If so we need + // to relayout as the input to the flex algorithm is incorrect. + if (!ignore_child_scrollbar_changes_) { + if (flex_item.scrollbars != + ComputeScrollbarsForNonAnonymous(flex_item.ng_input_node)) + success = false; + } else { + DCHECK_EQ(flex_item.scrollbars, + ComputeScrollbarsForNonAnonymous(flex_item.ng_input_node)); + } } } container_builder_.SetOverflowBlockSize(overflow_block_size + - border_scrollbar_padding_.block_end); + BorderScrollbarPadding().block_end); // Set the baseline to the fallback, if we didn't find any children with // baseline alignment. if (!container_builder_.Baseline() && fallback_baseline) container_builder_.SetBaseline(*fallback_baseline); + + // Signal if we need to relayout with new child scrollbar information. + return success; } void NGFlexLayoutAlgorithm::PropagateBaselineFromChild( @@ -1090,7 +1130,7 @@ void NGFlexLayoutAlgorithm::PropagateBaselineFromChild( MinMaxSizesResult NGFlexLayoutAlgorithm::ComputeMinMaxSizes( const MinMaxSizesInput& child_input) const { if (auto result = CalculateMinMaxSizesIgnoringChildren( - Node(), border_scrollbar_padding_)) + Node(), BorderScrollbarPadding())) return *result; MinMaxSizes sizes; @@ -1136,7 +1176,7 @@ MinMaxSizesResult NGFlexLayoutAlgorithm::ComputeMinMaxSizes( // 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 += border_scrollbar_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, depends_on_percentage_block_size}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h index e0e33c2bdc6..8bb19f7da46 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h @@ -23,11 +23,13 @@ class CORE_EXPORT NGFlexLayoutAlgorithm public: NGFlexLayoutAlgorithm(const NGLayoutAlgorithmParams& params); - scoped_refptr<const NGLayoutResult> Layout() override; - MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override; + scoped_refptr<const NGLayoutResult> Layout() override; private: + scoped_refptr<const NGLayoutResult> RelayoutIgnoringChildScrollbarChanges(); + scoped_refptr<const NGLayoutResult> LayoutInternal(); + bool DoesItemCrossSizeComputeToAuto(const NGBlockNode& child) const; bool IsItemFlexBasisDefinite(const NGBlockNode& child) const; bool IsItemMainSizeDefinite(const NGBlockNode& child) const; @@ -51,6 +53,9 @@ class CORE_EXPORT NGFlexLayoutAlgorithm bool IsColumnContainerMainSizeDefinite() const; bool IsContainerCrossSizeDefinite() const; + LayoutUnit CalculateFixedCrossSize(const MinMaxSizes& cross_axis_min_max, + const NGBoxStrut& margins) const; + NGConstraintSpace BuildSpaceForFlexBasis(const NGBlockNode& flex_item) const; NGConstraintSpace BuildSpaceForIntrinsicBlockSize( const NGBlockNode& flex_item, @@ -58,7 +63,7 @@ class CORE_EXPORT NGFlexLayoutAlgorithm const MinMaxSizes& cross_axis) const; void ConstructAndAppendFlexItems(); void ApplyStretchAlignmentToChild(FlexItem& flex_item); - void GiveLinesAndItemsFinalPositionAndSize(); + bool GiveLinesAndItemsFinalPositionAndSize(); void LayoutColumnReverse(LayoutUnit main_axis_content_size); // This is same method as FlexItem but we need that logic before FlexItem is @@ -75,13 +80,11 @@ class CORE_EXPORT NGFlexLayoutAlgorithm LayoutUnit block_offset, base::Optional<LayoutUnit>* fallback_baseline); - const NGBoxStrut border_padding_; - const NGBoxStrut border_scrollbar_padding_; const bool is_column_; const bool is_horizontal_flow_; const bool is_cross_size_definite_; + bool ignore_child_scrollbar_changes_ = false; LogicalSize border_box_size_; - LogicalSize content_box_size_; LogicalSize child_percentage_size_; base::Optional<FlexLayoutAlgorithm> algorithm_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_grid.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.cc index 672f1a4b32c..b4bdc582181 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_grid.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "third_party/blink/renderer/core/layout/ng/layout_ng_grid.h" +#include "third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_grid.h b/chromium/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h index 060835974ba..d7601da5c03 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_grid.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.h @@ -2,8 +2,8 @@ // 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_LAYOUT_NG_GRID_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_GRID_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_LAYOUT_NG_GRID_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_LAYOUT_NG_GRID_H_ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/layout_block.h" @@ -28,4 +28,4 @@ class CORE_EXPORT LayoutNGGrid : public LayoutNGMixin<LayoutBlock> { } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_GRID_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_LAYOUT_NG_GRID_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.cc new file mode 100644 index 00000000000..d0791c16dea --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.cc @@ -0,0 +1,37 @@ +// Copyright 2020 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/grid/ng_grid_child_iterator.h" + +namespace blink { + +NGGridChildIterator::NGGridChildIterator(const NGBlockNode node) { + Setup(node); +} + +void NGGridChildIterator::Setup(const NGBlockNode node) { + const int initial_order = ComputedStyleInitialValues::InitialOrder(); + bool needs_sort = false; + + // Collect all our children, and order them by either their order property. + for (NGLayoutInputNode child = node.FirstChild(); child; + child = child.NextSibling()) { + int order = child.Style().Order(); + needs_sort |= order != initial_order; + children_.emplace_back(To<NGBlockNode>(child), order); + } + + // We only need to sort this vector if we encountered a non-initial order + // property. + if (needs_sort) { + std::stable_sort(children_.begin(), children_.end(), + [](const ChildWithOrder& c1, const ChildWithOrder& c2) { + return c1.order < c2.order; + }); + } + + iterator_ = children_.begin(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.h b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.h new file mode 100644 index 00000000000..440e4a8180f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.h @@ -0,0 +1,54 @@ +// Copyright 2020 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_GRID_NG_GRID_CHILD_ITERATOR_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_CHILD_ITERATOR_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +// A utility class which given the current grid node will iterate through its +// children. +// +// TODO(layout-dev): Once LayoutNG supports NG-fragmentation this will need +// to be updated to accept a break-token. +// +// This class does not handle modifications to its arguments after it has been +// constructed. +class CORE_EXPORT NGGridChildIterator { + STACK_ALLOCATED(); + + public: + explicit NGGridChildIterator(const NGBlockNode node); + + // Returns the next block node which should be laid out. + NGBlockNode NextChild() { + if (iterator_ == children_.end()) + return nullptr; + + return (*iterator_++).child; + } + + struct ChildWithOrder { + DISALLOW_NEW(); + ChildWithOrder(NGBlockNode child, int order) : child(child), order(order) {} + NGBlockNode child; + int order; + }; + + protected: + virtual void Setup(const NGBlockNode node); + Vector<ChildWithOrder, 4> children_; + Vector<ChildWithOrder, 4>::const_iterator iterator_; +}; + +} // namespace blink + +WTF_ALLOW_MOVE_AND_INIT_WITH_MEM_FUNCTIONS( + blink::NGGridChildIterator::ChildWithOrder) + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_CHILD_ITERATOR_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator_test.cc new file mode 100644 index 00000000000..d5468ce0775 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator_test.cc @@ -0,0 +1,98 @@ +// Copyright 2020 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/grid/ng_grid_child_iterator.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" + +namespace blink { +namespace { + +TEST_F(NGLayoutTest, TestNGGridChildIterator) { + SetBodyInnerHTML(R"HTML( + <div id="parent" style="display: grid"> + <div id="child1">Child 1</div> + <div id="child2">Child 2</div> + <div id="child3">Child 3</div> + <div id="child4">Child 4</div> + </div> + )HTML"); + + NGBlockNode parent_block(ToLayoutBox(GetLayoutObjectByElementId("parent"))); + + int index = 0; + NGGridChildIterator iterator(parent_block); + for (NGBlockNode child = iterator.NextChild(); child; + child = iterator.NextChild()) { + StringBuilder cell_id; + cell_id.Append("child"); + cell_id.Append(AtomicString::Number(++index)); + NGBlockNode cell_block(ToLayoutBox( + GetLayoutObjectByElementId(cell_id.ToString().Ascii().c_str()))); + EXPECT_EQ(child, cell_block); + } + + EXPECT_EQ(index, 4); +} + +TEST_F(NGLayoutTest, TestNGGridChildIteratorWithOrderReversed) { + SetBodyInnerHTML(R"HTML( + <div id="parent" style="display: grid"> + <div id="child1" style="order: 4">Child 1</div> + <div id="child2" style="order: 3">Child 2</div> + <div id="child3" style="order: 2">Child 3</div> + <div id="child4" style="order: 1">Child 4</div> + </div> + )HTML"); + + NGBlockNode parent_block(ToLayoutBox(GetLayoutObjectByElementId("parent"))); + + int index = 4; + NGGridChildIterator iterator(parent_block); + for (NGBlockNode child = iterator.NextChild(); child; + child = iterator.NextChild()) { + StringBuilder cell_id; + cell_id.Append("child"); + cell_id.Append(AtomicString::Number(index)); + NGBlockNode cell_block(ToLayoutBox( + GetLayoutObjectByElementId(cell_id.ToString().Ascii().c_str()))); + EXPECT_EQ(child, cell_block); + --index; + } + + EXPECT_EQ(index, 0); +} + +TEST_F(NGLayoutTest, TestNGGridChildIteratorWithOrderMixed) { + SetBodyInnerHTML(R"HTML( + <div id="parent" style="display: grid"> + <div id="child1" style="order: 3">Child 1</div> + <div id="child2" style="order: 3">Child 2</div> + <div id="child3" style="order: -1">Child 3</div> + <div id="child4" style="order: 0">Child 4</div> + </div> + )HTML"); + + NGBlockNode parent_block(ToLayoutBox(GetLayoutObjectByElementId("parent"))); + int expected_order[] = {3, 4, 1, 2}; + + int index = 0; + NGGridChildIterator iterator(parent_block); + for (NGBlockNode child = iterator.NextChild(); child; + child = iterator.NextChild()) { + StringBuilder cell_id; + cell_id.Append("child"); + cell_id.Append(AtomicString::Number(expected_order[index])); + NGBlockNode cell_block(ToLayoutBox( + GetLayoutObjectByElementId(cell_id.ToString().Ascii().c_str()))); + EXPECT_EQ(child, cell_block); + ++index; + } + + EXPECT_EQ(index, 4); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc new file mode 100644 index 00000000000..63c63ee2c72 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc @@ -0,0 +1,76 @@ +// Copyright 2020 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/grid/ng_grid_layout_algorithm.h" + +#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.h" +#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" + +namespace blink { + +NGGridLayoutAlgorithm::NGGridLayoutAlgorithm( + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + state_(GridLayoutAlgorithmState::kMeasuringItems) { + DCHECK(params.space.IsNewFormattingContext()); + DCHECK(!params.break_token); + container_builder_.SetIsNewFormattingContext(true); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + + child_percentage_size_ = CalculateChildPercentageSize( + ConstraintSpace(), Node(), ChildAvailableSize()); +} + +scoped_refptr<const NGLayoutResult> NGGridLayoutAlgorithm::Layout() { + switch (state_) { + case GridLayoutAlgorithmState::kMeasuringItems: + ConstructAndAppendGridItems(); + break; + + default: + break; + } + + return container_builder_.ToBoxFragment(); +} + +MinMaxSizesResult NGGridLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + return {MinMaxSizes(), /* depends_on_percentage_block_size */ true}; +} + +void NGGridLayoutAlgorithm::ConstructAndAppendGridItems() { + NGGridChildIterator iterator(Node()); + for (NGBlockNode child = iterator.NextChild(); child; + child = iterator.NextChild()) { + ConstructAndAppendGridItem(child); + } +} + +void NGGridLayoutAlgorithm::ConstructAndAppendGridItem( + const NGBlockNode& node) { + GridItem item; + item.constraint_space = BuildSpaceForMeasure(node); + items_.emplace_back(item); +} + +NGConstraintSpace NGGridLayoutAlgorithm::BuildSpaceForMeasure( + const NGBlockNode& grid_item) { + const ComputedStyle& child_style = grid_item.Style(); + + NGConstraintSpaceBuilder space_builder(ConstraintSpace(), + child_style.GetWritingMode(), + /* is_new_fc */ true); + space_builder.SetCacheSlot(NGCacheSlot::kMeasure); + space_builder.SetIsPaintedAtomically(true); + + // TODO(kschmi) - do layout/measuring and handle non-fixed sizes here. + space_builder.SetAvailableSize(ChildAvailableSize()); + space_builder.SetPercentageResolutionSize(child_percentage_size_); + space_builder.SetTextDirection(child_style.Direction()); + return space_builder.ToConstraintSpace(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h index 65530019710..e0898325e1a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h @@ -2,12 +2,14 @@ // 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_GRID_LAYOUT_ALGORITHM_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_GRID_LAYOUT_ALGORITHM_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_LAYOUT_ALGORITHM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_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" +#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink { @@ -21,8 +23,27 @@ class CORE_EXPORT NGGridLayoutAlgorithm scoped_refptr<const NGLayoutResult> Layout() override; MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override; + + private: + friend class NGGridLayoutAlgorithmTest; + + void ConstructAndAppendGridItems(); + void ConstructAndAppendGridItem(const NGBlockNode& node); + NGConstraintSpace BuildSpaceForMeasure(const NGBlockNode& grid_item); + + enum class GridLayoutAlgorithmState { + kMeasuringItems, + }; + GridLayoutAlgorithmState state_; + + struct GridItem { + NGConstraintSpace constraint_space; + }; + Vector<GridItem> items_; + + LogicalSize child_percentage_size_; }; } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_GRID_LAYOUT_ALGORITHM_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_LAYOUT_ALGORITHM_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc new file mode 100644 index 00000000000..0f991d63a5d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm_test.cc @@ -0,0 +1,106 @@ +// Copyright 2020 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/grid/ng_grid_layout_algorithm.h" +#include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" + +namespace blink { + +class NGGridLayoutAlgorithmTest + : public NGBaseLayoutAlgorithmTest, + private ScopedLayoutNGGridForTest, + private ScopedLayoutNGBlockFragmentationForTest { + protected: + NGGridLayoutAlgorithmTest() + : ScopedLayoutNGGridForTest(true), + ScopedLayoutNGBlockFragmentationForTest(true) {} + void SetUp() override { + NGBaseLayoutAlgorithmTest::SetUp(); + style_ = ComputedStyle::Create(); + } + + // Helper methods to access private data on NGGridLayoutAlgorithm. This class + // is a friend of NGGridLayoutAlgorithm but the individual tests are not. + size_t GridItemSize(NGGridLayoutAlgorithm& algorithm) { + return algorithm.items_.size(); + } + + Vector<NGConstraintSpace> GridItemConstraintSpaces( + NGGridLayoutAlgorithm& algorithm) { + Vector<NGConstraintSpace> constraint_spaces; + for (auto& item : algorithm.items_) { + constraint_spaces.push_back(NGConstraintSpace(item.constraint_space)); + } + return constraint_spaces; + } + + scoped_refptr<ComputedStyle> style_; +}; + +TEST_F(NGGridLayoutAlgorithmTest, NGGridLayoutAlgorithmMeasuring) { + if (!RuntimeEnabledFeatures::LayoutNGGridEnabled()) + return; + + SetBodyInnerHTML(R"HTML( + <style> + #grid1 { + display: grid; + grid-template-columns: 100px 100px; + grid-template-rows: 100px 100px; + } + #cell1 { + grid-column: 1; + grid-row: 1; + width: 50px; + } + #cell2 { + grid-column: 2; + grid-row: 1; + width: 50px; + } + #cell3 { + grid-column: 1; + grid-row: 2; + width: 50px; + } + #cell4 { + grid-column: 2; + grid-row: 2; + width: 50px; + } + </style> + <div id="grid1"> + <div id="cell1">Cell 1</div> + <div id="cell2">Cell 2</div> + <div id="cell3">Cell 3</div> + <div id="cell4">Cell 4</div> + </div> + )HTML"); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("grid1"))); + + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(100), LayoutUnit(100)), false, true); + + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(space, node); + + NGGridLayoutAlgorithm algorithm({node, fragment_geometry, space}); + EXPECT_EQ(GridItemSize(algorithm), 0U); + algorithm.Layout(); + EXPECT_EQ(GridItemSize(algorithm), 4U); + + Vector<NGConstraintSpace> constraint_spaces = + GridItemConstraintSpaces(algorithm); + + EXPECT_EQ(GridItemSize(algorithm), constraint_spaces.size()); + for (auto& constraint_space : constraint_spaces) { + EXPECT_EQ(constraint_space.AvailableSize().inline_size.ToInt(), 100); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.cc new file mode 100644 index 00000000000..26c190ad4a7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.cc @@ -0,0 +1,510 @@ +// Copyright 2020 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/grid/ng_grid_track_collection.h" +#include "base/check.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +constexpr wtf_size_t NGGridTrackCollectionBase::kInvalidRangeIndex; + +wtf_size_t NGGridTrackCollectionBase::RangeIndexFromTrackNumber( + wtf_size_t track_number) const { + wtf_size_t upper = RangeCount(); + wtf_size_t lower = 0u; + + // We can't look for a range in a collection with no ranges. + DCHECK_NE(upper, 0u); + // We don't expect a |track_number| outside of the bounds of the collection. + DCHECK_NE(track_number, kInvalidRangeIndex); + DCHECK_LT(track_number, + RangeTrackNumber(upper - 1u) + RangeTrackCount(upper - 1u)); + + // Do a binary search on the tracks. + wtf_size_t range = upper - lower; + while (range > 1) { + wtf_size_t center = lower + (range / 2u); + + wtf_size_t center_track_number = RangeTrackNumber(center); + wtf_size_t center_track_count = RangeTrackCount(center); + + if (center_track_number <= track_number && + (track_number - center_track_number) < center_track_count) { + // We found the track. + return center; + } else if (center_track_number > track_number) { + // This track is too high. + upper = center; + range = upper - lower; + } else { + // This track is too low. + lower = center + 1u; + range = upper - lower; + } + } + + return lower; +} + +String NGGridTrackCollectionBase::ToString() const { + if (RangeCount() == kInvalidRangeIndex) + return "NGGridTrackCollection: Empty"; + + StringBuilder builder; + builder.Append("NGGridTrackCollection: [RangeCount: "); + builder.AppendNumber<wtf_size_t>(RangeCount()); + builder.Append("], Ranges: "); + for (wtf_size_t range_index = 0; range_index < RangeCount(); ++range_index) { + builder.Append("[Start: "); + builder.AppendNumber<wtf_size_t>(RangeTrackNumber(range_index)); + builder.Append(", Count: "); + builder.AppendNumber<wtf_size_t>(RangeTrackCount(range_index)); + if (IsRangeCollapsed(range_index)) { + builder.Append(", Collapsed "); + } + builder.Append("]"); + if (range_index + 1 < RangeCount()) + builder.Append(", "); + } + return builder.ToString(); +} + +NGGridTrackCollectionBase::RangeRepeatIterator::RangeRepeatIterator( + const NGGridTrackCollectionBase* collection, + wtf_size_t range_index) + : collection_(collection) { + DCHECK(collection_); + range_count_ = collection_->RangeCount(); + SetRangeIndex(range_index); +} + +bool NGGridTrackCollectionBase::RangeRepeatIterator::MoveToNextRange() { + return SetRangeIndex(range_index_ + 1); +} + +wtf_size_t NGGridTrackCollectionBase::RangeRepeatIterator::RepeatCount() const { + return range_track_count_; +} + +wtf_size_t NGGridTrackCollectionBase::RangeRepeatIterator::RangeTrackStart() + const { + return range_track_start_; +} + +wtf_size_t NGGridTrackCollectionBase::RangeRepeatIterator::RangeTrackEnd() + const { + if (range_index_ == kInvalidRangeIndex) + return kInvalidRangeIndex; + return range_track_start_ + range_track_count_ - 1; +} + +bool NGGridTrackCollectionBase::RangeRepeatIterator::IsRangeCollapsed() const { + DCHECK(range_index_ != kInvalidRangeIndex); + DCHECK(collection_); + return collection_->IsRangeCollapsed(range_index_); +} + +bool NGGridTrackCollectionBase::RangeRepeatIterator::SetRangeIndex( + wtf_size_t range_index) { + if (range_index < 0 || range_index >= range_count_) { + // Invalid index. + range_index_ = kInvalidRangeIndex; + range_track_start_ = kInvalidRangeIndex; + range_track_count_ = 0; + return false; + } + + range_index_ = range_index; + range_track_start_ = collection_->RangeTrackNumber(range_index_); + range_track_count_ = collection_->RangeTrackCount(range_index_); + return true; +} + +NGGridTrackRepeater::NGGridTrackRepeater(wtf_size_t track_index, + wtf_size_t repeat_size, + wtf_size_t repeat_count, + RepeatType repeat_type) + : track_index(track_index), + repeat_size(repeat_size), + repeat_count(repeat_count), + repeat_type(repeat_type) {} + +String NGGridTrackRepeater::ToString() const { + StringBuilder builder; + builder.Append("Repeater: [Index: "); + builder.AppendNumber<wtf_size_t>(track_index); + builder.Append("], [RepeatSize: "); + builder.AppendNumber<wtf_size_t>(repeat_size); + builder.Append("], [RepeatCount: "); + switch (repeat_type) { + case RepeatType::kCount: + builder.AppendNumber<wtf_size_t>(repeat_count); + builder.Append("]"); + break; + case RepeatType::kAutoFill: + builder.Append("auto-fill]"); + break; + case RepeatType::kAutoFit: + builder.Append("auto-fit]"); + break; + } + return builder.ToString(); +} + +NGGridTrackList::NGGridTrackList() { + auto_repeater_index_ = NGGridTrackCollectionBase::kInvalidRangeIndex; + total_track_count_ = 0; +} + +wtf_size_t NGGridTrackList::RepeatCount(wtf_size_t index, + wtf_size_t auto_value) const { + DCHECK_LT(index, RepeaterCount()); + if (index == auto_repeater_index_) + return auto_value; + return repeaters_[index].repeat_count; +} + +wtf_size_t NGGridTrackList::RepeatSize(wtf_size_t index) const { + DCHECK_LT(index, RepeaterCount()); + return repeaters_[index].repeat_size; +} + +NGGridTrackRepeater::RepeatType NGGridTrackList::RepeatType( + wtf_size_t index) const { + DCHECK_LT(index, RepeaterCount()); + return repeaters_[index].repeat_type; +} + +wtf_size_t NGGridTrackList::RepeaterCount() const { + return repeaters_.size(); +} + +wtf_size_t NGGridTrackList::TotalTrackCount() const { + return total_track_count_; +} + +bool NGGridTrackList::AddRepeater(wtf_size_t track_index, + wtf_size_t track_count, + wtf_size_t repeat_count) { + return AddRepeater(track_index, track_count, repeat_count, + NGGridTrackRepeater::RepeatType::kCount); +} + +bool NGGridTrackList::AddAutoRepeater( + wtf_size_t track_index, + wtf_size_t track_count, + NGGridTrackRepeater::RepeatType repeat_type) { + return AddRepeater(track_index, track_count, 1u, repeat_type); +} + +bool NGGridTrackList::AddRepeater(wtf_size_t track_index, + wtf_size_t track_count, + wtf_size_t repeat_count, + NGGridTrackRepeater::RepeatType repeat_type) { + // Ensure valid track index. + DCHECK_NE(NGGridTrackCollectionBase::kInvalidRangeIndex, track_index); + +#if DCHECK_IS_ON() + // Ensure we do not skip or overlap tracks. + DCHECK(IsTrackContiguous(track_index)); +#endif + + // If the repeater is auto, the repeat_count should be 1. + DCHECK(repeat_type == NGGridTrackRepeater::RepeatType::kCount || + repeat_count == 1u); + + // Ensure adding tracks will not overflow the total in this track list and + // that there is only one auto repeater per track list. + switch (repeat_type) { + case NGGridTrackRepeater::RepeatType::kCount: + if (track_count > AvailableTrackCount() / repeat_count) + return false; + total_track_count_ += track_count * repeat_count; + break; + case NGGridTrackRepeater::RepeatType::kAutoFill: + case NGGridTrackRepeater::RepeatType::kAutoFit: // Intentional Fallthrough. + if (HasAutoRepeater() || track_count > AvailableTrackCount()) + return false; + total_track_count_ += track_count; + // Update auto repeater index and append repeater. + auto_repeater_index_ = repeaters_.size(); + break; + } + + repeaters_.emplace_back(track_index, track_count, repeat_count, repeat_type); + + return true; +} + +String NGGridTrackList::ToString() const { + StringBuilder builder; + builder.Append("TrackList: {"); + for (wtf_size_t i = 0; i < repeaters_.size(); ++i) { + builder.Append(" "); + builder.Append(repeaters_[i].ToString()); + if (i + 1 != repeaters_.size()) + builder.Append(", "); + } + builder.Append(" } "); + return builder.ToString(); +} + +bool NGGridTrackList::HasAutoRepeater() { + return auto_repeater_index_ != NGGridTrackCollectionBase::kInvalidRangeIndex; +} + +wtf_size_t NGGridTrackList::AvailableTrackCount() const { + return NGGridTrackCollectionBase::kMaxRangeIndex - total_track_count_; +} + +#if DCHECK_IS_ON() +bool NGGridTrackList::IsTrackContiguous(wtf_size_t track_index) const { + return repeaters_.IsEmpty() || + (repeaters_.back().track_index + repeaters_.back().repeat_size == + track_index); +} +#endif + +void NGGridBlockTrackCollection::SetSpecifiedTracks( + const NGGridTrackList& specified_tracks, + wtf_size_t auto_repeat_count, + const NGGridTrackList& implicit_tracks) { + // The implicit track list should have only one repeater, if any. + DCHECK_LE(implicit_tracks.RepeaterCount(), 1u); + specified_tracks_ = specified_tracks; + implicit_tracks_ = implicit_tracks; + auto_repeat_count_ = auto_repeat_count; + + wtf_size_t repeater_count = specified_tracks_.RepeaterCount(); + wtf_size_t total_track_count = 0; + + for (wtf_size_t i = 0; i < repeater_count; ++i) { + wtf_size_t repeater_track_start = total_track_count + 1; + wtf_size_t repeater_track_count = + specified_tracks_.RepeatCount(i, auto_repeat_count_) * + specified_tracks_.RepeatSize(i); + if (repeater_track_count != 0) { + starting_tracks_.push_back(repeater_track_start); + ending_tracks_.push_back(repeater_track_start + repeater_track_count - 1); + } + total_track_count += repeater_track_count; + } +} + +void NGGridBlockTrackCollection::EnsureTrackCoverage(wtf_size_t track_number, + wtf_size_t span_length) { + DCHECK_NE(kInvalidRangeIndex, track_number); + DCHECK_NE(kInvalidRangeIndex, span_length); + track_indices_need_sort_ = true; + starting_tracks_.push_back(track_number); + ending_tracks_.push_back(track_number + span_length - 1); +} + +void NGGridBlockTrackCollection::FinalizeRanges() { + ranges_.clear(); + + // Sort start and ending tracks from low to high. + if (track_indices_need_sort_) { + std::stable_sort(starting_tracks_.begin(), starting_tracks_.end()); + std::stable_sort(ending_tracks_.begin(), ending_tracks_.end()); + } + + wtf_size_t current_range_track_start = 1u; + if (starting_tracks_.size() > 0 && starting_tracks_[0] == 0) + current_range_track_start = 0; + + // Indices into the starting and ending track vectors. + wtf_size_t starting_tracks_index = 0; + wtf_size_t ending_tracks_index = 0; + + wtf_size_t repeater_index = kInvalidRangeIndex; + wtf_size_t repeater_track_start = kInvalidRangeIndex; + wtf_size_t next_repeater_track_start = 1u; + wtf_size_t current_repeater_track_count = 0; + + wtf_size_t total_repeater_count = specified_tracks_.RepeaterCount(); + wtf_size_t open_items_or_repeaters = 0; + bool is_in_auto_fit_range = false; + + while (true) { + // Identify starting tracks index. + while (starting_tracks_index < starting_tracks_.size() && + current_range_track_start >= + starting_tracks_[starting_tracks_index]) { + ++starting_tracks_index; + ++open_items_or_repeaters; + } + + // Identify ending tracks index. + while (ending_tracks_index < ending_tracks_.size() && + current_range_track_start > ending_tracks_[ending_tracks_index]) { + ++ending_tracks_index; + --open_items_or_repeaters; + DCHECK_GE(open_items_or_repeaters, 0u); + } + + // Identify ending tracks index. + if (ending_tracks_index >= ending_tracks_.size()) { + DCHECK_EQ(open_items_or_repeaters, 0u); + break; + } + + // Determine the next starting and ending track index. + wtf_size_t next_starting_track = kInvalidRangeIndex; + if (starting_tracks_index < starting_tracks_.size()) + next_starting_track = starting_tracks_[starting_tracks_index]; + wtf_size_t next_ending_track = ending_tracks_[ending_tracks_index]; + + // Move |next_repeater_track_start| to the start of the next repeater, if + // needed. + while (current_range_track_start == next_repeater_track_start) { + if (++repeater_index == total_repeater_count) { + repeater_index = kInvalidRangeIndex; + repeater_track_start = next_repeater_track_start; + is_in_auto_fit_range = false; + break; + } + + is_in_auto_fit_range = specified_tracks_.RepeatType(repeater_index) == + NGGridTrackRepeater::RepeatType::kAutoFit; + current_repeater_track_count = + specified_tracks_.RepeatCount(repeater_index, auto_repeat_count_) * + specified_tracks_.RepeatSize(repeater_index); + repeater_track_start = next_repeater_track_start; + next_repeater_track_start += current_repeater_track_count; + } + + // Determine track number and count of the range. + Range range; + range.starting_track_number = current_range_track_start; + if (next_starting_track != kInvalidRangeIndex) { + range.track_count = + std::min(next_ending_track + 1u, next_starting_track) - + current_range_track_start; + } else { + range.track_count = next_ending_track + 1u - current_range_track_start; + } + + // Compute repeater index and offset. + if (repeater_index == kInvalidRangeIndex) { + range.is_implicit_range = true; + if (implicit_tracks_.RepeaterCount() == 0) { + // No specified implicit tracks, use auto tracks. + range.repeater_index = kInvalidRangeIndex; + range.repeater_offset = 0; + } else { + // Use implicit tracks. + wtf_size_t implicit_repeat_size = ImplicitRepeatSize(); + range.repeater_index = 0; + if (range.starting_track_number == 0) { + wtf_size_t offset_from_end = + (1 - range.starting_track_number) % implicit_repeat_size; + if (offset_from_end == implicit_repeat_size) { + range.repeater_offset = 0; + } else { + range.repeater_offset = + current_range_track_start - repeater_track_start; + } + } + } + } else { + range.is_implicit_range = false; + range.repeater_index = repeater_index; + range.repeater_offset = current_range_track_start - repeater_track_start; + } + range.is_collapsed = is_in_auto_fit_range && open_items_or_repeaters == 1u; + + current_range_track_start += range.track_count; + ranges_.push_back(range); + } + +#if DCHECK_IS_ON() + while (repeater_index != kInvalidRangeIndex && + repeater_index < total_repeater_count - 1u) { + ++repeater_index; + DCHECK_EQ(0u, specified_tracks_.RepeatSize(repeater_index)); + } +#endif + DCHECK_EQ(starting_tracks_index, starting_tracks_.size()); + DCHECK_EQ(ending_tracks_index, starting_tracks_.size()); + DCHECK(repeater_index == total_repeater_count - 1u || + repeater_index == kInvalidRangeIndex); + starting_tracks_.clear(); + ending_tracks_.clear(); +} + +const NGGridBlockTrackCollection::Range& +NGGridBlockTrackCollection::RangeAtRangeIndex(wtf_size_t range_index) const { + DCHECK_NE(range_index, kInvalidRangeIndex); + DCHECK_LT(range_index, ranges_.size()); + return ranges_[range_index]; +} +const NGGridBlockTrackCollection::Range& +NGGridBlockTrackCollection::RangeAtTrackNumber(wtf_size_t track_number) const { + wtf_size_t range_index = RangeIndexFromTrackNumber(track_number); + DCHECK_NE(range_index, kInvalidRangeIndex); + DCHECK_LT(range_index, ranges_.size()); + return ranges_[range_index]; +} + +String NGGridBlockTrackCollection::ToString() const { + if (ranges_.IsEmpty()) { + StringBuilder builder; + builder.Append("NGGridTrackCollection: [SpecifiedTracks: "); + builder.Append(specified_tracks_.ToString()); + if (HasImplicitTracks()) { + builder.Append("], [ImplicitTracks: "); + builder.Append(implicit_tracks_.ToString()); + } + + builder.Append("], [Starting: {"); + for (wtf_size_t i = 0; i < starting_tracks_.size(); ++i) { + builder.AppendNumber<wtf_size_t>(starting_tracks_[i]); + if (i + 1 != starting_tracks_.size()) + builder.Append(", "); + } + builder.Append("} ], [Ending: {"); + for (wtf_size_t i = 0; i < ending_tracks_.size(); ++i) { + builder.AppendNumber<wtf_size_t>(ending_tracks_[i]); + if (i + 1 != ending_tracks_.size()) + builder.Append(", "); + } + builder.Append("} ] "); + return builder.ToString(); + } else { + return NGGridTrackCollectionBase::ToString(); + } +} +bool NGGridBlockTrackCollection::HasImplicitTracks() const { + return implicit_tracks_.RepeaterCount() != 0; +} +wtf_size_t NGGridBlockTrackCollection::ImplicitRepeatSize() const { + DCHECK(HasImplicitTracks()); + return implicit_tracks_.RepeatSize(0); +} + +wtf_size_t NGGridBlockTrackCollection::RangeTrackNumber( + wtf_size_t range_index) const { + DCHECK_LT(range_index, RangeCount()); + return ranges_[range_index].starting_track_number; +} + +wtf_size_t NGGridBlockTrackCollection::RangeTrackCount( + wtf_size_t range_index) const { + DCHECK_LT(range_index, RangeCount()); + return ranges_[range_index].track_count; +} + +bool NGGridBlockTrackCollection::IsRangeCollapsed( + wtf_size_t range_index) const { + DCHECK_LT(range_index, RangeCount()); + return ranges_[range_index].is_collapsed; +} + +wtf_size_t NGGridBlockTrackCollection::RangeCount() const { + return ranges_.size(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.h b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.h new file mode 100644 index 00000000000..ca66e0190f3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.h @@ -0,0 +1,207 @@ +// Copyright 2020 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_GRID_NG_GRID_TRACK_COLLECTION_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_TRACK_COLLECTION_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// NGGridTrackCollectionBase provides an implementation for some shared +// functionality on track range collections, specifically binary search on +// the collection to get a range index given a track number. +class CORE_EXPORT NGGridTrackCollectionBase { + public: + static constexpr wtf_size_t kInvalidRangeIndex = kNotFound; + static constexpr wtf_size_t kMaxRangeIndex = kNotFound - 1; + + class CORE_EXPORT RangeRepeatIterator { + public: + RangeRepeatIterator(const NGGridTrackCollectionBase* collection, + wtf_size_t range_index); + + // Moves iterator to next range, skipping over repeats in a range. Return + // true if the move was successful. + bool MoveToNextRange(); + wtf_size_t RepeatCount() const; + // Returns the track number for the start of the range. + wtf_size_t RangeTrackStart() const; + // Returns the track number at the end of the range. + wtf_size_t RangeTrackEnd() const; + + bool IsRangeCollapsed() const; + + private: + bool SetRangeIndex(wtf_size_t range_index); + const NGGridTrackCollectionBase* collection_; + wtf_size_t range_index_; + wtf_size_t range_count_; + + // First track number of a range. + wtf_size_t range_track_start_; + // Count of repeated tracks in a range. + wtf_size_t range_track_count_; + }; + + // Gets the range index for the range that contains the given track number. + wtf_size_t RangeIndexFromTrackNumber(wtf_size_t track_number) const; + + String ToString() const; + + protected: + // Returns the first track number of a range. + virtual wtf_size_t RangeTrackNumber(wtf_size_t range_index) const = 0; + + // Returns the number of tracks in a range. + virtual wtf_size_t RangeTrackCount(wtf_size_t range_index) const = 0; + + // Returns true if the range at the given index is collapsed. + virtual bool IsRangeCollapsed(wtf_size_t range_index) const = 0; + + // Returns the number of track ranges in the collection. + virtual wtf_size_t RangeCount() const = 0; +}; + +// Stores tracks related data by compressing repeated tracks into a single node. +struct NGGridTrackRepeater { + enum class RepeatType { kCount, kAutoFill, kAutoFit }; + NGGridTrackRepeater(wtf_size_t track_index, + wtf_size_t repeat_size, + wtf_size_t repeat_count, + RepeatType repeat_type); + String ToString() const; + bool operator==(const NGGridTrackRepeater& rhs) const; + // Index of the first track being repeated. + wtf_size_t track_index; + // Amount of tracks to be repeated. + wtf_size_t repeat_size; + // Amount of times the group of tracks are repeated. + wtf_size_t repeat_count; + // Type of repetition. + RepeatType repeat_type; +}; + +class CORE_EXPORT NGGridTrackList { + public: + NGGridTrackList(); + // Returns the repeat count of the repeater at |index|, or |auto_value| + // if the repeater is auto. + wtf_size_t RepeatCount(wtf_size_t index, wtf_size_t auto_value) const; + // Returns the number of tracks in the repeater at |index|. + wtf_size_t RepeatSize(wtf_size_t index) const; + // Returns the repeat type of the repeater at |index|. + NGGridTrackRepeater::RepeatType RepeatType(wtf_size_t index) const; + // Returns the count of repeaters. + wtf_size_t RepeaterCount() const; + // Returns the total count of all the tracks in this list. + wtf_size_t TotalTrackCount() const; + + // Adds a non-auto repeater. + bool AddRepeater(wtf_size_t track_index, + wtf_size_t track_count, + wtf_size_t repeat_count); + // Adds an auto repeater. + bool AddAutoRepeater(wtf_size_t track_index, + wtf_size_t track_count, + NGGridTrackRepeater::RepeatType repeat_type); + // Returns true if this list contains an auto repeater. + bool HasAutoRepeater(); + + // Clears all data. + void Clear(); + + String ToString() const; + + private: + bool AddRepeater(wtf_size_t track_index, + wtf_size_t track_count, + wtf_size_t repeat_count, + NGGridTrackRepeater::RepeatType repeat_type); + // Returns the amount of tracks available before overflow. + wtf_size_t AvailableTrackCount() const; + +#if DCHECK_IS_ON() + // Helper to check if |track_index| does not cause a gap or overlap with the + // tracks in this list. Ensures |track_index| is equal to 1 + the last track's + // index. + bool IsTrackContiguous(wtf_size_t track_index) const; +#endif + + Vector<NGGridTrackRepeater> repeaters_; + // The index of the automatic repeater, if there is one; |kInvalidRangeIndex| + // otherwise. + wtf_size_t auto_repeater_index_; + // Total count of tracks. + wtf_size_t total_track_count_; +}; + +class CORE_EXPORT NGGridBlockTrackCollection + : public NGGridTrackCollectionBase { + public: + struct Range { + wtf_size_t starting_track_number; + wtf_size_t track_count; + wtf_size_t repeater_index; + wtf_size_t repeater_offset; + bool is_collapsed; + bool is_implicit_range; + }; + + // Sets the specified, implicit tracks, along with a given auto repeat value. + void SetSpecifiedTracks(const NGGridTrackList& specified_tracks, + wtf_size_t auto_repeat_count, + const NGGridTrackList& implicit_tracks); + // Ensures that after FinalizeRanges is called, a range will start at the + // |track_number|, and a range will end at |track_number| + |span_length| + void EnsureTrackCoverage(wtf_size_t track_number, wtf_size_t span_length); + + // Build the collection of ranges based on information provided by + // SetSpecifiedTracks and EnsureTrackCoverage. + void FinalizeRanges(); + // Returns the range at the given range index. + const Range& RangeAtRangeIndex(wtf_size_t range_index) const; + // Returns the range at the given track. + const Range& RangeAtTrackNumber(wtf_size_t track_number) const; + + String ToString() const; + + protected: + // Returns the first track number of a range. + wtf_size_t RangeTrackNumber(wtf_size_t range_index) const override; + + // Returns the number of tracks in a range. + wtf_size_t RangeTrackCount(wtf_size_t range_index) const override; + + // Returns true if the range at |range_index| is collapsed. + bool IsRangeCollapsed(wtf_size_t range_index) const override; + + // Returns the number of track ranges in the collection. + wtf_size_t RangeCount() const override; + + private: + // Returns true if this collection had implicit tracks provided. + bool HasImplicitTracks() const; + // Returns the repeat size of the implicit tracks. + wtf_size_t ImplicitRepeatSize() const; + + bool track_indices_need_sort_ = false; + wtf_size_t auto_repeat_count_ = 0; + + // Stores the specified and implicit tracks specified by SetSpecifiedTracks. + NGGridTrackList specified_tracks_; + NGGridTrackList implicit_tracks_; + + // Starting and ending tracks mark where ranges will start and end. + // Once the ranges have been built in FinalizeRanges, these are cleared. + Vector<wtf_size_t> starting_tracks_; + Vector<wtf_size_t> ending_tracks_; + Vector<Range> ranges_; +}; + +} // namespace blink +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGGridTrackRepeater) + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_GRID_NG_GRID_TRACK_COLLECTION_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection_test.cc new file mode 100644 index 00000000000..e194b80d014 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection_test.cc @@ -0,0 +1,248 @@ +// Copyright 2020 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 <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" + +namespace blink { +namespace { +#define EXPECT_RANGE(expected_start, expected_count, iterator) \ + EXPECT_EQ(expected_count, iterator.RepeatCount()); \ + EXPECT_EQ(expected_start, iterator.RangeTrackStart()); \ + EXPECT_EQ(expected_start + expected_count - 1, iterator.RangeTrackEnd()); \ + EXPECT_FALSE(iterator.IsRangeCollapsed()); +#define EXPECT_COLLAPSED_RANGE(expected_start, expected_count, iterator) \ + EXPECT_EQ(expected_start, iterator.RangeTrackStart()); \ + EXPECT_EQ(expected_count, iterator.RepeatCount()); \ + EXPECT_EQ(expected_start + expected_count - 1, iterator.RangeTrackEnd()); \ + EXPECT_TRUE(iterator.IsRangeCollapsed()); +class NGGridTrackCollectionBaseTest : public NGGridTrackCollectionBase { + public: + struct TestTrackRange { + wtf_size_t track_number; + wtf_size_t track_count; + }; + explicit NGGridTrackCollectionBaseTest( + const std::vector<wtf_size_t>& range_sizes) { + wtf_size_t track_number = 0; + for (wtf_size_t size : range_sizes) { + TestTrackRange range; + range.track_number = track_number; + range.track_count = size; + ranges_.push_back(range); + track_number += size; + } + } + + protected: + wtf_size_t RangeTrackNumber(wtf_size_t range_index) const override { + return ranges_[range_index].track_number; + } + wtf_size_t RangeTrackCount(wtf_size_t range_index) const override { + return ranges_[range_index].track_count; + } + bool IsRangeCollapsed(wtf_size_t range_index) const override { return false; } + + wtf_size_t RangeCount() const override { return ranges_.size(); } + + private: + Vector<TestTrackRange> ranges_; +}; + +using NGGridTrackCollectionTest = NGLayoutTest; + +TEST_F(NGGridTrackCollectionTest, TestRangeIndexFromTrackNumber) { + // Small case. + NGGridTrackCollectionBaseTest track_collection({3, 10u, 5u}); + EXPECT_EQ(0u, track_collection.RangeIndexFromTrackNumber(0u)); + EXPECT_EQ(1u, track_collection.RangeIndexFromTrackNumber(4u)); + EXPECT_EQ(2u, track_collection.RangeIndexFromTrackNumber(15u)); + + // Small case with large repeat count. + track_collection = NGGridTrackCollectionBaseTest({3000000u, 7u, 10u}); + EXPECT_EQ(0u, track_collection.RangeIndexFromTrackNumber(600u)); + EXPECT_EQ(1u, track_collection.RangeIndexFromTrackNumber(3000000u)); + EXPECT_EQ(1u, track_collection.RangeIndexFromTrackNumber(3000004u)); + + // Larger case. + track_collection = NGGridTrackCollectionBaseTest({ + 10u, // 0 - 9 + 10u, // 10 - 19 + 10u, // 20 - 29 + 10u, // 30 - 39 + 20u, // 40 - 59 + 20u, // 60 - 79 + 20u, // 80 - 99 + 100u, // 100 - 199 + }); + EXPECT_EQ(0u, track_collection.RangeIndexFromTrackNumber(0u)); + EXPECT_EQ(3u, track_collection.RangeIndexFromTrackNumber(35u)); + EXPECT_EQ(4u, track_collection.RangeIndexFromTrackNumber(40u)); + EXPECT_EQ(5u, track_collection.RangeIndexFromTrackNumber(79)); + EXPECT_EQ(7u, track_collection.RangeIndexFromTrackNumber(105u)); +} + +TEST_F(NGGridTrackCollectionTest, TestRangeRepeatIteratorMoveNext) { + // [1-3] [4-13] [14 -18] + NGGridTrackCollectionBaseTest track_collection({3u, 10u, 5u}); + EXPECT_EQ(0u, track_collection.RangeIndexFromTrackNumber(0u)); + + NGGridTrackCollectionBaseTest::RangeRepeatIterator iterator(&track_collection, + 0u); + EXPECT_RANGE(0u, 3u, iterator); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(3u, 10u, iterator); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(13u, 5u, iterator); + + EXPECT_FALSE(iterator.MoveToNextRange()); + + NGGridTrackCollectionBaseTest empty_collection({}); + + NGGridTrackCollectionBaseTest::RangeRepeatIterator empty_iterator( + &empty_collection, 0u); + EXPECT_EQ(NGGridTrackCollectionBase::kInvalidRangeIndex, + empty_iterator.RangeTrackStart()); + EXPECT_EQ(NGGridTrackCollectionBase::kInvalidRangeIndex, + empty_iterator.RangeTrackEnd()); + EXPECT_EQ(0u, empty_iterator.RepeatCount()); + EXPECT_FALSE(empty_iterator.MoveToNextRange()); +} + +TEST_F(NGGridTrackCollectionTest, TestNGGridTrackList) { + NGGridTrackList track_list; + ASSERT_EQ(0u, track_list.RepeaterCount()); + EXPECT_FALSE(track_list.HasAutoRepeater()); + + EXPECT_TRUE(track_list.AddRepeater(0, 2, 4)); + ASSERT_EQ(1u, track_list.RepeaterCount()); + EXPECT_EQ(8u, track_list.TotalTrackCount()); + EXPECT_EQ(4u, track_list.RepeatCount(0, 77)); + EXPECT_EQ(2u, track_list.RepeatSize(0)); + EXPECT_FALSE(track_list.HasAutoRepeater()); + + EXPECT_TRUE(track_list.AddAutoRepeater( + 2, 3, NGGridTrackRepeater::RepeatType::kAutoFill)); + ASSERT_EQ(2u, track_list.RepeaterCount()); + EXPECT_EQ(11u, track_list.TotalTrackCount()); + EXPECT_EQ(77u, track_list.RepeatCount(1, 77)); + EXPECT_EQ(3u, track_list.RepeatSize(1)); + EXPECT_TRUE(track_list.HasAutoRepeater()); + + // Can't add more than one auto repeater to a list. + EXPECT_FALSE(track_list.AddAutoRepeater( + 5, 3, NGGridTrackRepeater::RepeatType::kAutoFill)); + + EXPECT_TRUE(track_list.AddRepeater( + 5, NGGridTrackCollectionBase::kMaxRangeIndex - 20, 1)); + ASSERT_EQ(3u, track_list.RepeaterCount()); + EXPECT_EQ(NGGridTrackCollectionBase::kMaxRangeIndex - 9, + track_list.TotalTrackCount()); + EXPECT_EQ(1u, track_list.RepeatCount(2, 77)); + EXPECT_EQ(NGGridTrackCollectionBase::kMaxRangeIndex - 20, + track_list.RepeatSize(2)); + + // Try to add a repeater that would overflow the total track count. + EXPECT_FALSE(track_list.AddRepeater( + NGGridTrackCollectionBase::kMaxRangeIndex - 15u, 3, 10)); + ASSERT_EQ(3u, track_list.RepeaterCount()); + + // Try to add a repeater that would overflow the track size in a repeater. + EXPECT_FALSE( + track_list.AddRepeater(NGGridTrackCollectionBase::kMaxRangeIndex - 15u, + NGGridTrackCollectionBase::kMaxRangeIndex, 10)); + ASSERT_EQ(3u, track_list.RepeaterCount()); +} + +TEST_F(NGGridTrackCollectionTest, TestNGGridBlockTrackCollection) { + NGGridTrackList specified_tracks; + ASSERT_TRUE(specified_tracks.AddRepeater(1, 2, 4)); + ASSERT_TRUE(specified_tracks.AddAutoRepeater( + 3, 3, NGGridTrackRepeater::RepeatType::kAutoFill)); + ASSERT_EQ(2u, specified_tracks.RepeaterCount()); + NGGridBlockTrackCollection block_collection; + block_collection.SetSpecifiedTracks(specified_tracks, 3, NGGridTrackList()); + block_collection.FinalizeRanges(); + + NGGridTrackCollectionBase::RangeRepeatIterator iterator(&block_collection, + 0u); + EXPECT_RANGE(1u, 8u, iterator); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(9u, 9u, iterator); + + EXPECT_FALSE(iterator.MoveToNextRange()); +} + +TEST_F(NGGridTrackCollectionTest, TestNGGridBlockTrackCollectionCollapsed) { + NGGridTrackList specified_tracks; + ASSERT_TRUE(specified_tracks.AddRepeater(1, 2, 4)); + ASSERT_TRUE(specified_tracks.AddAutoRepeater( + 3, 3, NGGridTrackRepeater::RepeatType::kAutoFit)); + ASSERT_TRUE(specified_tracks.AddRepeater(6, 3, 7)); + ASSERT_EQ(3u, specified_tracks.RepeaterCount()); + NGGridBlockTrackCollection block_collection; + block_collection.SetSpecifiedTracks(specified_tracks, 3, NGGridTrackList()); + block_collection.FinalizeRanges(); + + NGGridTrackCollectionBase::RangeRepeatIterator iterator(&block_collection, + 0u); + EXPECT_RANGE(1u, 8u, iterator); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_COLLAPSED_RANGE(9u, 9u, iterator); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(18u, 21u, iterator); + + EXPECT_FALSE(iterator.MoveToNextRange()); +} + +TEST_F(NGGridTrackCollectionTest, TestNGGridBlockTrackCollectionImplicit) { + NGGridTrackList specified_tracks; + ASSERT_TRUE(specified_tracks.AddRepeater(1, 2, 4)); + ASSERT_TRUE(specified_tracks.AddRepeater(3, 3, 3)); + ASSERT_TRUE(specified_tracks.AddRepeater(6, 3, 7)); + ASSERT_EQ(3u, specified_tracks.RepeaterCount()); + + NGGridTrackList implicit_tracks; + ASSERT_TRUE(implicit_tracks.AddRepeater(1, 8, 2)); + + NGGridBlockTrackCollection block_collection; + block_collection.SetSpecifiedTracks(specified_tracks, 3, implicit_tracks); + block_collection.EnsureTrackCoverage(3, 40); + block_collection.EnsureTrackCoverage(3, 40); + block_collection.FinalizeRanges(); + NGGridTrackCollectionBase::RangeRepeatIterator iterator(&block_collection, + 0u); + EXPECT_RANGE(1u, 2u, iterator); + EXPECT_FALSE(block_collection.RangeAtTrackNumber(1u).is_implicit_range); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(3u, 6u, iterator); + EXPECT_FALSE(block_collection.RangeAtTrackNumber(4).is_implicit_range); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(9u, 9u, iterator); + EXPECT_FALSE(block_collection.RangeAtTrackNumber(7).is_implicit_range); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_RANGE(18u, 21u, iterator); + EXPECT_FALSE(block_collection.RangeAtTrackNumber(20).is_implicit_range); + + EXPECT_TRUE(iterator.MoveToNextRange()); + EXPECT_TRUE(block_collection.RangeAtTrackNumber(40).is_implicit_range); + EXPECT_RANGE(39u, 4u, iterator); + + EXPECT_FALSE(iterator.MoveToNextRange()); +} + +} // namespace + +} // namespace blink 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 0ddc51ec99d..6c6a762b856 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 @@ -62,11 +62,12 @@ as in the following. Inline layout is performed in the following phases: -1. **Pre-layout** converts LayoutObject tree to a concatenated string +1. **[Pre-layout]** converts LayoutObject tree to a concatenated string and a list of [NGInlineItem]. -2. **Line breaking** breaks it into lines and +2. **[Line breaking]** breaks it into lines and produces a list of [NGInlineItemResult] for each line. -3. **Line box construction** produces a fragment tree. +3. **[Line box construction]** orders and positions items on a line. +4. **[Generate fragments]** generates physical fragments. This is similar to [CSS Text Processing Order of Operations], but not exactly the same, @@ -74,8 +75,8 @@ because the spec prioritizes the simple description than being accurate. [CSS Text Processing Order of Operations]: https://drafts.csswg.org/css-text-3/#order -### Pre-layout ### -[Pre-layout]: #pre-layout +### <a name="pre-layout">Pre-layout</a> ### +[pre-layout]: #pre-layout For inline layout there is a pre-layout pass that prepares the internal data structures needed to perform line layout. @@ -104,7 +105,8 @@ three separate steps or stages that are executed in order: [text-transform]: https://drafts.csswg.org/css-text-3/#propdef-text-transform -### Line Breaking ### +### <a name="line-breaking">Line Breaking</a> ### +[line breaking]: #line-breaking [NGLineBreaker] takes a list of [NGInlineItem], measure them, break into lines, and @@ -134,53 +136,51 @@ This phase: [CSS Calculating widths and margins]: https://drafts.csswg.org/css2/visudet.html#Computing_widths_and_margins -### Line Box Construction ### +### <a name="create-line">Line Box Construction</a> ### +[line Box Construction]: #create-line `NGInlineLayoutAlgorithm::CreateLine()` takes a list of [NGInlineItemResult] and -produces [NGPhysicalLineBoxFragment] for each line. - -Lines are then wrapped in an anonymous [NGPhysicalBoxFragment] -so that one [NGInlineNode] has one corresponding fragment. +produces a list of [NGLogicalLineItem]. This phase consists of following sub-phases: -1. Bidirectional reordering: - Reorder the list of [NGInlineItemResult] +1. Create a [NGLogicalLineItem] for each [NGInlineItemResult] + and determine the positions. + + The inline size of each item was already determined by [NGLineBreaker], + but the inline position is recomputed + because [BiDi reordering](#bidi) may change them. + + In block direction, + [NGLogicalLineItem] is placed as if the baseline is at 0. + This is adjusted later, possibly multiple times, + for [vertical-align] and the block offset of the parent inline box. + + An open-tag item pushes a new stack entry of [NGInlineBoxState], + and a close-tag item pops a stack entry. + This stack is used to determine the size of the inline box, + for [vertical-align], and for a few other purposes. + Please see [Inline Box Tree] below. + +2. Process all pending operations in [Inline Box Tree]. +3. [Bidirectional reordering](#bidi): + Reorder the list of [NGLogicalLineItem] according to [UAX#9 Reordering Resolved Levels]. See [Bidirectional text] below. - After this point forward, the list of [NGInlineItemResult] is + After this point forward, the list of [NGLogicalLineItem] is in _visual order_; which is from [line-left] to [line-right]. The block direction is still logical, but the inline direction is physical. +4. Applies [ellipsizing] if needed. +5. Applies the CSS [text-align] property. +6. Moves the baseline to the correct position + based on the height of the line box. -2. Create a [NGPhysicalFragment] for each [NGInlineItemResult] - in visual ([line-left] to [line-right]) order, - and place them into [NGPhysicalLineBoxFragment]. - - 1. A text item produces a [NGPhysicalTextFragment]. - 2. An open-tag item pushes a new stack entry of [NGInlineBoxState], - and a close-tag item pops a stack entry. - Performs operations that require the size of the inline box, - or ancestor boxes. - See [Inline Box Tree] below. - - The inline size of each item was already determined by [NGLineBreaker], - but the inline position is recomputed - because BiDi reordering may have changed it. - - In block direction, [NGPhysicalFragment] is placed - as if the baseline is at 0. - This is adjusted later, possibly multiple times. - See [Inline Box Tree] and the post-process below. - -3. Post-process the constructed line box. - This includes: - 1. Process all pending operations in [Inline Box Tree]. - 2. Moves the baseline to the correct position - based on the height of the line box. - 3. Applies the CSS [text-align] property. +Note: There is [a discussion](https://docs.google.com/document/d/1dxzIHl1dwBtgeKgWd2cKcog8AyydN5rduQvXthMOMD0/edit?usp=sharing) +to merge [NGInlineItemResult] and [NGLogicalLineItem], but this hasn't been done yet. +[ellipsizing]: https://drafts.csswg.org/css-ui-3/#overflow-ellipsis [line-left]: https://drafts.csswg.org/css-writing-modes-3/#line-left [line-right]: https://drafts.csswg.org/css-writing-modes-3/#line-right [text-align]: https://drafts.csswg.org/css-text-3/#propdef-text-align @@ -240,6 +240,20 @@ Once all children and their positions and sizes are finalized, `NGInlineLayoutStateStack::CreateBoxFragments()` creates [NGPhysicalBoxFragment] and add children to it. +### <a name="generate-fragments">Generate Fragments</a> ### +[generate fragments]: #generate-fragments + +When all [NGLogicalLineItem]s are ordered and positioned, +they are converted to fragments. + +Without [NGFragmentItem] enabled, +each [NGLogicalLineItem] produces a [NGPhysicalFragment], +added to the [NGPhysicalLineBoxFragment]. + +With [NGFragmentItem] enabled, +each [NGLogicalLineItem] produces a [NGFragmentItem], +added to the [NGFragmentItems] in the containing block of the inline formatting context. + ## Miscellaneous topics ## ### Baseline ### @@ -355,6 +369,8 @@ positions in the context. See [design doc](https://goo.gl/CJbxky) for details. [NGBoxFragmentBuilder]: ../ng_box_fragment_builder.h [NGConstraintSpace]: ../ng_constraint_space_builder.h [NGConstraintSpaceBuilder]: ../ng_constraint_space_builder.h +[NGFragmentItem]: ng_fragment_item.h +[NGFragmentItems]: ng_fragment_items.h [NGInlineBoxState]: ng_inline_box_state.h [NGInlineItem]: ng_inline_item.h [NGInlineItemResult]: ng_inline_item_result.h @@ -362,6 +378,8 @@ positions in the context. See [design doc](https://goo.gl/CJbxky) for details. [NGInlineLayoutAlgorithm]: ng_inline_layout_algorithm.h [NGLayoutInputNode]: ../ng_layout_input_node.h [NGLineBreaker]: ng_line_breaker.h +[NGLogicalLineItem]: ng_logical_line_item.h +[NGLogicalLineItems]: ng_logical_line_items.h [NGOffsetMapping]: ng_offset_mapping.h [NGPhysicalBoxFragment]: ../ng_physical_box_fragment.h [NGPhysicalFragment]: ../ng_physical_fragment.h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc index da28e31bfcf..ed5a6e062c9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc @@ -142,6 +142,23 @@ TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteCollapseWhiteSpaceEnd) { GetItemsAsString(*text.GetLayoutObject())); } +// web_tests/external/wpt/editing/run/delete.html?993-993 +// web_tests/external/wpt/editing/run/forwarddelete.html?1193-1193 +TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteNbspInPreWrap) { + if (!RuntimeEnabledFeatures::LayoutNGEnabled()) + return; + + InsertStyleElement("#target { white-space:pre-wrap; }"); + SetBodyInnerHTML(u"<p id=target> abc</p>"); + Text& text = To<Text>(*GetElementById("target")->firstChild()); + text.deleteData(0, 1, ASSERT_NO_EXCEPTION); + + EXPECT_EQ( + "*{' ', ShapeResult=0+1}\n" + "*{'abc', ShapeResult=2+3}\n", + GetItemsAsString(*text.GetLayoutObject())); +} + TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteRTL) { if (!RuntimeEnabledFeatures::LayoutNGEnabled()) return; @@ -174,6 +191,38 @@ TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteRTL2) { GetItemsAsString(*text.GetLayoutObject())); } +// editing/deleting/delete_ws_fixup.html +TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteThenNonCollapse) { + if (!RuntimeEnabledFeatures::LayoutNGEnabled()) + return; + + SetBodyInnerHTML(u"<div id=target>abc def<b> </b>ghi</div>"); + Text& text = To<Text>(*GetElementById("target")->firstChild()); + text.deleteData(4, 3, ASSERT_NO_EXCEPTION); // remove "def" + + EXPECT_EQ( + "*{'abc ', ShapeResult=0+4}\n" + "{''}\n" + "{'ghi', ShapeResult=4+3}\n", + GetItemsAsString(*text.GetLayoutObject())); +} + +// editing/deleting/delete_ws_fixup.html +TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteThenNonCollapse2) { + if (!RuntimeEnabledFeatures::LayoutNGEnabled()) + return; + + SetBodyInnerHTML(u"<div id=target>abc def<b> X </b>ghi</div>"); + Text& text = To<Text>(*GetElementById("target")->firstChild()); + text.deleteData(4, 3, ASSERT_NO_EXCEPTION); // remove "def" + + EXPECT_EQ( + "*{'abc ', ShapeResult=0+4}\n" + "{'X ', ShapeResult=4+2}\n" + "{'ghi', ShapeResult=6+3}\n", + GetItemsAsString(*text.GetLayoutObject())); +} + // http://crbug.com/1039143 TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteWithBidiControl) { if (!RuntimeEnabledFeatures::LayoutNGEnabled()) diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.cc index 4098a6363f7..08a2a61198f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.cc @@ -15,17 +15,23 @@ NGBidiParagraph::~NGBidiParagraph() { bool NGBidiParagraph::SetParagraph(const String& text, const ComputedStyle& block_style) { + if (UNLIKELY(block_style.GetUnicodeBidi() == UnicodeBidi::kPlaintext)) + return SetParagraph(text, base::nullopt); + return SetParagraph(text, block_style.Direction()); +} + +bool NGBidiParagraph::SetParagraph( + const String& text, + base::Optional<TextDirection> base_direction) { DCHECK(!ubidi_); ubidi_ = ubidi_open(); - bool use_heuristic_base_direction = - block_style.GetUnicodeBidi() == UnicodeBidi::kPlaintext; UBiDiLevel para_level; - if (use_heuristic_base_direction) { - para_level = UBIDI_DEFAULT_LTR; - } else { - base_direction_ = block_style.Direction(); + if (base_direction) { + base_direction_ = *base_direction; para_level = IsLtr(base_direction_) ? UBIDI_LTR : UBIDI_RTL; + } else { + para_level = UBIDI_DEFAULT_LTR; } ICUError error; @@ -38,7 +44,7 @@ bool NGBidiParagraph::SetParagraph(const String& text, return false; } - if (use_heuristic_base_direction) + if (!base_direction) base_direction_ = DirectionFromLevel(ubidi_getParaLevel(ubidi_)); return true; @@ -60,6 +66,17 @@ unsigned NGBidiParagraph::GetLogicalRun(unsigned start, return end; } +void NGBidiParagraph::GetLogicalRuns(const String& text, Runs* runs) const { + DCHECK(runs->IsEmpty()); + for (unsigned start = 0; start < text.length();) { + UBiDiLevel level; + unsigned end = GetLogicalRun(start, &level); + DCHECK_GT(end, start); + runs->emplace_back(start, end, level); + start = end; + } +} + void NGBidiParagraph::IndicesInVisualOrder( const Vector<UBiDiLevel, 32>& levels, Vector<int32_t, 32>* indices_in_visual_order_out) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h index dc53a487dfa..fc40c73b1df 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h @@ -5,6 +5,8 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_BIDI_PARAGRAPH_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_BIDI_PARAGRAPH_H_ +#include "base/optional.h" +#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/platform/text/text_direction.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" #include "third_party/blink/renderer/platform/wtf/forward.h" @@ -23,7 +25,7 @@ class ComputedStyle; // UAX#9 and create logical runs. // http://unicode.org/reports/tr9/ // It can also create visual runs once lines breaks are determined. -class NGBidiParagraph { +class CORE_EXPORT NGBidiParagraph { STACK_ALLOCATED(); public: @@ -35,6 +37,8 @@ class NGBidiParagraph { // Returns false on failure. Nothing other than the destructor should be // called. bool SetParagraph(const String&, const ComputedStyle&); + bool SetParagraph(const String&, + base::Optional<TextDirection> base_direction); // @return the entire text is unidirectional. bool IsUnidirectional() const { @@ -52,6 +56,30 @@ class NGBidiParagraph { // http://unicode.org/reports/tr9/#The_Paragraph_Level static TextDirection BaseDirectionForString(const StringView&); + struct Run { + Run(unsigned start, unsigned end, UBiDiLevel level) + : start(start), end(end), level(level) { + DCHECK_GT(end, start); + } + + unsigned Length() const { return end - start; } + TextDirection Direction() const { return DirectionFromLevel(level); } + + bool operator==(const Run& other) const { + return start == other.start && end == other.end && level == other.level; + } + + unsigned start; + unsigned end; + UBiDiLevel level; + }; + using Runs = Vector<Run, 32>; + + // Get a list of |Run| in the logical order (before bidi reorder.) + // |text| must be the same one as |SetParagraph|. + // This is higher-level API for |GetLogicalRun|. + void GetLogicalRuns(const String& text, Runs* runs) const; + // Returns the end offset of a logical run that starts from the |start| // offset. unsigned GetLogicalRun(unsigned start, UBiDiLevel*) const; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph_test.cc new file mode 100644 index 00000000000..15e6591055e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph_test.cc @@ -0,0 +1,40 @@ +// Copyright 2020 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_bidi_paragraph.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using testing::ElementsAre; + +TEST(NGBidiParagraph, SetParagraphHeuristicLtr) { + String text(u"abc"); + NGBidiParagraph bidi; + bidi.SetParagraph(text, base::nullopt); + EXPECT_EQ(bidi.BaseDirection(), TextDirection::kLtr); +} + +TEST(NGBidiParagraph, SetParagraphHeuristicRtl) { + String text(u"\u05D0\u05D1\u05D2"); + NGBidiParagraph bidi; + bidi.SetParagraph(text, base::nullopt); + EXPECT_EQ(bidi.BaseDirection(), TextDirection::kRtl); +} + +TEST(NGBidiParagraph, GetLogicalRuns) { + String text(u"\u05D0\u05D1\u05D2 abc \u05D3\u05D4\u05D5"); + NGBidiParagraph bidi; + bidi.SetParagraph(text, TextDirection::kRtl); + NGBidiParagraph::Runs runs; + bidi.GetLogicalRuns(text, &runs); + EXPECT_THAT(runs, ElementsAre(NGBidiParagraph::Run(0, 4, 1), + NGBidiParagraph::Run(4, 7, 2), + NGBidiParagraph::Run(7, 11, 1))); +} + +} // 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 d5075dba7d6..5867968d822 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 @@ -299,7 +299,12 @@ NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) { return NGCaretPosition(); const NGOffsetMapping* mapping = NGInlineNode::GetOffsetMapping(context); - DCHECK(mapping); + if (!mapping) { + // TODO(yosin): We should find when we reach here[1]. + // [1] http://crbug.com/1100481 + NOTREACHED() << context; + return NGCaretPosition(); + } const base::Optional<unsigned> maybe_offset = mapping->GetTextContentOffset(position.GetPosition()); if (!maybe_offset.has_value()) { @@ -322,17 +327,17 @@ PositionWithAffinity NGCaretPosition::ToPositionInDOMTreeWithAffinity() const { return PositionWithAffinity(); switch (position_type) { case NGCaretPositionType::kBeforeBox: - if (cursor.Current().GetNode()) - return PositionWithAffinity(); - return PositionWithAffinity( - Position::BeforeNode(*cursor.Current().GetNode()), - TextAffinity::kDownstream); + if (const Node* node = cursor.Current().GetNode()) { + return PositionWithAffinity(Position::BeforeNode(*node), + TextAffinity::kDownstream); + } + return PositionWithAffinity(); case NGCaretPositionType::kAfterBox: - if (cursor.Current().GetNode()) - return PositionWithAffinity(); - return PositionWithAffinity( - Position::AfterNode(*cursor.Current().GetNode()), - TextAffinity::kUpstreamIfPossible); + if (const Node* node = cursor.Current().GetNode()) { + return PositionWithAffinity(Position::AfterNode(*node), + TextAffinity::kUpstreamIfPossible); + } + return PositionWithAffinity(); case NGCaretPositionType::kAtTextOffset: // In case of ::first-letter, |cursor.Current().GetNode()| is null. DCHECK(text_offset.has_value()); 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 bccd83f3551..6bef0e6f0f6 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 @@ -52,6 +52,26 @@ PhysicalRect ComputeLocalCaretRectByBoxSide(const NGInlineCursor& cursor, return PhysicalRect(caret_location, caret_size); } +bool ShouldAlignCaretRight(ETextAlign text_align, TextDirection direction) { + switch (text_align) { + case ETextAlign::kRight: + case ETextAlign::kWebkitRight: + return true; + case ETextAlign::kLeft: + case ETextAlign::kWebkitLeft: + case ETextAlign::kCenter: + case ETextAlign::kWebkitCenter: + return false; + case ETextAlign::kJustify: + case ETextAlign::kStart: + return IsRtl(direction); + case ETextAlign::kEnd: + return IsLtr(direction); + } + NOTREACHED(); + return false; +} + PhysicalRect ComputeLocalCaretRectAtTextOffset(const NGInlineCursor& cursor, unsigned offset) { DCHECK(cursor.Current().IsText()); @@ -62,7 +82,8 @@ PhysicalRect ComputeLocalCaretRectAtTextOffset(const NGInlineCursor& cursor, cursor.Current().GetLayoutObject()->GetDocument().View(); LayoutUnit caret_width = frame_view->CaretWidth(); - const bool is_horizontal = cursor.Current().Style().IsHorizontalWritingMode(); + const ComputedStyle& style = cursor.Current().Style(); + const bool is_horizontal = style.IsHorizontalWritingMode(); LayoutUnit caret_height = is_horizontal ? cursor.Current().Size().height : cursor.Current().Size().width; @@ -90,15 +111,33 @@ PhysicalRect ComputeLocalCaretRectAtTextOffset(const NGInlineCursor& cursor, line_box.Current().OffsetInContainerBlock(); const PhysicalRect line_box_rect(line_box_offset, line_box.Current().Size()); + const NGInlineBreakToken& break_token = + *line_box.Current().InlineBreakToken(); + const bool is_last_line = + break_token.IsFinished() || break_token.IsForcedBreak(); + const ComputedStyle& block_style = fragmentainer.Style(); + bool should_align_caret_right = + ShouldAlignCaretRight(block_style.GetTextAlign(is_last_line), + block_style.Direction()) && + (style.GetUnicodeBidi() != UnicodeBidi::kPlaintext || + IsLtr(cursor.Current().ResolvedDirection())); + // 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 // then round it to the nearest pixel. if (is_horizontal) { - const LayoutUnit min_x = std::min(LayoutUnit(), line_box_offset.left); - caret_location.left = std::max(caret_location.left, min_x); - const LayoutUnit max_x = - std::max(fragmentainer.Size().width, line_box_rect.Right()); - caret_location.left = std::min(caret_location.left, max_x - caret_width); + if (should_align_caret_right) { + const LayoutUnit left_edge = std::min(LayoutUnit(), line_box_rect.X()); + caret_location.left = std::max(caret_location.left, left_edge); + caret_location.left = + std::min(caret_location.left, line_box_rect.Right() - caret_width); + } else { + const LayoutUnit right_edge = + std::max(fragmentainer.Size().width, line_box_rect.Right()); + caret_location.left = + std::min(caret_location.left, right_edge - caret_width); + caret_location.left = std::max(caret_location.left, line_box_rect.X()); + } caret_location.left = LayoutUnit(caret_location.left.Round()); return PhysicalRect(caret_location, caret_size); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc index 4aedb519e64..56af2e5c74d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc @@ -15,6 +15,23 @@ namespace blink { +namespace { + +struct SameSizeAsNGFragmentItem { + struct { + void* pointer; + NGTextOffset text_offset; + } type_data; + PhysicalRect rect; + void* pointers[2]; + wtf_size_t sizes[2]; + unsigned flags; +}; + +static_assert(sizeof(NGFragmentItem) == sizeof(SameSizeAsNGFragmentItem), + "NGFragmentItem should stay small"); +} // namespace + NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) : layout_object_(text.GetLayoutObject()), text_({text.TextShapeResult(), text.TextOffset()}), @@ -26,7 +43,6 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) text_direction_(static_cast<unsigned>(text.ResolvedDirection())), ink_overflow_computed_(false), is_dirty_(false), - is_first_for_node_(true), is_last_for_node_(true) { #if DCHECK_IS_ON() if (text_.shape_result) { @@ -44,19 +60,22 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) DCHECK(!IsFormattingContextRoot()); } -NGFragmentItem::NGFragmentItem(NGInlineItemResult&& item_result, - const PhysicalSize& size) - : layout_object_(item_result.item->GetLayoutObject()), - text_({std::move(item_result.shape_result), item_result.TextOffset()}), +NGFragmentItem::NGFragmentItem( + const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const NGTextOffset& text_offset, + const PhysicalSize& size, + bool is_hidden_for_paint) + : layout_object_(inline_item.GetLayoutObject()), + text_({std::move(shape_result), text_offset}), rect_({PhysicalOffset(), size}), type_(kText), - sub_type_(static_cast<unsigned>(item_result.item->TextType())), - style_variant_(static_cast<unsigned>(item_result.item->StyleVariant())), - is_hidden_for_paint_(false), // TODO(kojii): not supported yet. - text_direction_(static_cast<unsigned>(item_result.item->Direction())), + sub_type_(static_cast<unsigned>(inline_item.TextType())), + style_variant_(static_cast<unsigned>(inline_item.StyleVariant())), + is_hidden_for_paint_(is_hidden_for_paint), + text_direction_(static_cast<unsigned>(inline_item.Direction())), ink_overflow_computed_(false), is_dirty_(false), - is_first_for_node_(true), is_last_for_node_(true) { #if DCHECK_IS_ON() if (text_.shape_result) { @@ -64,15 +83,40 @@ NGFragmentItem::NGFragmentItem(NGInlineItemResult&& item_result, DCHECK_EQ(text_.shape_result->EndIndex(), EndOffset()); } #endif - // TODO(kojii): Generated text not supported yet. DCHECK_NE(TextType(), NGTextType::kLayoutGenerated); DCHECK(!IsFormattingContextRoot()); } -NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line, - wtf_size_t item_count) +NGFragmentItem::NGFragmentItem( + const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const String& text_content, + const PhysicalSize& size, + bool is_hidden_for_paint) + : layout_object_(inline_item.GetLayoutObject()), + generated_text_({std::move(shape_result), text_content}), + rect_({PhysicalOffset(), size}), + type_(kGeneratedText), + sub_type_(static_cast<unsigned>(inline_item.TextType())), + style_variant_(static_cast<unsigned>(inline_item.StyleVariant())), + is_hidden_for_paint_(is_hidden_for_paint), + text_direction_(static_cast<unsigned>(inline_item.Direction())), + ink_overflow_computed_(false), + is_dirty_(false), + is_last_for_node_(true) { +#if DCHECK_IS_ON() + if (text_.shape_result) { + DCHECK_EQ(text_.shape_result->StartIndex(), StartOffset()); + DCHECK_EQ(text_.shape_result->EndIndex(), EndOffset()); + } +#endif + DCHECK_EQ(TextType(), NGTextType::kLayoutGenerated); + DCHECK(!IsFormattingContextRoot()); +} + +NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line) : layout_object_(line.ContainerLayoutObject()), - line_({&line, item_count}), + line_({&line, /* descendants_count */ 1}), rect_({PhysicalOffset(), line.Size()}), type_(kLine), sub_type_(static_cast<unsigned>(line.LineBoxType())), @@ -81,7 +125,6 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line, text_direction_(static_cast<unsigned>(line.BaseDirection())), ink_overflow_computed_(false), is_dirty_(false), - is_first_for_node_(true), is_last_for_node_(true) { DCHECK(!IsFormattingContextRoot()); } @@ -89,7 +132,7 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line, NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box, TextDirection resolved_direction) : layout_object_(box.GetLayoutObject()), - box_({&box, 1}), + box_({&box, /* descendants_count */ 1}), rect_({PhysicalOffset(), box.Size()}), type_(kBox), style_variant_(static_cast<unsigned>(box.StyleVariant())), @@ -97,65 +140,83 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box, text_direction_(static_cast<unsigned>(resolved_direction)), ink_overflow_computed_(false), is_dirty_(false), - is_first_for_node_(true), is_last_for_node_(true) { DCHECK_EQ(IsFormattingContextRoot(), box.IsFormattingContextRoot()); } -NGFragmentItem::NGFragmentItem(const NGInlineItem& inline_item, - const PhysicalSize& size) - : layout_object_(inline_item.GetLayoutObject()), - box_({nullptr, 1}), - rect_({PhysicalOffset(), size}), - type_(kBox), - style_variant_(static_cast<unsigned>(inline_item.StyleVariant())), - is_hidden_for_paint_(false), - text_direction_(static_cast<unsigned>(TextDirection::kLtr)), - ink_overflow_computed_(false), - is_dirty_(false), - is_first_for_node_(true), - is_last_for_node_(true) { - DCHECK_EQ(inline_item.Type(), NGInlineItem::kOpenTag); - DCHECK(layout_object_); - DCHECK(layout_object_->IsLayoutInline()); - DCHECK(!IsFormattingContextRoot()); -} +NGFragmentItem::NGFragmentItem(NGLogicalLineItem&& line_item, + WritingMode writing_mode) { + DCHECK(line_item.CanCreateFragmentItem()); -// static -void NGFragmentItem::Create(NGLineBoxFragmentBuilder::ChildList* child_list, - const String& text_content, - WritingMode writing_mode) { - for (auto& child : *child_list) { - DCHECK(!child.fragment_item); + if (line_item.fragment) { + new (this) NGFragmentItem(*line_item.fragment); + return; + } - if (child.fragment) { - child.fragment_item = base::AdoptRef(new NGFragmentItem(*child.fragment)); - continue; + if (line_item.inline_item) { + if (UNLIKELY(line_item.text_content)) { + new (this) NGFragmentItem( + *line_item.inline_item, std::move(line_item.shape_result), + line_item.text_content, + ToPhysicalSize(line_item.MarginSize(), writing_mode), + line_item.is_hidden_for_paint); + return; } - if (child.item_result) { - child.fragment_item = base::AdoptRef(new NGFragmentItem( - std::move(*child.item_result), - ToPhysicalSize({child.inline_size, child.rect.size.block_size}, - writing_mode))); - continue; - } + new (this) + NGFragmentItem(*line_item.inline_item, + std::move(line_item.shape_result), line_item.text_offset, + ToPhysicalSize(line_item.MarginSize(), writing_mode), + line_item.is_hidden_for_paint); + return; + } - if (child.layout_result) { - const NGPhysicalBoxFragment& fragment = - To<NGPhysicalBoxFragment>(child.layout_result->PhysicalFragment()); - child.fragment_item = base::AdoptRef( - new NGFragmentItem(fragment, child.ResolvedDirection())); - continue; - } + if (line_item.layout_result) { + const NGPhysicalBoxFragment& box_fragment = + To<NGPhysicalBoxFragment>(line_item.layout_result->PhysicalFragment()); + new (this) NGFragmentItem(box_fragment, line_item.ResolvedDirection()); + return; + } - if (child.inline_item) { - child.fragment_item = base::AdoptRef(new NGFragmentItem( - *child.inline_item, - ToPhysicalSize(child.rect.size, - child.inline_item->Style()->GetWritingMode()))); - } + // CanCreateFragmentItem() + NOTREACHED(); + CHECK(false); +} + +NGFragmentItem::NGFragmentItem(const NGFragmentItem& source) + : layout_object_(source.layout_object_), + rect_(source.rect_), + fragment_id_(source.fragment_id_), + delta_to_next_for_same_layout_object_( + source.delta_to_next_for_same_layout_object_), + type_(source.type_), + sub_type_(source.sub_type_), + style_variant_(source.style_variant_), + is_hidden_for_paint_(source.is_hidden_for_paint_), + text_direction_(source.text_direction_), + ink_overflow_computed_(source.ink_overflow_computed_), + is_dirty_(source.is_dirty_), + is_last_for_node_(source.is_last_for_node_) { + switch (Type()) { + case kText: + new (&text_) TextItem(source.text_); + break; + case kGeneratedText: + new (&generated_text_) GeneratedTextItem(source.generated_text_); + break; + case kLine: + new (&line_) LineItem(source.line_); + break; + case kBox: + new (&box_) BoxItem(source.box_); + break; } + + // Copy |ink_overflow_| only for text items, because ink overflow for other + // items may be chnaged even in simplified layout or when reusing lines, and + // that they need to be re-computed anyway. + if (source.ink_overflow_ && ink_overflow_computed_ && IsText()) + ink_overflow_ = std::make_unique<NGInkOverflow>(*source.ink_overflow_); } NGFragmentItem::~NGFragmentItem() { @@ -179,8 +240,7 @@ bool NGFragmentItem::IsInlineBox() const { if (Type() == kBox) { if (const NGPhysicalBoxFragment* box = BoxFragment()) return box->IsInlineBox(); - DCHECK(GetLayoutObject()->IsLayoutInline()); - return true; + NOTREACHED(); } return false; } @@ -336,7 +396,7 @@ TextDirection NGFragmentItem::ResolvedDirection() const { return static_cast<TextDirection>(text_direction_); } -String NGFragmentItem::DebugName() const { +String NGFragmentItem::ToString() const { // TODO(yosin): Once |NGPaintFragment| is removed, we should get rid of // following if-statements. // For ease of rebasing, we use same |DebugName()| as |NGPaintFrgment|. @@ -368,20 +428,6 @@ String NGFragmentItem::DebugName() const { return "NGFragmentItem"; } -IntRect NGFragmentItem::VisualRect() const { - // TODO(kojii): Need to reconsider the storage of |VisualRect|, to integrate - // better with |FragmentData| and to avoid dependency to |LayoutObject|. - DCHECK(GetLayoutObject()); - return GetLayoutObject()->VisualRectForInlineBox(); -} - -IntRect NGFragmentItem::PartialInvalidationVisualRect() const { - // TODO(yosin): Need to reconsider the storage of |VisualRect|, to integrate - // better with |FragmentData| and to avoid dependency to |LayoutObject|. - DCHECK(GetLayoutObject()); - return GetLayoutObject()->PartialInvalidationVisualRectForInlineBox(); -} - PhysicalRect NGFragmentItem::LocalVisualRectFor( const LayoutObject& layout_object) { DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); @@ -427,6 +473,14 @@ void NGFragmentItem::RecalcInkOverflow( PhysicalRect* self_and_contents_rect_out) { DCHECK_EQ(this, cursor->CurrentItem()); + if (UNLIKELY(IsLayoutObjectDestroyedOrMoved())) { + // TODO(crbug.com/1099613): This should not happen, as long as it is really + // layout-clean. It looks like there are cases where the layout is dirty. + NOTREACHED(); + cursor->MoveToNextSkippingChildren(); + return; + } + if (IsText()) { cursor->MoveToNext(); @@ -482,7 +536,7 @@ void NGFragmentItem::RecalcInkOverflow( DCHECK(box_fragment->IsInlineBox()); self_rect = box_fragment->ComputeSelfInkOverflow(); } else { - self_rect = LocalRect(); + NOTREACHED(); } *self_and_contents_rect_out = UnionRect(self_rect, contents_rect); } else { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h index 703564f3ebc..7823ff0bcef 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h @@ -21,16 +21,15 @@ namespace blink { class NGFragmentItems; class NGInlineBreakToken; -class NGInlineItem; class NGPhysicalTextFragment; struct NGTextFragmentPaintInfo; +struct NGLogicalLineItem; // This class represents a text run or a box in an inline formatting context. // // This class consumes less memory than a full fragment, and can be stored in a // flat list (NGFragmentItems) for easier and faster traversal. -class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, - public DisplayItemClient { +class CORE_EXPORT NGFragmentItem { public: // Represents regular text that exists in the DOM. struct TextItem { @@ -66,23 +65,21 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, enum ItemType { kText, kGeneratedText, kLine, kBox }; + // Create appropriate type for |line_item|. + NGFragmentItem(NGLogicalLineItem&& line_item, WritingMode writing_mode); // Create a text item. // TODO(kojii): Should be able to create without once creating fragments. explicit NGFragmentItem(const NGPhysicalTextFragment& text); // Create a box item. NGFragmentItem(const NGPhysicalBoxFragment& box, TextDirection resolved_direction); - // Create a culled box item. - NGFragmentItem(const NGInlineItem& inline_item, const PhysicalSize& size); // Create a line item. - NGFragmentItem(const NGPhysicalLineBoxFragment& line, wtf_size_t item_count); + explicit NGFragmentItem(const NGPhysicalLineBoxFragment& line); - // Create |NGFragmentItem| for all items in |child_list|. - static void Create(NGLineBoxFragmentBuilder::ChildList* child_list, - const String& text_content, - WritingMode writing_mode); + // The copy constructor. + NGFragmentItem(const NGFragmentItem&); - ~NGFragmentItem() final; + ~NGFragmentItem(); ItemType Type() const { return static_cast<ItemType>(type_); } @@ -95,19 +92,33 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, bool IsHiddenForPaint() const { return is_hidden_for_paint_; } bool IsListMarker() const; - // Return true if this is the first fragment generated from a node. - bool IsFirstForNode() const { - DCHECK(Type() != kLine); - return is_first_for_node_; + // A sequence number of fragments generated from a |LayoutObject|. + // For line boxes, please see |kInitialLineFragmentId|. + wtf_size_t FragmentId() const { + DCHECK_NE(Type(), kLine); + return fragment_id_; } + void SetFragmentId(wtf_size_t id) const { + DCHECK_NE(Type(), kLine); + fragment_id_ = id; + } + // The initial framgent_id for line boxes. + // TODO(kojii): This is to avoid conflict with multicol because line boxes use + // its |LayoutBlockFlow| as their |DisplayItemClient|, but multicol also uses + // fragment id for |LayoutBlockFlow| today. The plan is to make |FragmentData| + // a |DisplayItemClient| instead. + // TODO(kojii): The fragment id for line boxes must be unique across NG block + // fragmentation. This is not implemented yet. + static constexpr wtf_size_t kInitialLineFragmentId = 0x80000000; + + // Return true if this is the first fragment generated from a node. + bool IsFirstForNode() const { return !FragmentId(); } // Return true if this is the last fragment generated from a node. bool IsLastForNode() const { DCHECK(Type() != kLine); return is_last_for_node_; } - - void SetIsFirstForNode(bool is_first) const { is_first_for_node_ = is_first; } void SetIsLastForNode(bool is_last) const { is_last_for_node_ = is_last; } NGStyleVariant StyleVariant() const { @@ -129,11 +140,17 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, LayoutObject* GetMutableLayoutObject() const { return const_cast<LayoutObject*>(layout_object_); } + bool IsLayoutObjectDestroyedOrMoved() const { return !layout_object_; } void LayoutObjectWillBeDestroyed() const; void LayoutObjectWillBeMoved() const; Node* GetNode() const { return layout_object_->GetNode(); } Node* NodeForHitTest() const { return layout_object_->NodeForHitTest(); } + // Use |LayoutObject|+|FragmentId()| for |DisplayItem::Id|. + const DisplayItemClient* GetDisplayItemClient() const { + return GetLayoutObject(); + } + wtf_size_t DeltaToNextForSameLayoutObject() const { return delta_to_next_for_same_layout_object_; } @@ -161,8 +178,15 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, } bool HasChildren() const { return DescendantsCount() > 1; } void SetDescendantsCount(wtf_size_t count) { - CHECK_EQ(Type(), kBox); - box_.descendants_count = count; + if (Type() == kBox) { + box_.descendants_count = count; + return; + } + if (Type() == kLine) { + line_.descendants_count = count; + return; + } + NOTREACHED(); } // Returns |NGPhysicalBoxFragment| if one is associated with this item. @@ -202,11 +226,6 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, return NGLineBoxType::kNormalLineBox; } - // DisplayItemClient overrides - String DebugName() const override; - IntRect VisualRect() const override; - IntRect PartialInvalidationVisualRect() const override; - static PhysicalRect LocalVisualRectFor(const LayoutObject& layout_object); // Re-compute the ink overflow for the |cursor| until its end. @@ -347,9 +366,24 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, // Returns true if this item is reusable. bool CanReuse() const; + const NGFragmentItem* operator->() const { return this; } + + // Get a description of |this| for the debug purposes. + String ToString() const; + private: // Create a text item. - NGFragmentItem(NGInlineItemResult&& item_result, const PhysicalSize& size); + NGFragmentItem(const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const NGTextOffset& text_offset, + const PhysicalSize& size, + bool is_hidden_for_paint); + // Create a generated text item. + NGFragmentItem(const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const String& text_content, + const PhysicalSize& size, + bool is_hidden_for_paint); const LayoutBox* InkOverflowOwnerBox() const; LayoutBox* MutableInkOverflowOwnerBox(); @@ -375,6 +409,8 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, std::unique_ptr<NGInkOverflow> ink_overflow_; + mutable wtf_size_t fragment_id_ = 0; + // Item index delta to the next item for the same |LayoutObject|. mutable wtf_size_t delta_to_next_for_same_layout_object_ = 0; @@ -392,7 +428,6 @@ class CORE_EXPORT NGFragmentItem : public RefCounted<NGFragmentItem>, mutable unsigned is_dirty_ : 1; - mutable unsigned is_first_for_node_ : 1; mutable unsigned is_last_for_node_ : 1; }; @@ -405,6 +440,9 @@ inline bool NGFragmentItem::CanReuse() const { return false; } +CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGFragmentItem*); +CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGFragmentItem&); + } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_FRAGMENT_ITEM_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc index 59d6f53c852..cfe1900729d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc @@ -12,14 +12,6 @@ namespace blink { -namespace { - -inline bool ShouldSetFirstAndLastForNode() { - return RuntimeEnabledFeatures::LayoutNGFragmentTraversalEnabled(); -} - -} // namespace - NGFragmentItems::NGFragmentItems(NGFragmentItemsBuilder* builder) : text_content_(std::move(builder->text_content_)), first_line_text_content_(std::move(builder->first_line_text_content_)), @@ -28,16 +20,13 @@ NGFragmentItems::NGFragmentItems(NGFragmentItemsBuilder* builder) for (unsigned i = 0; i < size_; ++i) { // Call the move constructor to move without |AddRef|. Items in // |NGFragmentItemsBuilder| are not used after |this| was constructed. - DCHECK(source_items[i].item); - new (&items_[i]) - scoped_refptr<const NGFragmentItem>(std::move(source_items[i].item)); - DCHECK(!source_items[i].item); // Ensure the source was moved. + new (&items_[i]) NGFragmentItem(std::move(source_items[i].item)); } } NGFragmentItems::~NGFragmentItems() { for (unsigned i = 0; i < size_; ++i) - items_[i]->Release(); + items_[i].~NGFragmentItem(); } bool NGFragmentItems::IsSubSpan(const Span& span) const { @@ -47,63 +36,66 @@ bool NGFragmentItems::IsSubSpan(const Span& span) const { void NGFragmentItems::FinalizeAfterLayout( const Vector<scoped_refptr<const NGLayoutResult>, 1>& results) { - HashMap<const LayoutObject*, const NGFragmentItem*> first_and_last; + struct LastItem { + const NGFragmentItem* item; + wtf_size_t fragment_id; + wtf_size_t item_index; + }; + HashMap<const LayoutObject*, LastItem> last_items; for (const auto& result : results) { const auto& fragment = To<NGPhysicalBoxFragment>(result->PhysicalFragment()); const NGFragmentItems* current = fragment.Items(); if (UNLIKELY(!current)) continue; - HashMap<const LayoutObject*, wtf_size_t> last_fragment_map; + + // TODO(layout-dev): Make this work for multiple box fragments (block + // fragmentation). + const bool create_index_cache = fragment.IsFirstForNode(); + const Span items = current->Items(); wtf_size_t index = 0; - for (const scoped_refptr<const NGFragmentItem>& item : items) { + for (const NGFragmentItem& item : items) { ++index; - if (item->Type() == NGFragmentItem::kLine) { - DCHECK_EQ(item->DeltaToNextForSameLayoutObject(), 0u); - continue; - } - LayoutObject* const layout_object = item->GetMutableLayoutObject(); - if (UNLIKELY(layout_object->IsFloating())) { - DCHECK_EQ(item->DeltaToNextForSameLayoutObject(), 0u); + if (item.Type() == NGFragmentItem::kLine) { + DCHECK_EQ(item.DeltaToNextForSameLayoutObject(), 0u); continue; } + LayoutObject* const layout_object = item.GetMutableLayoutObject(); DCHECK(!layout_object->IsOutOfFlowPositioned()); - DCHECK(layout_object->IsInLayoutNGInlineFormattingContext()) << *item; - item->SetDeltaToNextForSameLayoutObject(0); - - if (ShouldSetFirstAndLastForNode()) { - bool is_first_for_node = - first_and_last.Set(layout_object, item.get()).is_new_entry; - item->SetIsFirstForNode(is_first_for_node); - item->SetIsLastForNode(false); - } - - // TODO(layout-dev): Make this work for multiple box fragments (block - // fragmentation). - if (!fragment.IsFirstForNode()) + DCHECK(layout_object->IsInLayoutNGInlineFormattingContext()); + + item.SetDeltaToNextForSameLayoutObject(0); + item.SetIsLastForNode(false); + + const auto last_item_result = + last_items.insert(layout_object, LastItem{&item, 0, index}); + if (last_item_result.is_new_entry) { + item.SetFragmentId(0); + if (create_index_cache) { + DCHECK_EQ(layout_object->FirstInlineFragmentItemIndex(), 0u); + layout_object->SetFirstInlineFragmentItemIndex(index); + } continue; + } - auto insert_result = last_fragment_map.insert(layout_object, index); - if (insert_result.is_new_entry) { - DCHECK_EQ(layout_object->FirstInlineFragmentItemIndex(), 0u); - layout_object->SetFirstInlineFragmentItemIndex(index); - continue; + LastItem* last = &last_item_result.stored_value->value; + const NGFragmentItem* last_item = last->item; + DCHECK_EQ(last_item->DeltaToNextForSameLayoutObject(), 0u); + if (create_index_cache) { + const wtf_size_t last_index = last->item_index; + DCHECK_GT(last_index, 0u); + DCHECK_LT(last_index, items.size()); + DCHECK_LT(last_index, index); + last_item->SetDeltaToNextForSameLayoutObject(index - last_index); } - const wtf_size_t last_index = insert_result.stored_value->value; - insert_result.stored_value->value = index; - DCHECK_GT(last_index, 0u) << *item; - DCHECK_LT(last_index, items.size()); - DCHECK_LT(last_index, index); - DCHECK_EQ(items[last_index - 1]->DeltaToNextForSameLayoutObject(), 0u); - items[last_index - 1]->SetDeltaToNextForSameLayoutObject(index - - last_index); + item.SetFragmentId(++last->fragment_id); + last->item = &item; + last->item_index = index; } } - if (!ShouldSetFirstAndLastForNode()) - return; - for (const auto& iter : first_and_last) - iter.value->SetIsLastForNode(true); + for (const auto& iter : last_items) + iter.value.item->SetIsLastForNode(true); } void NGFragmentItems::ClearAssociatedFragments(LayoutObject* container) { @@ -113,7 +105,7 @@ void NGFragmentItems::ClearAssociatedFragments(LayoutObject* container) { for (LayoutObject* child = container->SlowFirstChild(); child; child = child->NextSibling()) { if (UNLIKELY(!child->IsInLayoutNGInlineFormattingContext() || - child->IsFloatingOrOutOfFlowPositioned())) + child->IsOutOfFlowPositioned())) continue; child->ClearFirstInlineFragmentItemIndex(); @@ -183,7 +175,7 @@ bool NGFragmentItems::TryDirtyFirstLineFor( DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()); DCHECK(!layout_object.IsFloatingOrOutOfFlowPositioned()); if (wtf_size_t index = layout_object.FirstInlineFragmentItemIndex()) { - const NGFragmentItem& item = *Items()[index - 1]; + const NGFragmentItem& item = Items()[index - 1]; DCHECK_EQ(&layout_object, item.GetLayoutObject()); item.SetDirty(); return true; @@ -251,32 +243,18 @@ void NGFragmentItems::DirtyLinesFromChangedChild( void NGFragmentItems::DirtyLinesFromNeedsLayout( const LayoutBlockFlow* container) const { DCHECK_EQ(this, container->FragmentItems()); - for (LayoutObject* layout_object = container->FirstChild(); layout_object;) { - if (layout_object->IsText()) { - if (layout_object->SelfNeedsLayout()) { - DirtyLinesFromChangedChild(layout_object); - return; - } - } else if (auto* layout_inline = ToLayoutInlineOrNull(layout_object)) { - if (layout_object->SelfNeedsLayout()) { - DirtyLinesFromChangedChild(layout_object); - return; - } - if (layout_object->NormalChildNeedsLayout() || - layout_object->PosChildNeedsLayout()) { - if (LayoutObject* child = layout_inline->FirstChild()) { - layout_object = child; - continue; - } - } - } else { - if (layout_object->NeedsLayout()) { - DirtyLinesFromChangedChild(layout_object); - return; - } + // Mark dirty for the first top-level child that has |NeedsLayout|. + // + // TODO(kojii): We could mark first descendant to increase reuse + // opportunities. Doing this complicates the logic, especially when culled + // inline is involved, and common case is to append to large IFC. Choose + // simpler logic and faster to check over more reuse opportunities. + for (LayoutObject* child = container->FirstChild(); child; + child = child->NextSibling()) { + if (child->NeedsLayout()) { + DirtyLinesFromChangedChild(child); + return; } - - layout_object = layout_object->NextInPreOrderAfterChildren(container); } } @@ -292,8 +270,8 @@ void NGFragmentItems::LayoutObjectWillBeMoved( *container.GetPhysicalFragment(idx); DCHECK(fragment.Items()); for (const auto& item : fragment.Items()->Items()) { - if (item->GetLayoutObject() == &layout_object) - item->LayoutObjectWillBeMoved(); + if (item.GetLayoutObject() == &layout_object) + item.LayoutObjectWillBeMoved(); } } return; @@ -319,8 +297,8 @@ void NGFragmentItems::LayoutObjectWillBeDestroyed( *container.GetPhysicalFragment(idx); DCHECK(fragment.Items()); for (const auto& item : fragment.Items()->Items()) { - if (item->GetLayoutObject() == &layout_object) - item->LayoutObjectWillBeDestroyed(); + if (item.GetLayoutObject() == &layout_object) + item.LayoutObjectWillBeDestroyed(); } } return; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h index eebdb5f616e..00025571a5a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h @@ -24,15 +24,21 @@ class CORE_EXPORT NGFragmentItems { wtf_size_t Size() const { return size_; } - using Span = base::span<const scoped_refptr<const NGFragmentItem>>; + using Span = base::span<const NGFragmentItem>; Span Items() const { return base::make_span(ItemsData(), size_); } + bool Equals(const Span& span) const { + return ItemsData() == span.data() && Size() == span.size(); + } bool IsSubSpan(const Span& span) const; const NGFragmentItem& front() const { CHECK_GE(size_, 1u); - return *items_[0]; + return items_[0]; } + // Text content for `::first-line`. Available only if `::first-line` has + // different style than non-first-line style. + const String& FirstLineText() const { return first_line_text_content_; } const String& Text(bool first_line) const { return UNLIKELY(first_line) ? first_line_text_content_ : text_content_; } @@ -66,9 +72,7 @@ class CORE_EXPORT NGFragmentItems { wtf_size_t ByteSize() const { return ByteSizeFor(Size()); } private: - const scoped_refptr<const NGFragmentItem>* ItemsData() const { - return reinterpret_cast<const scoped_refptr<const NGFragmentItem>*>(items_); - } + const NGFragmentItem* ItemsData() const { return items_; } static bool CanReuseAll(NGInlineCursor* cursor); bool TryDirtyFirstLineFor(const LayoutObject& layout_object) const; @@ -86,7 +90,7 @@ class CORE_EXPORT NGFragmentItems { static_assert( sizeof(NGFragmentItem*) == sizeof(scoped_refptr<const NGFragmentItem>), "scoped_refptr must be the size of a pointer for |ItemsData()| to work"); - NGFragmentItem* items_[]; + NGFragmentItem items_[0]; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc index 452e1c2f102..74f8d5d2feb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" @@ -11,7 +12,14 @@ namespace blink { -NGFragmentItemsBuilder::NGFragmentItemsBuilder(const NGInlineNode& node) { +NGFragmentItemsBuilder::NGFragmentItemsBuilder( + WritingDirectionMode writing_direction) + : node_(nullptr), writing_direction_(writing_direction) {} + +NGFragmentItemsBuilder::NGFragmentItemsBuilder( + const NGInlineNode& node, + WritingDirectionMode writing_direction) + : node_(node), writing_direction_(writing_direction) { const NGInlineItemsData& items_data = node.ItemsData(false); text_content_ = items_data.text_content; const NGInlineItemsData& first_line = node.ItemsData(true); @@ -30,11 +38,17 @@ NGFragmentItemsBuilder::NGFragmentItemsBuilder(const NGInlineNode& node) { void NGFragmentItemsBuilder::SetCurrentLine( const NGPhysicalLineBoxFragment& line, - ChildList&& children) { + NGLogicalLineItems* current_line) { #if DCHECK_IS_ON() current_line_fragment_ = &line; #endif - current_line_ = std::move(children); + DCHECK(current_line); + DCHECK(!current_line_); // Check |AddLine| runs after |SetCurrentLine|. + current_line_ = current_line; +} + +void NGFragmentItemsBuilder::ClearCurrentLineForTesting() { + current_line_ = nullptr; } void NGFragmentItemsBuilder::AddLine(const NGPhysicalLineBoxFragment& line, @@ -43,79 +57,81 @@ void NGFragmentItemsBuilder::AddLine(const NGPhysicalLineBoxFragment& line, #if DCHECK_IS_ON() DCHECK_EQ(current_line_fragment_, &line); #endif + DCHECK(current_line_); // Reserve the capacity for (children + line box item). const wtf_size_t size_before = items_.size(); - const wtf_size_t estimated_size = size_before + current_line_.size() + 1; + const wtf_size_t estimated_size = size_before + current_line_->size() + 1; const wtf_size_t old_capacity = items_.capacity(); if (estimated_size > old_capacity) items_.ReserveCapacity(std::max(estimated_size, old_capacity * 2)); // Add an empty item so that the start of the line can be set later. const wtf_size_t line_start_index = items_.size(); - items_.emplace_back(offset); + items_.emplace_back(offset, line); - AddItems(current_line_.begin(), current_line_.end()); + AddItems(current_line_->begin(), current_line_->end()); // All children are added. Create an item for the start of the line. + NGFragmentItem& line_item = items_[line_start_index].item; const wtf_size_t item_count = items_.size() - line_start_index; - DCHECK(!items_[line_start_index].item); - items_[line_start_index].item = - base::MakeRefCounted<NGFragmentItem>(line, item_count); + DCHECK_EQ(line_item.DescendantsCount(), 1u); + line_item.SetDescendantsCount(item_count); // Keep children's offsets relative to |line|. They will be adjusted later in // |ConvertToPhysical()|. - current_line_.clear(); + // Clear the current line without releasing the buffer. It is likely to be + // reused again. + current_line_->Shrink(0); #if DCHECK_IS_ON() + current_line_ = nullptr; current_line_fragment_ = nullptr; #endif DCHECK_LE(items_.size(), estimated_size); } -void NGFragmentItemsBuilder::AddItems(Child* child_begin, Child* child_end) { +void NGFragmentItemsBuilder::AddItems(NGLogicalLineItem* child_begin, + NGLogicalLineItem* child_end) { DCHECK(!is_converted_to_physical_); - for (Child* child_iter = child_begin; child_iter != child_end;) { - Child& child = *child_iter; + const WritingMode writing_mode = GetWritingMode(); + for (NGLogicalLineItem* child_iter = child_begin; child_iter != child_end;) { + NGLogicalLineItem& child = *child_iter; // OOF children should have been added to their parent box fragments. DCHECK(!child.out_of_flow_positioned_box); - if (!child.fragment_item) { + if (!child.CanCreateFragmentItem()) { ++child_iter; continue; } if (child.children_count <= 1) { - items_.emplace_back(std::move(child.fragment_item), child.rect.offset); + items_.emplace_back(child.rect.offset, std::move(child), writing_mode); ++child_iter; continue; } - DCHECK(child.fragment_item->IsContainer()); - DCHECK(!child.fragment_item->IsFloating()); // Children of inline boxes are flattened and added to |items_|, with the // count of descendant items to preserve the tree structure. // // Add an empty item so that the start of the box can be set later. - wtf_size_t box_start_index = items_.size(); - items_.emplace_back(child.rect.offset); + const wtf_size_t box_start_index = items_.size(); + items_.emplace_back(child.rect.offset, std::move(child), writing_mode); // Add all children, including their desendants, skipping this item. CHECK_GE(child.children_count, 1u); // 0 will loop infinitely. - Child* end_child_iter = child_iter + child.children_count; + NGLogicalLineItem* end_child_iter = child_iter + child.children_count; CHECK_LE(end_child_iter - child_begin, child_end - child_begin); AddItems(child_iter + 1, end_child_iter); child_iter = end_child_iter; // All children are added. Compute how many items are actually added. The // number of items added maybe different from |child.children_count|. - wtf_size_t item_count = items_.size() - box_start_index; - - // Create an item for the start of the box. - child.fragment_item->SetDescendantsCount(item_count); - DCHECK(!items_[box_start_index].item); - items_[box_start_index].item = std::move(child.fragment_item); + const wtf_size_t item_count = items_.size() - box_start_index; + NGFragmentItem& box_item = items_[box_start_index].item; + DCHECK_EQ(box_item.DescendantsCount(), 1u); + box_item.SetDescendantsCount(item_count); } } @@ -127,24 +143,28 @@ void NGFragmentItemsBuilder::AddListMarker( // Resolved direction matters only for inline items, and outside list markers // are not inline. const TextDirection resolved_direction = TextDirection::kLtr; - items_.emplace_back( - base::MakeRefCounted<NGFragmentItem>(marker_fragment, resolved_direction), - offset); + items_.emplace_back(offset, marker_fragment, resolved_direction); } NGFragmentItemsBuilder::AddPreviousItemsResult NGFragmentItemsBuilder::AddPreviousItems( const NGFragmentItems& items, - WritingMode writing_mode, - TextDirection direction, const PhysicalSize& container_size, NGBoxFragmentBuilder* container_builder, - bool stop_at_dirty) { - AddPreviousItemsResult result; - if (stop_at_dirty) { + const NGFragmentItem* end_item) { + if (end_item) { + DCHECK(node_); DCHECK(container_builder); DCHECK(text_content_); + + if (UNLIKELY(items.FirstLineText() && !first_line_text_content_)) { + // Don't reuse previous items if they have different `::first-line` style + // but |this| doesn't. Reaching here means that computed style doesn't + // change, but |NGFragmentItem| has wrong |NGStyleVariant|. + return AddPreviousItemsResult(); + } } else { + DCHECK(!container_builder); DCHECK(!text_content_); text_content_ = items.Text(false); first_line_text_content_ = items.Text(true); @@ -159,11 +179,13 @@ NGFragmentItemsBuilder::AddPreviousItems( // This is needed because the container size may be different, in that case, // the physical offsets are different when `writing-mode: vertial-rl`. DCHECK(!is_converted_to_physical_); - const WritingMode line_writing_mode = ToLineWritingMode(writing_mode); + const WritingModeConverter converter(GetWritingDirection(), container_size); + const WritingMode writing_mode = GetWritingMode(); + WritingModeConverter line_converter( + {ToLineWritingMode(writing_mode), TextDirection::kLtr}); - const NGFragmentItem* const end_item = - stop_at_dirty ? items.EndOfReusableItems() : nullptr; - const NGFragmentItem* last_line_start_item = nullptr; + const NGInlineBreakToken* last_break_token = nullptr; + const NGInlineItemsData* items_data = nullptr; LayoutUnit used_block_size; for (NGInlineCursor cursor(items); cursor;) { @@ -174,74 +196,88 @@ NGFragmentItemsBuilder::AddPreviousItems( DCHECK(!item.IsDirty()); const LogicalOffset item_offset = - item.OffsetInContainerBlock().ConvertToLogical( - writing_mode, direction, container_size, item.Size()); - items_.emplace_back(&item, item_offset); + converter.ToLogical(item.OffsetInContainerBlock(), item.Size()); if (item.Type() == NGFragmentItem::kLine) { + DCHECK(item.LineBoxFragment()); + if (end_item) { + // Check if this line has valid item_index and offset. + const NGPhysicalLineBoxFragment* line_fragment = item.LineBoxFragment(); + const NGInlineBreakToken* break_token = + To<NGInlineBreakToken>(line_fragment->BreakToken()); + DCHECK(!break_token->IsFinished()); + const NGInlineItemsData* current_items_data; + if (UNLIKELY(break_token->UseFirstLineStyle())) + current_items_data = &node_.ItemsData(true); + else if (items_data) + current_items_data = items_data; + else + current_items_data = items_data = &node_.ItemsData(false); + if (UNLIKELY(!current_items_data->IsValidOffset( + break_token->ItemIndex(), break_token->TextOffset()))) { + NOTREACHED(); + break; + } + + last_break_token = break_token; + container_builder->AddChild(*line_fragment, item_offset); + used_block_size += + item.Size().ConvertToLogical(writing_mode).block_size; + } + + items_.emplace_back(item_offset, item); const PhysicalRect line_box_bounds = item.RectInContainerBlock(); + line_converter.SetOuterSize(line_box_bounds.size); for (NGInlineCursor line = cursor.CursorForDescendants(); line; line.MoveToNext()) { const NGFragmentItem& line_child = *line.Current().Item(); DCHECK(line_child.CanReuse()); items_.emplace_back( - &line_child, - (line_child.OffsetInContainerBlock() - line_box_bounds.offset) - .ConvertToLogical(line_writing_mode, TextDirection::kLtr, - line_box_bounds.size, line_child.Size())); + line_converter.ToLogical( + line_child.OffsetInContainerBlock() - line_box_bounds.offset, + line_child.Size()), + line_child); } cursor.MoveToNextSkippingChildren(); - DCHECK(item.LineBoxFragment()); - if (stop_at_dirty) { - container_builder->AddChild(*item.LineBoxFragment(), item_offset); - last_line_start_item = &item; - used_block_size += - item.Size().ConvertToLogical(writing_mode).block_size; - } continue; } DCHECK_NE(item.Type(), NGFragmentItem::kLine); - DCHECK(!stop_at_dirty); + DCHECK(!end_item); + items_.emplace_back(item_offset, item); cursor.MoveToNext(); } + DCHECK_LE(items_.size(), estimated_size); - if (stop_at_dirty && last_line_start_item) { - result.inline_break_token = last_line_start_item->InlineBreakToken(); - DCHECK(result.inline_break_token); - DCHECK(!result.inline_break_token->IsFinished()); - result.used_block_size = used_block_size; - result.succeeded = true; + if (end_item && last_break_token) { + DCHECK(!last_break_token->IsFinished()); + return AddPreviousItemsResult{last_break_token, used_block_size, true}; } - - DCHECK_LE(items_.size(), estimated_size); - return result; + return AddPreviousItemsResult(); } const NGFragmentItemsBuilder::ItemWithOffsetList& NGFragmentItemsBuilder::Items( - WritingMode writing_mode, - TextDirection direction, const PhysicalSize& outer_size) { - ConvertToPhysical(writing_mode, direction, outer_size); + ConvertToPhysical(outer_size); return items_; } // Convert internal logical offsets to physical. Items are kept with logical // offset until outer box size is determined. -void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode, - TextDirection direction, - const PhysicalSize& outer_size) { +void NGFragmentItemsBuilder::ConvertToPhysical(const PhysicalSize& outer_size) { if (is_converted_to_physical_) return; + const WritingModeConverter converter(GetWritingDirection(), outer_size); // Children of lines have line-relative offsets. Use line-writing mode to - // convert their logical offsets. - const WritingMode line_writing_mode = ToLineWritingMode(writing_mode); + // convert their logical offsets. Use `kLtr` because inline items are after + // bidi-reoder, and that their offset is visual, not logical. + WritingModeConverter line_converter( + {ToLineWritingMode(GetWritingMode()), TextDirection::kLtr}); for (ItemWithOffset* iter = items_.begin(); iter != items_.end(); ++iter) { - NGFragmentItem* item = const_cast<NGFragmentItem*>(iter->item.get()); - item->SetOffset(iter->offset.ConvertToPhysical(writing_mode, direction, - outer_size, item->Size())); + NGFragmentItem* item = &iter->item; + item->SetOffset(converter.ToPhysical(iter->offset, item->Size())); // Transform children of lines separately from children of the block, // because they may have different directions from the block. To do @@ -251,16 +287,14 @@ void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode, DCHECK(descendants_count); if (descendants_count) { const PhysicalRect line_box_bounds = item->RectInContainerBlock(); + line_converter.SetOuterSize(line_box_bounds.size); while (--descendants_count) { ++iter; DCHECK_NE(iter, items_.end()); - item = const_cast<NGFragmentItem*>(iter->item.get()); - // Use `kLtr` because inline items are after bidi-reoder, and that - // their offset is visual, not logical. - item->SetOffset(iter->offset.ConvertToPhysical( - line_writing_mode, TextDirection::kLtr, - line_box_bounds.size, item->Size()) + - line_box_bounds.offset); + item = &iter->item; + item->SetOffset( + line_converter.ToPhysical(iter->offset, item->Size()) + + line_box_bounds.offset); } } } @@ -278,12 +312,10 @@ base::Optional<LogicalOffset> NGFragmentItemsBuilder::LogicalOffsetFor( return base::nullopt; } -void NGFragmentItemsBuilder::ToFragmentItems(WritingMode writing_mode, - TextDirection direction, - const PhysicalSize& outer_size, +void NGFragmentItemsBuilder::ToFragmentItems(const PhysicalSize& outer_size, void* data) { DCHECK(text_content_); - ConvertToPhysical(writing_mode, direction, outer_size); + ConvertToPhysical(outer_size); new (data) NGFragmentItems(this); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h index c161eed2aec..081a0286c6a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h @@ -7,7 +7,8 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.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_logical_line_item.h" +#include "third_party/blink/renderer/platform/text/writing_direction_mode.h" namespace blink { @@ -22,8 +23,17 @@ class CORE_EXPORT NGFragmentItemsBuilder { STACK_ALLOCATED(); public: - NGFragmentItemsBuilder() = default; - explicit NGFragmentItemsBuilder(const NGInlineNode& node); + explicit NGFragmentItemsBuilder(WritingDirectionMode writing_direction); + NGFragmentItemsBuilder(const NGInlineNode& node, + WritingDirectionMode writing_direction); + + WritingDirectionMode GetWritingDirection() const { + return writing_direction_; + } + WritingMode GetWritingMode() const { + return writing_direction_.GetWritingMode(); + } + TextDirection Direction() const { return writing_direction_.Direction(); } wtf_size_t Size() const { return items_.size(); } @@ -39,22 +49,20 @@ class CORE_EXPORT NGFragmentItemsBuilder { : text_content_; } - // The caller should create a |ChildList| for a complete line and add to this - // builder. + // The caller should create a |NGLogicalLineItems| for a complete line and add + // to this builder. // // Adding a line is a two-pass operation, because |NGInlineLayoutAlgorithm| // creates and positions children within a line box, but its parent algorithm // positions the line box. |SetCurrentLine| sets the children, and the next // |AddLine| adds them. // - // TODO(kojii): Moving |ChildList| is not cheap because it has inline - // capacity. Reconsider the ownership. - using Child = NGLineBoxFragmentBuilder::Child; - using ChildList = NGLineBoxFragmentBuilder::ChildList; + // The caller must keep |children| alive until |AddLine| completes. void SetCurrentLine(const NGPhysicalLineBoxFragment& line, - ChildList&& children); + NGLogicalLineItems* current_line); void AddLine(const NGPhysicalLineBoxFragment& line, const LogicalOffset& offset); + void ClearCurrentLineForTesting(); // Add a list marker to the current line. void AddListMarker(const NGPhysicalBoxFragment& marker_fragment, @@ -76,25 +84,22 @@ class CORE_EXPORT NGFragmentItemsBuilder { // items and stops copying before the first dirty line. AddPreviousItemsResult AddPreviousItems( const NGFragmentItems& items, - WritingMode writing_mode, - TextDirection direction, const PhysicalSize& container_size, NGBoxFragmentBuilder* container_builder = nullptr, - bool stop_at_dirty = false); + const NGFragmentItem* end_item = nullptr); struct ItemWithOffset { DISALLOW_NEW(); public: - ItemWithOffset(scoped_refptr<const NGFragmentItem> item, - const LogicalOffset& offset) - : item(std::move(item)), offset(offset) {} - explicit ItemWithOffset(const LogicalOffset& offset) : offset(offset) {} + template <class... Args> + explicit ItemWithOffset(const LogicalOffset& offset, Args&&... args) + : item(std::forward<Args>(args)...), offset(offset) {} - const NGFragmentItem& operator*() const { return *item; } - const NGFragmentItem* operator->() const { return item.get(); } + const NGFragmentItem& operator*() const { return item; } + const NGFragmentItem* operator->() const { return &item; } - scoped_refptr<const NGFragmentItem> item; + NGFragmentItem item; LogicalOffset offset; }; @@ -110,30 +115,27 @@ class CORE_EXPORT NGFragmentItemsBuilder { // containing block geometry for OOF-positioned nodes. // // Once this method has been called, new items cannot be added. - const ItemWithOffsetList& Items(WritingMode, - TextDirection, - const PhysicalSize& outer_size); + const ItemWithOffsetList& Items(const PhysicalSize& outer_size); // Build a |NGFragmentItems|. The builder cannot build twice because data set // to this builder may be cleared. - void ToFragmentItems(WritingMode, - TextDirection, - const PhysicalSize& outer_size, - void* data); + void ToFragmentItems(const PhysicalSize& outer_size, void* data); private: - void AddItems(Child* child_begin, Child* child_end); + void AddItems(NGLogicalLineItem* child_begin, NGLogicalLineItem* child_end); - void ConvertToPhysical(WritingMode writing_mode, - TextDirection direction, - const PhysicalSize& outer_size); + void ConvertToPhysical(const PhysicalSize& outer_size); ItemWithOffsetList items_; String text_content_; String first_line_text_content_; // Keeps children of a line until the offset is determined. See |AddLine|. - ChildList current_line_; + NGLogicalLineItems* current_line_ = nullptr; + + NGInlineNode node_; + + WritingDirectionMode writing_direction_; bool has_floating_descendants_for_paint_ = false; bool is_converted_to_physical_ = false; 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 33ba9243ca0..bb2d2a16de5 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 @@ -106,7 +106,7 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnBeginPlaceItems( const ComputedStyle& line_style, FontBaseline baseline_type, bool line_height_quirk, - NGLineBoxFragmentBuilder::ChildList* line_box) { + NGLogicalLineItems* line_box) { if (stack_.IsEmpty()) { // For the first line, push a box state for the line itself. stack_.resize(1); @@ -156,7 +156,7 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag( const NGInlineItem& item, const NGInlineItemResult& item_result, FontBaseline baseline_type, - NGLineBoxFragmentBuilder::ChildList* line_box) { + NGLogicalLineItems* line_box) { NGInlineBoxState* box = OnOpenTag(item, item_result, baseline_type, *line_box); box->needs_box_fragment = item.ShouldCreateBoxFragment(); @@ -169,7 +169,7 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag( const NGInlineItem& item, const NGInlineItemResult& item_result, FontBaseline baseline_type, - const NGLineBoxFragmentBuilder::ChildList& line_box) { + const NGLogicalLineItems& line_box) { DCHECK(item.Style()); const ComputedStyle& style = *item.Style(); stack_.resize(stack_.size() + 1); @@ -186,7 +186,7 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag( } NGInlineBoxState* NGInlineLayoutStateStack::OnCloseTag( - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, NGInlineBoxState* box, FontBaseline baseline_type, bool has_end_edge) { @@ -200,9 +200,8 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnCloseTag( return &stack_.back(); } -void NGInlineLayoutStateStack::OnEndPlaceItems( - NGLineBoxFragmentBuilder::ChildList* line_box, - FontBaseline baseline_type) { +void NGInlineLayoutStateStack::OnEndPlaceItems(NGLogicalLineItems* line_box, + FontBaseline baseline_type) { for (auto it = stack_.rbegin(); it != stack_.rend(); ++it) { NGInlineBoxState* box = &(*it); if (!box->has_end_edge && box->needs_box_fragment && @@ -215,17 +214,15 @@ void NGInlineLayoutStateStack::OnEndPlaceItems( // that |ApplyBaselineShift()| can compute offset for both children and boxes. // Copy the final offset to |box_data_list_|. for (BoxData& box_data : box_data_list_) { - const NGLineBoxFragmentBuilder::Child& placeholder = - (*line_box)[box_data.fragment_start]; + const NGLogicalLineItem& placeholder = (*line_box)[box_data.fragment_start]; DCHECK(placeholder.IsPlaceholder()); box_data.rect.offset = placeholder.rect.offset; } } -void NGInlineLayoutStateStack::EndBoxState( - NGInlineBoxState* box, - NGLineBoxFragmentBuilder::ChildList* line_box, - FontBaseline baseline_type) { +void NGInlineLayoutStateStack::EndBoxState(NGInlineBoxState* box, + NGLogicalLineItems* line_box, + FontBaseline baseline_type) { if (box->needs_box_fragment) AddBoxData(box, line_box); @@ -237,8 +234,6 @@ void NGInlineLayoutStateStack::EndBoxState( return; NGInlineBoxState& parent_box = *std::prev(box); - // Propagate necessary data back to the parent box. - // Unite the metrics to the parent box. if (position_pending == kPositionNotPending) parent_box.metrics.Unite(box->metrics); @@ -250,7 +245,7 @@ void NGInlineLayoutStateStack::EndBoxState( // from placeholders. void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( NGInlineBoxState* box, - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, FontBaseline baseline_type) { DCHECK(box != stack_.begin() && box->item->Type() != NGInlineItem::kAtomicInline); @@ -278,49 +273,37 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( } // Add a |BoxData|, for each close-tag that needs a box fragment. -void NGInlineLayoutStateStack::AddBoxData( - NGInlineBoxState* box, - NGLineBoxFragmentBuilder::ChildList* line_box) { - DCHECK(box->needs_box_fragment || - (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled() && - box->has_box_placeholder && box != stack_.begin() && - box->item->Type() != NGInlineItem::kAtomicInline)); +void NGInlineLayoutStateStack::AddBoxData(NGInlineBoxState* box, + NGLogicalLineItems* line_box) { + DCHECK(box->needs_box_fragment); DCHECK(box->style); const ComputedStyle& style = *box->style; - NGLineBoxFragmentBuilder::Child& placeholder = - (*line_box)[box->fragment_start]; + NGLogicalLineItem& placeholder = (*line_box)[box->fragment_start]; DCHECK(placeholder.IsPlaceholder()); const unsigned fragment_end = line_box->size(); DCHECK(box->item); BoxData& box_data = box_data_list_.emplace_back( box->fragment_start, fragment_end, box->item, placeholder.Size()); - if (box->needs_box_fragment) { - box_data.padding = box->padding; - if (box->has_start_edge) { - box_data.has_line_left_edge = true; - box_data.margin_line_left = box->margin_inline_start; - box_data.margin_border_padding_line_left = box->margin_inline_start + - box->borders.inline_start + - box->padding.inline_start; - } - if (box->has_end_edge) { - box_data.has_line_right_edge = true; - box_data.margin_line_right = box->margin_inline_end; - box_data.margin_border_padding_line_right = box->margin_inline_end + - box->borders.inline_end + - box->padding.inline_end; - } - if (IsRtl(style.Direction())) { - std::swap(box_data.has_line_left_edge, box_data.has_line_right_edge); - std::swap(box_data.margin_line_left, box_data.margin_line_right); - std::swap(box_data.margin_border_padding_line_left, - box_data.margin_border_padding_line_right); - } - } else { - DCHECK_EQ(box->margin_inline_start, 0); - DCHECK_EQ(box->margin_inline_end, 0); - DCHECK(box->padding.IsEmpty()); - DCHECK(box->borders.IsEmpty()); + box_data.padding = box->padding; + if (box->has_start_edge) { + box_data.has_line_left_edge = true; + box_data.margin_line_left = box->margin_inline_start; + box_data.margin_border_padding_line_left = box->margin_inline_start + + box->borders.inline_start + + box->padding.inline_start; + } + if (box->has_end_edge) { + box_data.has_line_right_edge = true; + box_data.margin_line_right = box->margin_inline_end; + box_data.margin_border_padding_line_right = box->margin_inline_end + + box->borders.inline_end + + box->padding.inline_end; + } + if (IsRtl(style.Direction())) { + std::swap(box_data.has_line_left_edge, box_data.has_line_right_edge); + std::swap(box_data.margin_line_left, box_data.margin_line_right); + std::swap(box_data.margin_border_padding_line_left, + box_data.margin_border_padding_line_right); } DCHECK((*line_box)[box->fragment_start].IsPlaceholder()); @@ -356,8 +339,7 @@ void NGInlineLayoutStateStack::ChildInserted(unsigned index) { } } -void NGInlineLayoutStateStack::PrepareForReorder( - NGLineBoxFragmentBuilder::ChildList* line_box) { +void NGInlineLayoutStateStack::PrepareForReorder(NGLogicalLineItems* line_box) { // There's nothing to do if no boxes. if (box_data_list_.IsEmpty()) return; @@ -368,7 +350,7 @@ void NGInlineLayoutStateStack::PrepareForReorder( box_data_index++; DCHECK((*line_box)[box_data.fragment_start].IsPlaceholder()); for (unsigned i = box_data.fragment_start; i < box_data.fragment_end; i++) { - NGLineBoxFragmentBuilder::Child& child = (*line_box)[i]; + NGLogicalLineItem& child = (*line_box)[i]; unsigned child_box_data_index = child.box_data_index; if (!child_box_data_index) { child.box_data_index = box_data_index; @@ -390,7 +372,7 @@ void NGInlineLayoutStateStack::PrepareForReorder( } void NGInlineLayoutStateStack::UpdateAfterReorder( - NGLineBoxFragmentBuilder::ChildList* line_box) { + NGLogicalLineItems* line_box) { // There's nothing to do if no boxes. if (box_data_list_.IsEmpty()) return; @@ -416,18 +398,18 @@ void NGInlineLayoutStateStack::UpdateAfterReorder( DCHECK_GT(box_data.fragment_end, box_data.fragment_start); } // Check all |box_data_index| were migrated to BoxData. - for (const NGLineBoxFragmentBuilder::Child& child : *line_box) { + for (const NGLogicalLineItem& child : *line_box) { DCHECK_EQ(child.box_data_index, 0u); } #endif } unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange( - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, unsigned index) { // Find the first line box item that should create a box fragment. for (; index < line_box->size(); index++) { - NGLineBoxFragmentBuilder::Child* start = &(*line_box)[index]; + NGLogicalLineItem* start = &(*line_box)[index]; const unsigned box_data_index = start->box_data_index; if (!box_data_index) continue; @@ -444,7 +426,7 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange( // Find the end line box item. const unsigned start_index = index; for (index++; index < line_box->size(); index++) { - NGLineBoxFragmentBuilder::Child* end = &(*line_box)[index]; + NGLogicalLineItem* end = &(*line_box)[index]; // If we found another box that maybe included in this box, update it // first. Updating will change |end->box_data_index| so that we can @@ -510,11 +492,11 @@ void NGInlineLayoutStateStack::BoxData::UpdateFragmentEdges( } LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, LayoutUnit position) { // At this point, children are in the visual order, and they have their // origins at (0, 0). Accumulate inline offset from left to right. - for (NGLineBoxFragmentBuilder::Child& child : *line_box) { + for (NGLogicalLineItem& child : *line_box) { child.margin_line_left = child.rect.offset.inline_offset; child.rect.offset.inline_offset += position; // Box margins/boders/paddings will be processed later. @@ -560,7 +542,7 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( // border/padding of this box and margin/border/padding of descendants // boxes, while accumulating its margin/border/padding. unsigned start = box_data.fragment_start; - NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start]; + NGLogicalLineItem& start_child = (*line_box)[start]; LayoutUnit line_left_offset = start_child.rect.offset.inline_offset - start_child.margin_line_left; LinePadding& start_padding = accumulated_padding[start]; @@ -569,7 +551,7 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( DCHECK_GT(box_data.fragment_end, start); unsigned last = box_data.fragment_end - 1; - NGLineBoxFragmentBuilder::Child& last_child = (*line_box)[last]; + NGLogicalLineItem& last_child = (*line_box)[last]; LayoutUnit line_right_offset = last_child.rect.offset.inline_offset - last_child.margin_line_left + last_child.inline_size; @@ -585,36 +567,19 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( } void NGInlineLayoutStateStack::CreateBoxFragments( - NGLineBoxFragmentBuilder::ChildList* line_box) { + NGLogicalLineItems* line_box) { DCHECK(!box_data_list_.IsEmpty()); for (BoxData& box_data : box_data_list_) { unsigned start = box_data.fragment_start; unsigned end = box_data.fragment_end; DCHECK_GT(end, start); - NGLineBoxFragmentBuilder::Child* child = &(*line_box)[start]; - if (box_data.item->ShouldCreateBoxFragment()) { - scoped_refptr<const NGLayoutResult> box_fragment = - box_data.CreateBoxFragment(line_box); - if (child->IsPlaceholder()) { - child->layout_result = std::move(box_fragment); - child->rect = box_data.rect; - child->children_count = end - start; - continue; - } - - // |AddBoxFragmentPlaceholder| adds a placeholder at |fragment_start|, but - // bidi reordering may move it. Insert in such case. - line_box->InsertChild(start, std::move(box_fragment), box_data.rect, - end - start + 1); - ChildInserted(start + 1); - continue; - } - - DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); - DCHECK(box_data.item); + NGLogicalLineItem* child = &(*line_box)[start]; + DCHECK(box_data.item->ShouldCreateBoxFragment()); + scoped_refptr<const NGLayoutResult> box_fragment = + box_data.CreateBoxFragment(line_box); if (child->IsPlaceholder()) { - child->inline_item = box_data.item; + child->layout_result = std::move(box_fragment); child->rect = box_data.rect; child->children_count = end - start; continue; @@ -622,7 +587,7 @@ void NGInlineLayoutStateStack::CreateBoxFragments( // |AddBoxFragmentPlaceholder| adds a placeholder at |fragment_start|, but // bidi reordering may move it. Insert in such case. - line_box->InsertChild(start, *box_data.item, box_data.rect, + line_box->InsertChild(start, std::move(box_fragment), box_data.rect, end - start + 1); ChildInserted(start + 1); } @@ -632,7 +597,7 @@ void NGInlineLayoutStateStack::CreateBoxFragments( scoped_refptr<const NGLayoutResult> NGInlineLayoutStateStack::BoxData::CreateBoxFragment( - NGLineBoxFragmentBuilder::ChildList* line_box) { + NGLogicalLineItems* line_box) { DCHECK(item); DCHECK(item->Style()); const ComputedStyle& style = *item->Style(); @@ -646,7 +611,7 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( // 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); + {style.GetWritingMode(), TextDirection::kLtr}); box.SetInitialFragmentGeometry(fragment_geometry); box.SetBoxType(NGPhysicalFragment::kInlineBox); box.SetStyleVariant(item->StyleVariant()); @@ -657,7 +622,13 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( box.SetBorderEdges({true, has_line_right_edge, true, has_line_left_edge}); for (unsigned i = fragment_start; i < fragment_end; i++) { - NGLineBoxFragmentBuilder::Child& child = (*line_box)[i]; + NGLogicalLineItem& child = (*line_box)[i]; + + // If |child| has a fragment created by previous |CreateBoxFragment|, skip + // children that were already added to |child|. + if (child.children_count) + i += child.children_count - 1; + if (child.out_of_flow_positioned_box) { DCHECK(item->GetLayoutObject()->IsLayoutInline()); NGBlockNode oof_box(ToLayoutBox(child.out_of_flow_positioned_box)); @@ -705,10 +676,9 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( } NGInlineLayoutStateStack::PositionPending -NGInlineLayoutStateStack::ApplyBaselineShift( - NGInlineBoxState* box, - NGLineBoxFragmentBuilder::ChildList* line_box, - FontBaseline baseline_type) { +NGInlineLayoutStateStack::ApplyBaselineShift(NGInlineBoxState* box, + NGLogicalLineItems* line_box, + FontBaseline baseline_type) { // Some 'vertical-align' values require the size of their parents. Align all // such descendant boxes that require the size of this box; they are queued in // |pending_descendants|. @@ -852,7 +822,7 @@ NGInlineLayoutStateStack::ApplyBaselineShift( NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign( const NGInlineBoxState& box, - const NGLineBoxFragmentBuilder::ChildList& line_box) const { + const NGLogicalLineItems& line_box) const { DCHECK(!box.pending_descendants.IsEmpty()); // |metrics| is the bounds of "aligned subtree", that is, bounds of @@ -871,8 +841,7 @@ NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign( continue; // |block_offset| is the top position when the baseline is at 0. - const NGLineBoxFragmentBuilder::Child& placeholder = - line_box[box_data.fragment_start]; + const NGLogicalLineItem& placeholder = line_box[box_data.fragment_start]; DCHECK(placeholder.IsPlaceholder()); LayoutUnit box_ascent = -placeholder.rect.offset.block_offset; NGLineHeightMetrics box_metrics(box_ascent, 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 6dcee9c380a..6f58b80073a 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 @@ -16,8 +16,9 @@ namespace blink { class NGInlineItem; -struct NGInlineItemResult; +class NGLogicalLineItems; class ShapeResultView; +struct NGInlineItemResult; // Fragments that require the layout position/size of ancestor are packed in // this struct. @@ -114,31 +115,30 @@ class CORE_EXPORT NGInlineLayoutStateStack { // Initialize the box state stack for a new line. // @return The initial box state for the line. - NGInlineBoxState* OnBeginPlaceItems( - const ComputedStyle&, - FontBaseline, - bool line_height_quirk, - NGLineBoxFragmentBuilder::ChildList* line_box); + NGInlineBoxState* OnBeginPlaceItems(const ComputedStyle&, + FontBaseline, + bool line_height_quirk, + NGLogicalLineItems* line_box); // Push a box state stack. NGInlineBoxState* OnOpenTag(const NGInlineItem&, const NGInlineItemResult&, FontBaseline baseline_type, - const NGLineBoxFragmentBuilder::ChildList&); + const NGLogicalLineItems&); // This variation adds a box placeholder to |line_box|. NGInlineBoxState* OnOpenTag(const NGInlineItem&, const NGInlineItemResult&, FontBaseline baseline_type, - NGLineBoxFragmentBuilder::ChildList* line_box); + NGLogicalLineItems* line_box); // Pop a box state stack. - NGInlineBoxState* OnCloseTag(NGLineBoxFragmentBuilder::ChildList*, + NGInlineBoxState* OnCloseTag(NGLogicalLineItems*, NGInlineBoxState*, FontBaseline, bool has_end_edge = true); // Compute all the pending positioning at the end of a line. - void OnEndPlaceItems(NGLineBoxFragmentBuilder::ChildList*, FontBaseline); + void OnEndPlaceItems(NGLogicalLineItems*, FontBaseline); bool HasBoxFragments() const { return !box_data_list_.IsEmpty(); } @@ -148,12 +148,12 @@ class CORE_EXPORT NGInlineLayoutStateStack { // This class keeps indexes to fragments in the line box, and that only // appending is allowed. Call this function to move all such data to the line // box, so that outside of this class can reorder fragments in the line box. - void PrepareForReorder(NGLineBoxFragmentBuilder::ChildList*); + void PrepareForReorder(NGLogicalLineItems*); // When reordering was complete, call this function to re-construct the box // data from the line box. Callers must call |PrepareForReorder()| before // reordering. - void UpdateAfterReorder(NGLineBoxFragmentBuilder::ChildList*); + void UpdateAfterReorder(NGLogicalLineItems*); // Update start/end of the first BoxData found at |index|. // @@ -161,19 +161,17 @@ class CORE_EXPORT NGInlineLayoutStateStack { // // Returns the index to process next. It should be given to the next call to // this function. - unsigned UpdateBoxDataFragmentRange(NGLineBoxFragmentBuilder::ChildList*, - unsigned index); + unsigned UpdateBoxDataFragmentRange(NGLogicalLineItems*, unsigned index); // Update edges of inline fragmented boxes. void UpdateFragmentedBoxDataEdges(); // Compute inline positions of fragments and boxes. - LayoutUnit ComputeInlinePositions(NGLineBoxFragmentBuilder::ChildList*, - LayoutUnit position); + LayoutUnit ComputeInlinePositions(NGLogicalLineItems*, LayoutUnit position); // Create box fragments. This function turns a flat list of children into // a box tree. - void CreateBoxFragments(NGLineBoxFragmentBuilder::ChildList*); + void CreateBoxFragments(NGLogicalLineItems*); #if DCHECK_IS_ON() void CheckSame(const NGInlineLayoutStateStack&) const; @@ -182,14 +180,12 @@ class CORE_EXPORT NGInlineLayoutStateStack { private: // End of a box state, either explicitly by close tag, or implicitly at the // end of a line. - void EndBoxState(NGInlineBoxState*, - NGLineBoxFragmentBuilder::ChildList*, - FontBaseline); + void EndBoxState(NGInlineBoxState*, NGLogicalLineItems*, FontBaseline); void AddBoxFragmentPlaceholder(NGInlineBoxState*, - NGLineBoxFragmentBuilder::ChildList*, + NGLogicalLineItems*, FontBaseline); - void AddBoxData(NGInlineBoxState*, NGLineBoxFragmentBuilder::ChildList*); + void AddBoxData(NGInlineBoxState*, NGLogicalLineItems*); enum PositionPending { kPositionNotPending, kPositionPending }; @@ -200,14 +196,14 @@ class CORE_EXPORT NGInlineLayoutStateStack { // https://www.w3.org/TR/CSS22/visudet.html#propdef-vertical-align // https://www.w3.org/TR/css-inline-3/#propdef-vertical-align PositionPending ApplyBaselineShift(NGInlineBoxState*, - NGLineBoxFragmentBuilder::ChildList*, + NGLogicalLineItems*, FontBaseline); // Compute the metrics for when 'vertical-align' is 'top' and 'bottom' from // |pending_descendants|. NGLineHeightMetrics MetricsForTopAndBottomAlign( const NGInlineBoxState&, - const NGLineBoxFragmentBuilder::ChildList&) const; + const NGLogicalLineItems&) const; // Data for a box fragment. See AddBoxFragmentPlaceholder(). // This is a transient object only while building a line box. @@ -253,8 +249,7 @@ class CORE_EXPORT NGInlineLayoutStateStack { void UpdateFragmentEdges(Vector<BoxData, 4>& list); - scoped_refptr<const NGLayoutResult> CreateBoxFragment( - NGLineBoxFragmentBuilder::ChildList*); + scoped_refptr<const NGLayoutResult> CreateBoxFragment(NGLogicalLineItems*); }; Vector<NGInlineBoxState, 4> stack_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc index 888119d970d..0f4293fd07c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc @@ -9,6 +9,7 @@ namespace blink { namespace { struct SameSizeAsNGInlineChildLayoutContext { + NGLogicalLineItems line_items_; base::Optional<NGInlineLayoutStateStack> box_states_; void* pointers[2]; unsigned number; 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 360714b2902..09210c5894f 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 @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.h" namespace blink { @@ -27,6 +28,10 @@ class NGInlineChildLayoutContext { items_builder_ = builder; } + // Returns an instance of |NGLogicalLineItems|. This is reused when laying out + // the next line. + NGLogicalLineItems* LogicalLineItems() { return &logical_line_items_; } + // Returns the NGInlineLayoutStateStack in this context. bool HasBoxStates() const { return box_states_.has_value(); } NGInlineLayoutStateStack* BoxStates() { return &*box_states_; } @@ -50,6 +55,8 @@ class NGInlineChildLayoutContext { // transit, allocating separately is easier. NGFragmentItemsBuilder* items_builder_ = nullptr; + NGLogicalLineItems logical_line_items_; + base::Optional<NGInlineLayoutStateStack> box_states_; // The items and its index this context is set up for. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc index 88473e916fd..1da02bfd534 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/editing/position_with_affinity.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/layout_text.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" @@ -18,8 +19,11 @@ namespace blink { inline void NGInlineCursor::MoveToItem(const ItemsSpan::iterator& iter) { DCHECK(IsItemCursor()); DCHECK(iter >= items_.begin() && iter <= items_.end()); - current_.item_iter_ = iter; - current_.item_ = iter == items_.end() ? nullptr : iter->get(); + if (iter != items_.end()) { + current_.Set(iter); + return; + } + MakeNull(); } void NGInlineCursor::SetRoot(const NGFragmentItems& fragment_items, @@ -117,7 +121,7 @@ const LayoutBlockFlow* NGInlineCursor::GetLayoutBlockFlow() const { return layout_object->RootInlineFormattingContext(); } if (IsItemCursor()) { - const NGFragmentItem& item = *fragment_items_->Items().front(); + const NGFragmentItem& item = fragment_items_->front(); const LayoutObject* layout_object = item.GetLayoutObject(); if (item.Type() == NGFragmentItem::kLine) return To<LayoutBlockFlow>(layout_object); @@ -454,11 +458,18 @@ const DisplayItemClient* NGInlineCursorPosition::GetDisplayItemClient() const { if (paint_fragment_) return paint_fragment_; if (item_) - return item_; + return item_->GetDisplayItemClient(); NOTREACHED(); return nullptr; } +wtf_size_t NGInlineCursorPosition::FragmentId() const { + if (paint_fragment_) + return 0; + DCHECK(item_); + return item_->FragmentId(); +} + const NGInlineBreakToken* NGInlineCursorPosition::InlineBreakToken() const { DCHECK(IsLineBox()); if (paint_fragment_) { @@ -652,6 +663,20 @@ PhysicalOffset NGInlineCursorPosition::LineEndPoint() const { pixel_size); } +LogicalRect NGInlineCursorPosition::ConvertChildToLogical( + const PhysicalRect& physical_rect) const { + return WritingModeConverter( + {Style().GetWritingMode(), ResolvedOrBaseDirection()}, Size()) + .ToLogical(physical_rect); +} + +PhysicalRect NGInlineCursorPosition::ConvertChildToPhysical( + const LogicalRect& logical_rect) const { + return WritingModeConverter( + {Style().GetWritingMode(), ResolvedOrBaseDirection()}, Size()) + .ToPhysical(logical_rect); +} + PositionWithAffinity NGInlineCursor::PositionForPointInInlineFormattingContext( const PhysicalOffset& point, const NGPhysicalBoxFragment& container) { @@ -667,20 +692,26 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineFormattingContext( PhysicalSize(LayoutUnit(1), LayoutUnit(1))) .block_offset; - // Stores the closest line box child above |point| in the block direction. + // Stores the closest line box child after |point| in the block direction. // Used if we can't find any child |point| falls in to resolve the position. - NGInlineCursorPosition closest_line_before; - LayoutUnit closest_line_before_block_offset = LayoutUnit::Min(); + NGInlineCursorPosition closest_line_after; + LayoutUnit closest_line_after_block_offset = LayoutUnit::Min(); - // Stores the closest line box child below |point| in the block direction. + // Stores the closest line box child before |point| in the block direction. // Used if we can't find any child |point| falls in to resolve the position. - NGInlineCursorPosition closest_line_after; - LayoutUnit closest_line_after_block_offset = LayoutUnit::Max(); + NGInlineCursorPosition closest_line_before; + LayoutUnit closest_line_before_block_offset = LayoutUnit::Max(); while (*this) { const NGFragmentItem* child_item = CurrentItem(); DCHECK(child_item); if (child_item->Type() == NGFragmentItem::kLine) { + if (!CursorForDescendants().TryToMoveToFirstInlineLeafChild()) { + // editing/selection/last-empty-inline.html requires this to skip + // empty <span> with padding. + MoveToNextItemSkippingChildren(); + continue; + } // Try to resolve if |point| falls in a line box in block direction. const LayoutUnit child_block_offset = child_item->OffsetInContainerBlock() @@ -688,9 +719,9 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineFormattingContext( child_item->Size()) .block_offset; if (point_block_offset < child_block_offset) { - if (child_block_offset < closest_line_after_block_offset) { - closest_line_after_block_offset = child_block_offset; - closest_line_after = Current(); + if (child_block_offset < closest_line_before_block_offset) { + closest_line_before_block_offset = child_block_offset; + closest_line_before = Current(); } MoveToNextItemSkippingChildren(); continue; @@ -701,9 +732,9 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineFormattingContext( child_block_offset + child_item->Size().ConvertToLogical(writing_mode).block_size; if (point_block_offset >= child_block_end_offset) { - if (child_block_end_offset > closest_line_before_block_offset) { - closest_line_before_block_offset = child_block_end_offset; - closest_line_before = Current(); + if (child_block_end_offset > closest_line_after_block_offset) { + closest_line_after_block_offset = child_block_end_offset; + closest_line_after = Current(); } MoveToNextItemSkippingChildren(); continue; @@ -719,18 +750,47 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineFormattingContext( MoveToNextItem(); } - if (closest_line_after) { - MoveTo(closest_line_after); - if (const PositionWithAffinity child_position = - PositionForPointInInlineBox(point)) + // At here, |point| is not inside any line in |this|: + // |closest_line_before| + // |point| + // |closest_line_after| + if (closest_line_before) { + MoveTo(closest_line_before); + // Note: |move_caret_to_boundary| is true for Mac and Unix. + const bool move_caret_to_boundary = + To<LayoutBlockFlow>(Current().GetLayoutObject()) + ->ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(); + if (move_caret_to_boundary) { + // Tests[1-3] reach here. + // [1] editing/selection/click-in-margins-inside-editable-div.html + // [2] fast/writing-mode/flipped-blocks-hit-test-line-edges.html + // [3] All/LayoutViewHitTestTest.HitTestHorizontal/4 + if (auto first_position = PositionForStartOfLine()) + return PositionWithAffinity(first_position.GetPosition()); + } else if (const PositionWithAffinity child_position = + PositionForPointInInlineBox(point)) return child_position; } - if (closest_line_before) { - MoveTo(closest_line_before); - if (const PositionWithAffinity child_position = - PositionForPointInInlineBox(point)) + if (closest_line_after) { + MoveTo(closest_line_after); + // Note: |move_caret_to_boundary| is true for Mac and Unix. + const bool move_caret_to_boundary = + To<LayoutBlockFlow>(Current().GetLayoutObject()) + ->ShouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom(); + if (move_caret_to_boundary) { + // Tests[1-3] reach here. + // [1] editing/selection/click-in-margins-inside-editable-div.html + // [2] fast/writing-mode/flipped-blocks-hit-test-line-edges.html + // [3] All/LayoutViewHitTestTest.HitTestHorizontal/4 + if (auto last_position = PositionForEndOfLine()) + return PositionWithAffinity(last_position.GetPosition()); + } else if (const PositionWithAffinity child_position = + PositionForPointInInlineBox(point)) { + // Test[1] reaches here. + // [1] editing/selection/last-empty-inline.html return child_position; + } } return PositionWithAffinity(); @@ -802,14 +862,14 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineBox( } if (const PositionWithAffinity child_position = - descendants.PositionForPointInChild(point, *child_item)) + descendants.PositionForPointInChild(point)) return child_position; } if (closest_child_after) { descendants.MoveTo(closest_child_after); if (const PositionWithAffinity child_position = - descendants.PositionForPointInChild(point, *closest_child_after)) + descendants.PositionForPointInChild(point)) return child_position; // TODO(yosin): we should do like "closest_child_before" once we have a // case. @@ -818,7 +878,7 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineBox( if (closest_child_before) { descendants.MoveTo(closest_child_before); if (const PositionWithAffinity child_position = - descendants.PositionForPointInChild(point, *closest_child_before)) + descendants.PositionForPointInChild(point)) return child_position; if (closest_child_before->BoxFragment()) { // LayoutViewHitTest.HitTestHorizontal "Top-right corner (outside) of div" @@ -827,36 +887,22 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineBox( } } - if (container->Type() == NGFragmentItem::kLine) { - // There are no inline items to hit in this line box, e.g. <span> with - // size and border. We try in lines before |this| line in the block. - // See editing/selection/last-empty-inline.html - NGInlineCursor cursor; - cursor.MoveTo(*this); - const PhysicalOffset point_in_line = - point - Current().OffsetInContainerBlock(); - for (;;) { - cursor.MoveToPreviousLine(); - if (!cursor) - break; - const PhysicalOffset adjusted_point = - point_in_line + cursor.Current().OffsetInContainerBlock(); - if (auto position = cursor.PositionForPointInInlineBox(adjusted_point)) - return position; - } - } - return PositionWithAffinity(); } PositionWithAffinity NGInlineCursor::PositionForPointInChild( - const PhysicalOffset& point, - const NGFragmentItem& child_item) const { - DCHECK_EQ(&child_item, CurrentItem()); + const PhysicalOffset& point_in_container) const { + if (auto* paint_fragment = CurrentPaintFragment()) { + const PhysicalOffset point_in_child = + point_in_container - paint_fragment->OffsetInContainerBlock(); + return paint_fragment->PositionForPoint(point_in_child); + } + DCHECK(CurrentItem()); + const NGFragmentItem& child_item = *CurrentItem(); switch (child_item.Type()) { case NGFragmentItem::kText: return child_item.PositionForPointInText( - point - child_item.OffsetInContainerBlock(), *this); + point_in_container - child_item.OffsetInContainerBlock(), *this); case NGFragmentItem::kGeneratedText: break; case NGFragmentItem::kBox: @@ -867,9 +913,9 @@ PositionWithAffinity NGInlineCursor::PositionForPointInChild( // can utilize LayoutBlock::PositionForPoint() that resolves the // position in block layout. // TODO(xiaochengh): Don't fallback to legacy for NG block layout. - if (box_fragment->IsBlockFlow() || box_fragment->IsLegacyLayoutRoot()) { + if (!box_fragment->IsInlineBox()) { return child_item.GetLayoutObject()->PositionForPoint( - point - child_item.OffsetInContainerBlock()); + point_in_container - child_item.OffsetInContainerBlock()); } } else { // |LayoutInline| used to be culled. @@ -883,6 +929,26 @@ PositionWithAffinity NGInlineCursor::PositionForPointInChild( return PositionWithAffinity(); } +PositionWithAffinity NGInlineCursor::PositionForStartOfLine() const { + DCHECK(Current().IsLineBox()); + const PhysicalOffset point_in_line = Current().LineStartPoint(); + if (IsItemCursor()) { + return PositionForPointInInlineBox(point_in_line + + Current().OffsetInContainerBlock()); + } + return CurrentPaintFragment()->PositionForPoint(point_in_line); +} + +PositionWithAffinity NGInlineCursor::PositionForEndOfLine() const { + DCHECK(Current().IsLineBox()); + const PhysicalOffset point_in_line = Current().LineEndPoint(); + if (IsItemCursor()) { + return PositionForPointInInlineBox(point_in_line + + Current().OffsetInContainerBlock()); + } + return CurrentPaintFragment()->PositionForPoint(point_in_line); +} + void NGInlineCursor::MoveTo(const NGInlineCursorPosition& position) { CheckValid(position); current_ = position; @@ -913,7 +979,7 @@ NGInlineCursor::ItemsSpan::iterator NGInlineCursor::SlowFirstItemIteratorFor( const LayoutObject& layout_object, const ItemsSpan& items) { for (ItemsSpan::iterator iter = items.begin(); iter != items.end(); ++iter) { - if ((*iter)->GetLayoutObject() == &layout_object) + if (iter->GetLayoutObject() == &layout_object) return iter; } return items.end(); @@ -992,7 +1058,7 @@ bool NGInlineCursor::IsAtFirst() const { if (const NGPaintFragment* paint_fragment = Current().PaintFragment()) return paint_fragment == root_paint_fragment_->FirstChild(); if (const NGFragmentItem* item = Current().Item()) - return item == items_.front().get(); + return item == &items_.front(); return false; } @@ -1022,7 +1088,7 @@ void NGInlineCursor::MoveToFirstLine() { if (IsItemCursor()) { auto iter = std::find_if( items_.begin(), items_.end(), - [](const auto& item) { return item->Type() == NGFragmentItem::kLine; }); + [](const auto& item) { return item.Type() == NGFragmentItem::kLine; }); if (iter != items_.end()) { MoveToItem(iter); return; @@ -1058,7 +1124,7 @@ void NGInlineCursor::MoveToLastLine() { DCHECK(IsItemCursor()); auto iter = std::find_if( items_.rbegin(), items_.rend(), - [](const auto& item) { return item->Type() == NGFragmentItem::kLine; }); + [](const auto& item) { return item.Type() == NGFragmentItem::kLine; }); if (iter != items_.rend()) MoveToItem(std::next(iter).base()); else @@ -1198,6 +1264,15 @@ bool NGInlineCursor::TryToMoveToFirstChild() { return true; } +bool NGInlineCursor::TryToMoveToFirstInlineLeafChild() { + while (IsNotNull()) { + if (Current().IsInlineLeaf()) + return true; + MoveToNext(); + } + return false; +} + bool NGInlineCursor::TryToMoveToLastChild() { if (!Current().HasChildren()) return false; @@ -1226,7 +1301,7 @@ void NGInlineCursor::MoveToNextItem() { return; DCHECK(current_.item_iter_ != items_.end()); if (++current_.item_iter_ != items_.end()) { - current_.item_ = current_.item_iter_->get(); + current_.item_ = &*current_.item_iter_; return; } MakeNull(); @@ -1250,7 +1325,7 @@ void NGInlineCursor::MoveToPreviousItem() { if (current_.item_iter_ == items_.begin()) return MakeNull(); --current_.item_iter_; - current_.item_ = current_.item_iter_->get(); + current_.item_ = &*current_.item_iter_; } void NGInlineCursor::MoveToParentPaintFragment() { @@ -1318,46 +1393,79 @@ void NGInlineCursor::MoveToPreviousSiblingPaintFragment() { void NGInlineCursor::MoveTo(const LayoutObject& layout_object) { DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()); - DCHECK(!layout_object.IsFloatingOrOutOfFlowPositioned()); - // If this cursor is rootless, find the root of the inline formatting context. - bool had_root = true; - if (!HasRoot()) { - had_root = false; - const LayoutBlockFlow& root = *layout_object.RootInlineFormattingContext(); - DCHECK(&root); - SetRoot(root); + DCHECK(!layout_object.IsOutOfFlowPositioned()); + + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + // If this cursor is rootless, find the root of the inline formatting + // context. if (!HasRoot()) { - if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - MakeNull(); + const LayoutBlockFlow& root = + *layout_object.RootInlineFormattingContext(); + DCHECK(&root); + SetRoot(root); + if (!HasRoot()) { + const auto fragments = + NGPaintFragment::InlineFragmentsFor(&layout_object); + if (!fragments.IsInLayoutNGInlineFormattingContext() || + fragments.IsEmpty()) + return MakeNull(); + // external/wpt/css/css-scroll-anchoring/text-anchor-in-vertical-rl.html + // reaches here. + root_paint_fragment_ = fragments.front().Root(); + } + DCHECK(HasRoot()); + } + + const auto fragments = NGPaintFragment::InlineFragmentsFor(&layout_object); + if (!fragments.IsEmpty()) { + // If |this| is IFC root, just move to the first fragment. + if (!root_paint_fragment_->Parent()) { + DCHECK(fragments.front().IsDescendantOfNotSelf(*root_paint_fragment_)); + MoveTo(fragments.front()); return; } - const auto fragments = - NGPaintFragment::InlineFragmentsFor(&layout_object); - if (!fragments.IsInLayoutNGInlineFormattingContext() || - fragments.IsEmpty()) - return MakeNull(); - // external/wpt/css/css-scroll-anchoring/text-anchor-in-vertical-rl.html - // reaches here. - root_paint_fragment_ = fragments.front().Root(); + // If |this| is limited, find the first fragment in the range. + for (const auto* fragment : fragments) { + if (fragment->IsDescendantOfNotSelf(*root_paint_fragment_)) { + MoveTo(*fragment); + return; + } + } } + return MakeNull(); } - if (fragment_items_) { - wtf_size_t item_index = layout_object.FirstInlineFragmentItemIndex(); - if (!item_index) { - DCHECK_EQ(SlowFirstItemIndexFor(layout_object, fragment_items_->Items()), - fragment_items_->Size()); + + // If this cursor is rootless, find the root of the inline formatting context. + if (!HasRoot()) { + const LayoutBlockFlow* root = layout_object.RootInlineFormattingContext(); + DCHECK(root); + const NGFragmentItems* fragment_items = root->FragmentItems(); + if (UNLIKELY(!fragment_items)) { MakeNull(); return; } - // |FirstInlineFragmentItemIndex| is 1-based. Convert to 0-based index. - --item_index; + SetRoot(*fragment_items); + DCHECK(HasRoot()); + } + + wtf_size_t item_index = layout_object.FirstInlineFragmentItemIndex(); + if (UNLIKELY(!item_index)) { DCHECK_EQ(SlowFirstItemIndexFor(layout_object, fragment_items_->Items()), - item_index); + fragment_items_->Size()); + MakeNull(); + return; + } + + // |FirstInlineFragmentItemIndex| is 1-based. Convert to 0-based index. + --item_index; + DCHECK_EQ(SlowFirstItemIndexFor(layout_object, fragment_items_->Items()), + item_index); - // Skip items before |items_|, in case |this| is part of IFC. + // Skip items before |items_|, in case |this| is part of IFC. + if (UNLIKELY(!fragment_items_->Equals(items_))) { const wtf_size_t span_begin_item_index = SpanBeginItemIndex(); - while (item_index < span_begin_item_index) { - const NGFragmentItem& item = *fragment_items_->Items()[item_index]; + while (UNLIKELY(item_index < span_begin_item_index)) { + const NGFragmentItem& item = fragment_items_->Items()[item_index]; const wtf_size_t next_delta = item.DeltaToNextForSameLayoutObject(); if (!next_delta) { MakeNull(); @@ -1365,21 +1473,15 @@ void NGInlineCursor::MoveTo(const LayoutObject& layout_object) { } item_index += next_delta; } - if (item_index >= span_begin_item_index + items_.size()) { + if (UNLIKELY(item_index >= span_begin_item_index + items_.size())) { MakeNull(); return; } - - const wtf_size_t span_index = item_index - span_begin_item_index; - DCHECK_LT(span_index, items_.size()); - return MoveToItem(items_.begin() + span_index); - } - if (root_paint_fragment_) { - const auto fragments = NGPaintFragment::InlineFragmentsFor(&layout_object); - if (!fragments.IsInLayoutNGInlineFormattingContext() || fragments.IsEmpty()) - return MakeNull(); - return MoveTo(fragments.front()); + item_index -= span_begin_item_index; } + + DCHECK_LT(item_index, items_.size()); + current_.Set(items_.begin() + item_index); } void NGInlineCursor::MoveToIncludingCulledInline( @@ -1412,18 +1514,34 @@ void NGInlineCursor::MoveToNextForSameLayoutObject() { if (current_.paint_fragment_) { if (auto* paint_fragment = current_.paint_fragment_->NextForSameLayoutObject()) { - // |paint_fragment| can be in another fragment tree rooted by - // |root_paint_fragment_|, e.g. "multicol-span-all-restyle-002.html" - root_paint_fragment_ = paint_fragment->Root(); - return MoveTo(*paint_fragment); + if (!root_paint_fragment_->Parent()) { + // |paint_fragment| can be in another fragment tree rooted by + // |root_paint_fragment_|, e.g. "multicol-span-all-restyle-002.html" + root_paint_fragment_ = paint_fragment->Root(); + MoveTo(*paint_fragment); + return; + } + // If |this| is limited, make sure the result is in the range. + if (paint_fragment->IsDescendantOfNotSelf(*root_paint_fragment_)) { + MoveTo(*paint_fragment); + return; + } } return MakeNull(); } if (current_.item_) { const wtf_size_t delta = current_.item_->DeltaToNextForSameLayoutObject(); - if (delta == 0u) - return MakeNull(); - return MoveToItem(current_.item_iter_ + delta); + if (delta) { + // Check the next item is in |items_| because |delta| can be beyond + // |end()| if |this| is limited. + const wtf_size_t delta_to_end = items_.end() - current_.item_iter_; + if (delta < delta_to_end) { + MoveToItem(current_.item_iter_ + delta); + return; + } + DCHECK(!fragment_items_->Equals(items_)); + } + MakeNull(); } } @@ -1458,10 +1576,8 @@ NGInlineBackwardCursor::NGInlineBackwardCursor(const NGInlineCursor& cursor) sibling.MoveToNextSkippingChildren()) sibling_item_iterators_.push_back(sibling.Current().item_iter_); current_index_ = sibling_item_iterators_.size(); - if (current_index_) { - current_.item_iter_ = sibling_item_iterators_[--current_index_]; - current_.item_ = current_.item_iter_->get(); - } + if (current_index_) + current_.Set(sibling_item_iterators_[--current_index_]); return; } DCHECK(!cursor); @@ -1486,8 +1602,7 @@ void NGInlineBackwardCursor::MoveToPreviousSibling() { return; } if (current_.item_) { - current_.item_iter_ = sibling_item_iterators_[--current_index_]; - current_.item_ = current_.item_iter_->get(); + current_.Set(sibling_item_iterators_[--current_index_]); return; } NOTREACHED(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h index 6e2b0d842c2..7bd011eb716 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h @@ -10,6 +10,7 @@ #include "base/containers/span.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/inline/ng_fragment_items.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h" #include "third_party/blink/renderer/platform/text/text_direction.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" @@ -43,7 +44,7 @@ struct PhysicalSize; // is faster than moving to |NGFragmentItem|. class CORE_EXPORT NGInlineCursorPosition { public: - using ItemsSpan = base::span<const scoped_refptr<const NGFragmentItem>>; + using ItemsSpan = NGFragmentItems::Span; const NGPaintFragment* PaintFragment() const { return paint_fragment_; } const NGFragmentItem* Item() const { return item_; } @@ -109,6 +110,7 @@ class CORE_EXPORT NGInlineCursorPosition { LayoutObject* GetMutableLayoutObject() const; const Node* GetNode() const; const DisplayItemClient* GetDisplayItemClient() const; + wtf_size_t FragmentId() const; // True if fragment at the current position can have children. bool CanHaveChildren() const; @@ -154,6 +156,10 @@ class CORE_EXPORT NGInlineCursorPosition { // line. TextDirection BaseDirection() const; + TextDirection ResolvedOrBaseDirection() const { + return IsLineBox() ? BaseDirection() : ResolvedDirection(); + } + // True if the current position is text or atomic inline box. // Note: Because of this function is used for caret rect, hit testing, etc, // this function returns false for hidden for paint, text overflow ellipsis, @@ -164,12 +170,25 @@ class CORE_EXPORT NGInlineCursorPosition { // other than line. bool HasSoftWrapToNextLine() const; - // Returns a point at the visual start/end of the line. + // Returns a point at the visual start/end of the line. (0, 0) is left-top of + // the line. // Encapsulates the handling of text direction and writing mode. PhysicalOffset LineStartPoint() const; PhysicalOffset LineEndPoint() const; + // LogicalRect/PhysicalRect conversions + // |logical_rect| and |physical_rect| are converted with |Size()| as + // "outer size". + LogicalRect ConvertChildToLogical(const PhysicalRect& physical_rect) const; + PhysicalRect ConvertChildToPhysical(const LogicalRect& logical_rect) const; + private: + void Set(const ItemsSpan::iterator& iter) { + DCHECK(!paint_fragment_); + item_iter_ = iter; + item_ = &*iter; + } + void Clear() { paint_fragment_ = nullptr; item_ = nullptr; @@ -197,7 +216,7 @@ class CORE_EXPORT NGInlineCursor { STACK_ALLOCATED(); public: - using ItemsSpan = base::span<const scoped_refptr<const NGFragmentItem>>; + using ItemsSpan = NGFragmentItems::Span; explicit NGInlineCursor(const LayoutBlockFlow& block_flow); explicit NGInlineCursor(const NGFragmentItems& items); @@ -296,6 +315,20 @@ class CORE_EXPORT NGInlineCursor { PositionWithAffinity PositionForPointInInlineBox( const PhysicalOffset& point) const; + // Returns |PositionWithAffinity| in current position at x-coordinate of + // |point_in_container| for horizontal writing mode, or y-coordinate of + // |point_in_container| for vertical writing mode. + // Note: Even if |point_in_container| is outside of an item of current + // position, this function returns boundary position of an item. + // Note: This function is used for locating caret at same x/y-coordinate as + // previous caret after line up/down. + PositionWithAffinity PositionForPointInChild( + const PhysicalOffset& point_in_container) const; + + // Returns first/last position of |this| line. |this| should be line box. + PositionWithAffinity PositionForStartOfLine() const; + PositionWithAffinity PositionForEndOfLine() const; + // // Functions to move the current position. // @@ -376,6 +409,9 @@ class CORE_EXPORT NGInlineCursor { // Returns true if the current position moves to first child. bool TryToMoveToFirstChild(); + // Returns true if the current position moves to first inline leaf child. + bool TryToMoveToFirstInlineLeafChild(); + // Returns true if the current position moves to last child. bool TryToMoveToLastChild(); @@ -434,10 +470,6 @@ class CORE_EXPORT NGInlineCursor { wtf_size_t SpanBeginItemIndex() const; wtf_size_t SpanIndexFromItemIndex(unsigned index) const; - PositionWithAffinity PositionForPointInChild( - const PhysicalOffset& point, - const NGFragmentItem& child_item) const; - NGInlineCursorPosition current_; ItemsSpan items_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc index 580588369e4..896d6a9e60c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc @@ -628,6 +628,46 @@ TEST_P(NGInlineCursorTest, NextForSameLayoutObject) { EXPECT_THAT(list, ElementsAre("abc", "", "def", "", "ghi")); } +// Test |NextForSameLayoutObject| with limit range set. +TEST_P(NGInlineCursorTest, NextForSameLayoutObjectWithRange) { + // In this snippet, `<span>` wraps to 3 lines, and that there are 3 fragments + // for `<span>`. + SetBodyInnerHTML(R"HTML( + <style> + div { + font-size: 10px; + width: 5ch; + } + span { + background: orange; + } + </style> + <div id="root"> + <span id="span1"> + 1111 + 2222 + 3333 + </span> + </div> + )HTML"); + LayoutBlockFlow* root = + To<LayoutBlockFlow>(GetLayoutObjectByElementId("root")); + NGInlineCursor cursor(*root); + cursor.MoveToFirstLine(); + cursor.MoveToNextLine(); + NGInlineCursor line2 = cursor.CursorForDescendants(); + + // Now |line2| is limited to the 2nd line. There should be only one framgnet + // for `<span>` if we search using `line2`. + LayoutObject* span1 = GetLayoutObjectByElementId("span1"); + wtf_size_t count = 0; + for (line2.MoveTo(*span1); line2; line2.MoveToNextForSameLayoutObject()) { + DCHECK_EQ(line2.Current().GetLayoutObject(), span1); + ++count; + } + EXPECT_EQ(count, 1u); +} + TEST_P(NGInlineCursorTest, Sibling) { // TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline. InsertStyleElement("a, b { background: gray; }"); @@ -698,6 +738,152 @@ TEST_P(NGInlineCursorTest, EmptyOutOfFlow) { EXPECT_THAT(list, ElementsAre()); } +TEST_P(NGInlineCursorTest, PositionForPointInChildHorizontalLTR) { + LoadAhem(); + InsertStyleElement( + "p {" + "direction: ltr;" + "font: 10px/20px Ahem;" + "padding: 10px;" + "writing-mode: horizontal-tb;" + "}"); + NGInlineCursor cursor = SetupCursor("<p id=root>ab</p>"); + const auto& text = *To<Text>(GetElementById("root")->firstChild()); + ASSERT_TRUE(cursor.Current().IsLineBox()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(10, 10), PhysicalSize(20, 20)), + cursor.Current().RectInContainerBlock()); + + cursor.MoveTo(*text.GetLayoutObject()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(10, 15), PhysicalSize(20, 10)), + cursor.Current().RectInContainerBlock()); + const PhysicalOffset left_top = cursor.Current().OffsetInContainerBlock(); + + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(-5, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top)); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(5, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(10, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(15, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(20, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(25, 0))); +} + +TEST_P(NGInlineCursorTest, PositionForPointInChildHorizontalRTL) { + LoadAhem(); + InsertStyleElement( + "p {" + "direction: rtl;" + "font: 10px/20px Ahem;" + "padding: 10px;" + "writing-mode: horizontal-tb;" + "}"); + NGInlineCursor cursor = SetupCursor("<p id=root><bdo dir=rtl>AB</bdo></p>"); + const auto& text = + *To<Text>(GetElementById("root")->firstChild()->firstChild()); + ASSERT_TRUE(cursor.Current().IsLineBox()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(754, 10), PhysicalSize(20, 20)), + cursor.Current().RectInContainerBlock()); + + cursor.MoveTo(*text.GetLayoutObject()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(754, 15), PhysicalSize(20, 10)), + cursor.Current().RectInContainerBlock()); + const PhysicalOffset left_top = cursor.Current().OffsetInContainerBlock(); + + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(-5, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top)); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(5, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(10, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(15, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(20, 0))); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(25, 0))); +} + +TEST_P(NGInlineCursorTest, PositionForPointInChildVerticalLTR) { + LoadAhem(); + InsertStyleElement( + "p {" + "direction: ltr;" + "font: 10px/20px Ahem;" + "padding: 10px;" + "writing-mode: vertical-lr;" + "}"); + NGInlineCursor cursor = SetupCursor("<p id=root>ab</p>"); + const auto& text = *To<Text>(GetElementById("root")->firstChild()); + ASSERT_TRUE(cursor.Current().IsLineBox()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(10, 10), PhysicalSize(20, 20)), + cursor.Current().RectInContainerBlock()); + + cursor.MoveTo(*text.GetLayoutObject()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(15, 10), PhysicalSize(10, 20)), + cursor.Current().RectInContainerBlock()); + const PhysicalOffset left_top = cursor.Current().OffsetInContainerBlock(); + + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, -5))); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top)); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 5))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 10))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 15))); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 20))); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 25))); +} + +TEST_P(NGInlineCursorTest, PositionForPointInChildVerticalRTL) { + LoadAhem(); + InsertStyleElement( + "p {" + "direction: rtl;" + "font: 10px/20px Ahem;" + "padding: 10px;" + "writing-mode: vertical-rl;" + "}"); + NGInlineCursor cursor = SetupCursor("<p id=root><bdo dir=rtl>AB</bdo></p>"); + const auto& text = + *To<Text>(GetElementById("root")->firstChild()->firstChild()); + ASSERT_TRUE(cursor.Current().IsLineBox()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(10, 10), PhysicalSize(20, 20)), + cursor.Current().RectInContainerBlock()); + + cursor.MoveTo(*text.GetLayoutObject()); + EXPECT_EQ(PhysicalRect(PhysicalOffset(15, 10), PhysicalSize(10, 20)), + cursor.Current().RectInContainerBlock()); + const PhysicalOffset left_top = cursor.Current().OffsetInContainerBlock(); + + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, -5))); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top)); + EXPECT_EQ(PositionWithAffinity(Position(text, 2), TextAffinity::kUpstream), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 5))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 10))); + EXPECT_EQ(PositionWithAffinity(Position(text, 1)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 15))); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 20))); + EXPECT_EQ(PositionWithAffinity(Position(text, 0)), + cursor.PositionForPointInChild(left_top + PhysicalOffset(0, 25))); +} + TEST_P(NGInlineCursorTest, Previous) { // TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline. InsertStyleElement("b { background: gray; }"); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h index 531527710c8..d7aa21969b1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h @@ -115,6 +115,8 @@ class CORE_EXPORT NGInlineItem { unsigned EndOffset() const { return end_offset_; } unsigned Length() const { return end_offset_ - start_offset_; } + bool IsValidOffset(unsigned offset) const; + TextDirection Direction() const { return DirectionFromLevel(BidiLevel()); } UBiDiLevel BidiLevel() const { return static_cast<UBiDiLevel>(bidi_level_); } // Resolved bidi level for the reordering algorithm. Certain items have @@ -131,6 +133,9 @@ class CORE_EXPORT NGInlineItem { bool IsImage() const { return GetLayoutObject() && GetLayoutObject()->IsLayoutImage(); } + bool IsRubyRun() const { + return GetLayoutObject() && GetLayoutObject()->IsRubyRun(); + } void SetOffset(unsigned start, unsigned end) { DCHECK_GE(end, start); @@ -184,6 +189,10 @@ class CORE_EXPORT NGInlineItem { (Type() == NGInlineItem::kControl && type == kCollapsible)); end_collapse_type_ = type; } + bool IsCollapsibleSpaceOnly() const { + return Type() == NGInlineItem::kText && + end_collapse_type_ == kCollapsible && Length() == 1u; + } // True if this item was generated (not in DOM). // NGInlineItemsBuilder may generate break opportunitites to express the @@ -229,7 +238,7 @@ class CORE_EXPORT NGInlineItem { unsigned end_offset, UBiDiLevel); - void AssertOffset(unsigned offset) const; + void AssertOffset(unsigned offset) const { DCHECK(IsValidOffset(offset)); } void AssertEndOffset(unsigned offset) const; String ToString() const; @@ -257,9 +266,9 @@ class CORE_EXPORT NGInlineItem { friend class NGInlineNodeDataEditor; }; -inline void NGInlineItem::AssertOffset(unsigned offset) const { - DCHECK((offset >= start_offset_ && offset < end_offset_) || - (offset == start_offset_ && start_offset_ == end_offset_)); +inline bool NGInlineItem::IsValidOffset(unsigned offset) const { + return (offset >= start_offset_ && offset < end_offset_) || + (start_offset_ == end_offset_ && offset == start_offset_); } inline void NGInlineItem::AssertEndOffset(unsigned offset) const { @@ -287,6 +296,10 @@ struct CORE_EXPORT NGInlineItemsData { // The DOM to text content offset mapping of this inline node. std::unique_ptr<NGOffsetMapping> offset_mapping; + bool IsValidOffset(unsigned index, unsigned offset) const { + return index < items.size() && items[index].IsValidOffset(offset); + } + void AssertOffset(unsigned index, unsigned offset) const { items[index].AssertOffset(offset); } 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 4fa2ff96298..592f46b652d 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 @@ -10,20 +10,17 @@ namespace blink { -NGInlineItemResult::NGInlineItemResult() - : item(nullptr), item_index(0), start_offset(0), end_offset(0) {} +NGInlineItemResult::NGInlineItemResult() : item(nullptr), item_index(0) {} NGInlineItemResult::NGInlineItemResult(const NGInlineItem* item, unsigned index, - unsigned start, - unsigned end, + const NGTextOffset& text_offset, bool break_anywhere_if_overflow, bool should_create_line_box, bool has_unpositioned_floats) : item(item), item_index(index), - start_offset(start), - end_offset(end), + text_offset(text_offset), break_anywhere_if_overflow(break_anywhere_if_overflow), should_create_line_box(should_create_line_box), has_unpositioned_floats(has_unpositioned_floats) {} @@ -33,16 +30,31 @@ void NGLineInfo::SetLineStyle(const NGInlineNode& node, bool use_first_line_style) { use_first_line_style_ = use_first_line_style; items_data_ = &items_data; - line_style_ = node.GetLayoutBox()->Style(use_first_line_style_); + const LayoutBox* box = node.GetLayoutBox(); + line_style_ = box->Style(use_first_line_style_); needs_accurate_end_position_ = ComputeNeedsAccurateEndPosition(); + is_ruby_base_ = box->IsRubyBase(); + is_ruby_text_ = box->IsRubyText(); +} + +ETextAlign NGLineInfo::GetTextAlign(bool is_last_line) const { + // See LayoutRubyBase::TextAlignmentForLine(). + if (is_ruby_base_) + return ETextAlign::kJustify; + + // See LayoutRubyText::TextAlignmentForLine(). + if (is_ruby_text_ && LineStyle().GetTextAlign() == + ComputedStyleInitialValues::InitialTextAlign()) + return ETextAlign::kJustify; + + return LineStyle().GetTextAlign(is_last_line); } bool NGLineInfo::ComputeNeedsAccurateEndPosition() const { // Some 'text-align' values need accurate end position. At this point, we // don't know if this is the last line or not, and thus we don't know whether // 'text-align' is used or 'text-align-last' is used. - const ComputedStyle& line_style = LineStyle(); - switch (line_style.GetTextAlign()) { + switch (GetTextAlign()) { case ETextAlign::kStart: break; case ETextAlign::kEnd: @@ -61,7 +73,16 @@ bool NGLineInfo::ComputeNeedsAccurateEndPosition() const { return true; break; } - switch (line_style.TextAlignLast()) { + ETextAlignLast align_last = LineStyle().TextAlignLast(); + if (is_ruby_base_) { + // See LayoutRubyBase::TextAlignmentForLine(). + align_last = ETextAlignLast::kJustify; + } else if (is_ruby_text_ && + align_last == ComputedStyleInitialValues::InitialTextAlignLast()) { + // See LayoutRubyText::TextAlignmentForLine(). + align_last = ETextAlignLast::kJustify; + } + switch (align_last) { case ETextAlignLast::kStart: case ETextAlignLast::kAuto: return false; @@ -85,13 +106,13 @@ bool NGLineInfo::ComputeNeedsAccurateEndPosition() const { void NGInlineItemResult::CheckConsistency(bool allow_null_shape_result) const { DCHECK(item); if (item->Type() == NGInlineItem::kText) { - DCHECK_LT(start_offset, end_offset); + text_offset.AssertNotEmpty(); if (allow_null_shape_result && !shape_result) return; DCHECK(shape_result); - DCHECK_EQ(end_offset - start_offset, shape_result->NumCharacters()); - DCHECK_EQ(start_offset, shape_result->StartIndex()); - DCHECK_EQ(end_offset, shape_result->EndIndex()); + DCHECK_EQ(Length(), shape_result->NumCharacters()); + DCHECK_EQ(StartOffset(), shape_result->StartIndex()); + DCHECK_EQ(EndOffset(), shape_result->EndIndex()); } } #endif @@ -105,7 +126,7 @@ unsigned NGLineInfo::InflowEndOffset() const { if (item.Type() == NGInlineItem::kText || item.Type() == NGInlineItem::kControl || item.Type() == NGInlineItem::kAtomicInline) - return item_result.end_offset; + return item_result.EndOffset(); } return StartOffset(); } @@ -133,7 +154,7 @@ bool NGLineInfo::ShouldHangTrailingSpaces() const { } void NGLineInfo::UpdateTextAlign() { - text_align_ = line_style_->GetTextAlign(IsLastLine()); + text_align_ = GetTextAlign(IsLastLine()); if (HasTrailingSpaces() && ShouldHangTrailingSpaces()) { hang_width_ = ComputeTrailingSpaceWidth(&end_offset_for_justify_); @@ -175,18 +196,18 @@ LayoutUnit NGLineInfo::ComputeTrailingSpaceWidth( // The last text item may contain trailing spaces if this is a last line, // has a forced break, or is 'white-space: pre'. - unsigned end_offset = item_result.end_offset; + unsigned end_offset = item_result.EndOffset(); DCHECK(end_offset); if (item.Type() == NGInlineItem::kText) { const String& text = items_data_->text_content; if (end_offset && text[end_offset - 1] == kSpaceCharacter) { do { --end_offset; - } while (end_offset > item_result.start_offset && + } while (end_offset > item_result.StartOffset() && text[end_offset - 1] == kSpaceCharacter); // If all characters in this item_result are spaces, check next item. - if (end_offset == item_result.start_offset) { + if (end_offset == item_result.StartOffset()) { trailing_spaces_width += item_result.inline_size; continue; } 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 66adbd9e850..eb31318da41 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 @@ -32,11 +32,10 @@ struct CORE_EXPORT NGInlineItemResult { DISALLOW_NEW(); public: - NGTextOffset TextOffset() const { return {start_offset, end_offset}; } - unsigned Length() const { - DCHECK_GT(end_offset, start_offset); - return end_offset - start_offset; - } + const NGTextOffset& TextOffset() const { return text_offset; } + unsigned StartOffset() const { return text_offset.start; } + unsigned EndOffset() const { return text_offset.end; } + unsigned Length() const { return text_offset.Length(); } LayoutUnit HyphenInlineSize() const { return hyphen_shape_result->SnappedWidth().ClampNegativeToZero(); @@ -52,12 +51,15 @@ struct CORE_EXPORT NGInlineItemResult { unsigned item_index; // The range of text content for this item. - unsigned start_offset; - unsigned end_offset; + NGTextOffset text_offset; // Inline size of this item. LayoutUnit inline_size; + // Pending inline-end overhang amount for RubyRun. + // This is committed if a following item meets conditions. + LayoutUnit pending_end_overhang; + // ShapeResult for text items. Maybe different from NGInlineItem if re-shape // is needed in the line breaker. scoped_refptr<const ShapeResultView> shape_result; @@ -129,8 +131,7 @@ struct CORE_EXPORT NGInlineItemResult { NGInlineItemResult(); NGInlineItemResult(const NGInlineItem*, unsigned index, - unsigned start, - unsigned end, + const NGTextOffset& text_offset, bool break_anywhere_if_overflow, bool should_create_line_box, bool has_unpositioned_floats); @@ -253,6 +254,7 @@ class CORE_EXPORT NGLineInfo { bool NeedsAccurateEndPosition() const { return needs_accurate_end_position_; } private: + ETextAlign GetTextAlign(bool is_last_line = false) const; bool ComputeNeedsAccurateEndPosition() const; // The width of preserved trailing spaces. @@ -283,6 +285,8 @@ class CORE_EXPORT NGLineInfo { bool has_overflow_ = false; bool has_trailing_spaces_ = false; bool needs_accurate_end_position_ = false; + bool is_ruby_base_ = false; + bool is_ruby_text_ = false; }; } // namespace blink 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 6e66ff045ba..69eaf05758d 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 @@ -894,6 +894,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendAtomicInline( RestoreTrailingCollapsibleSpaceIfRemoved(); Append(NGInlineItem::kAtomicInline, kObjectReplacementCharacter, layout_object); + has_ruby_ = has_ruby_ || layout_object->IsRubyRun(); // 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 @@ -980,7 +981,7 @@ void NGInlineItemsBuilderTemplate< // 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()|. + // |AppendEmptyTextItem()|. item->SetEndOffset(item->EndOffset() - 1); item->SetEndCollapseType(NGInlineItem::kCollapsed); @@ -1188,8 +1189,20 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ExitInline( break; } DCHECK_GT(i, current_box->item_index); - if (!item.IsEmptyItem()) - break; + if (item.IsEmptyItem()) { + // float, abspos, collapsed space(<div>ab <span> </span>). + // See editing/caret/empty_inlines.html + // See also [1] for empty line box. + // [1] https://drafts.csswg.org/css2/visuren.html#phantom-line-box + continue; + } + if (item.IsCollapsibleSpaceOnly()) { + // Because we can't collapse trailing spaces until next node, we + // create box fragment for it: <div>ab<span> </span></div> + // See editing/selection/mixed-editability-10.html + continue; + } + break; } } @@ -1226,6 +1239,11 @@ void NGInlineItemsBuilderTemplate< // |SegmentText()| will analyze the text and reset |is_bidi_enabled_| if it // doesn't contain any RTL characters. data->is_bidi_enabled_ = MayBeBidiEnabled(); + // Note: Even if |IsEmptyInline()| is true, |text_| isn't empty, e.g. it + // holds U+FFFC(ORC) for float or abspos. + data->has_line_even_if_empty_ = + IsEmptyInline() && block_flow_->HasLineIfEmpty(); + data->has_ruby_ = has_ruby_; data->is_empty_inline_ = IsEmptyInline(); data->is_block_level_ = IsBlockLevel(); data->changes_may_affect_earlier_lines_ = ChangesMayAffectEarlierLines(); 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 a8e6d3f5907..5178c1ebc01 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 @@ -44,10 +44,13 @@ class NGInlineItemsBuilderTemplate { public: // Create a builder that appends items to |items|. - explicit NGInlineItemsBuilderTemplate(Vector<NGInlineItem>* items) - : items_(items) {} + NGInlineItemsBuilderTemplate(LayoutBlockFlow* block_flow, + Vector<NGInlineItem>* items) + : block_flow_(block_flow), items_(items) {} ~NGInlineItemsBuilderTemplate(); + LayoutBlockFlow* GetLayoutBlockFlow() const { return block_flow_; } + String ToString(); // Returns whether the items contain any Bidi controls. @@ -146,6 +149,7 @@ class NGInlineItemsBuilderTemplate { private: static bool NeedsBoxInfo(); + LayoutBlockFlow* const block_flow_; Vector<NGInlineItem>* items_; StringBuilder text_; @@ -177,6 +181,7 @@ class NGInlineItemsBuilderTemplate { Vector<BidiContext> bidi_context_; bool has_bidi_controls_ = false; + bool has_ruby_ = false; bool is_empty_inline_ = true; bool is_block_level_ = true; bool changes_may_affect_earlier_lines_ = false; @@ -226,6 +231,8 @@ class NGInlineItemsBuilderTemplate { const ComputedStyle&, LayoutText*, unsigned* start); + + friend class NGInlineItemsBuilderTest; }; template <> 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 77aff76eecc..912b017a631 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 @@ -8,6 +8,7 @@ #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/layout_ng_ruby_run.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -28,6 +29,9 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { void SetUp() override { NGLayoutTest::SetUp(); style_ = ComputedStyle::Create(); + block_flow_ = LayoutBlockFlow::CreateAnonymous(&GetDocument(), style_, + LegacyLayout::kAuto); + anonymous_objects_.push_back(block_flow_); } void TearDown() override { @@ -36,6 +40,8 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { NGLayoutTest::TearDown(); } + LayoutBlockFlow* GetLayoutBlockFlow() const { return block_flow_; } + void SetWhiteSpace(EWhiteSpace whitespace) { style_->SetWhiteSpace(whitespace); } @@ -48,6 +54,10 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { return style; } + bool HasRuby(const NGInlineItemsBuilder& builder) const { + return builder.has_ruby_; + } + void AppendText(const String& text, NGInlineItemsBuilder* builder) { LayoutText* layout_text = LayoutText::CreateEmptyAnonymous( GetDocument(), style_.get(), LegacyLayout::kAuto); @@ -62,6 +72,14 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { builder->AppendAtomicInline(layout_block_flow); } + void AppendRubyRun(NGInlineItemsBuilder* builder) { + LayoutNGRubyRun* ruby_run = new LayoutNGRubyRun(); + ruby_run->SetDocumentForAnonymous(&GetDocument()); + ruby_run->SetStyle(style_); + anonymous_objects_.push_back(ruby_run); + builder->AppendAtomicInline(ruby_run); + } + struct Input { const String text; EWhiteSpace whitespace = EWhiteSpace::kNormal; @@ -71,7 +89,7 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { const String& TestAppend(Vector<Input> inputs) { items_.clear(); Vector<LayoutText*> anonymous_objects; - NGInlineItemsBuilder builder(&items_); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items_); for (Input& input : inputs) { if (!input.layout_text) { input.layout_text = LayoutText::CreateEmptyAnonymous( @@ -122,7 +140,7 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { fake_data.is_bidi_enabled_ = has_bidi_controls; Vector<NGInlineItem> reuse_items; - NGInlineItemsBuilder reuse_builder(&reuse_items); + NGInlineItemsBuilder reuse_builder(GetLayoutBlockFlow(), &reuse_items); for (Input& input : inputs) { // Collect items for this LayoutObject. DCHECK(input.layout_text); @@ -153,6 +171,7 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { EXPECT_EQ(text_, reuse_text); } + LayoutBlockFlow* block_flow_ = nullptr; Vector<NGInlineItem> items_; String text_; scoped_refptr<ComputedStyle> style_; @@ -317,7 +336,7 @@ TEST_F(NGInlineItemsBuilderTest, CollapseEastAsianWidth) { #endif TEST_F(NGInlineItemsBuilderTest, OpaqueToSpaceCollapsing) { - NGInlineItemsBuilder builder(&items_); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items_); AppendText("Hello ", &builder); builder.AppendOpaque(NGInlineItem::kBidiControl, kFirstStrongIsolateCharacter); @@ -329,7 +348,7 @@ TEST_F(NGInlineItemsBuilderTest, OpaqueToSpaceCollapsing) { } TEST_F(NGInlineItemsBuilderTest, CollapseAroundReplacedElement) { - NGInlineItemsBuilder builder(&items_); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items_); AppendText("Hello ", &builder); AppendAtomicInline(&builder); AppendText(" World", &builder); @@ -337,7 +356,7 @@ TEST_F(NGInlineItemsBuilderTest, CollapseAroundReplacedElement) { } TEST_F(NGInlineItemsBuilderTest, CollapseNewlineAfterObject) { - NGInlineItemsBuilder builder(&items_); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items_); AppendAtomicInline(&builder); AppendText("\n", &builder); AppendAtomicInline(&builder); @@ -389,7 +408,7 @@ TEST_F(NGInlineItemsBuilderTest, IgnorablePre) { TEST_F(NGInlineItemsBuilderTest, Empty) { Vector<NGInlineItem> items; - NGInlineItemsBuilder builder(&items); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items); scoped_refptr<ComputedStyle> block_style(ComputedStyle::Create()); builder.EnterBlock(block_style.get()); builder.ExitBlock(); @@ -431,7 +450,7 @@ TEST_F(NGInlineItemsBuilderTest, GenerateBreakOpportunityAfterLeadingSpaces) { TEST_F(NGInlineItemsBuilderTest, BidiBlockOverride) { Vector<NGInlineItem> items; - NGInlineItemsBuilder builder(&items); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items); scoped_refptr<ComputedStyle> block_style(ComputedStyle::Create()); block_style->SetUnicodeBidi(UnicodeBidi::kBidiOverride); block_style->SetDirection(TextDirection::kRtl); @@ -461,7 +480,7 @@ static LayoutInline* CreateLayoutInline( TEST_F(NGInlineItemsBuilderTest, BidiIsolate) { Vector<NGInlineItem> items; - NGInlineItemsBuilder builder(&items); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items); AppendText("Hello ", &builder); LayoutInline* const isolate_rtl = CreateLayoutInline(&GetDocument(), [](ComputedStyle* style) { @@ -486,7 +505,7 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolate) { TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) { Vector<NGInlineItem> items; - NGInlineItemsBuilder builder(&items); + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items); AppendText("Hello ", &builder); LayoutInline* const isolate_override_rtl = CreateLayoutInline(&GetDocument(), [](ComputedStyle* style) { @@ -509,4 +528,26 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) { isolate_override_rtl->Destroy(); } +TEST_F(NGInlineItemsBuilderTest, HasRuby) { + Vector<NGInlineItem> items; + NGInlineItemsBuilder builder(GetLayoutBlockFlow(), &items); + EXPECT_FALSE(HasRuby(builder)) << "has_ruby_ should be false initially."; + + AppendText("Hello ", &builder); + EXPECT_FALSE(HasRuby(builder)) + << "Adding non-AtomicInline should not affect it."; + + AppendAtomicInline(&builder); + EXPECT_FALSE(HasRuby(builder)) + << "Adding non-ruby AtomicInline should not affect it."; + + AppendRubyRun(&builder); + EXPECT_TRUE(HasRuby(builder)) + << "Adding a ruby AtomicInline should set it to true."; + + AppendAtomicInline(&builder); + EXPECT_TRUE(HasRuby(builder)) + << "Adding non-ruby AtomicInline should not clear it."; +} + } // 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 0ab10f3f371..81ead292e07 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 @@ -19,6 +19,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.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_ruby_utils.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h" @@ -63,12 +64,14 @@ NGInlineLayoutAlgorithm::NGInlineLayoutAlgorithm( // lays out in visual order. TextDirection::kLtr, break_token), + line_box_(*context->LogicalLineItems()), box_states_(nullptr), context_(context), baseline_type_(container_builder_.Style().GetFontBaseline()), is_horizontal_writing_mode_( blink::IsHorizontalWritingMode(space.GetWritingMode())) { DCHECK(context); + DCHECK(&line_box_); quirks_mode_ = inline_node.InLineHeightQuirksMode(); } @@ -79,7 +82,7 @@ NGInlineLayoutAlgorithm::~NGInlineLayoutAlgorithm() = default; NGInlineBoxState* NGInlineLayoutAlgorithm::HandleOpenTag( const NGInlineItem& item, const NGInlineItemResult& item_result, - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, NGInlineLayoutStateStack* box_states) const { NGInlineBoxState* box = box_states->OnOpenTag(item, item_result, baseline_type_, line_box); @@ -164,7 +167,7 @@ void NGInlineLayoutAlgorithm::RebuildBoxStates( } // Create box states for tags that are not closed yet. - NGLineBoxFragmentBuilder::ChildList line_box; + NGLogicalLineItems line_box; box_states->OnBeginPlaceItems(line_info.LineStyle(), baseline_type_, quirks_mode_, &line_box); for (const NGInlineItem* item : open_items) { @@ -180,7 +183,7 @@ void NGInlineLayoutAlgorithm::CheckBoxStates( const NGInlineBreakToken* break_token) const { NGInlineLayoutStateStack rebuilt; RebuildBoxStates(line_info, break_token, &rebuilt); - NGLineBoxFragmentBuilder::ChildList line_box; + NGLogicalLineItems line_box; rebuilt.OnBeginPlaceItems(line_info.LineStyle(), baseline_type_, quirks_mode_, &line_box); DCHECK(box_states_); @@ -194,7 +197,8 @@ void NGInlineLayoutAlgorithm::CreateLine( NGExclusionSpace* exclusion_space) { // Needs MutableResults to move ShapeResult out of the NGLineInfo. NGInlineItemResults* line_items = line_info->MutableResults(); - line_box_.resize(0); + // Clear the current line without releasing the buffer. + line_box_.Shrink(0); // Apply justification before placing items, because it affects size/position // of items, which are needed to compute inline static positions. @@ -244,13 +248,16 @@ void NGInlineLayoutAlgorithm::CreateLine( item.TextType() == NGTextType::kSymbolMarker); if (UNLIKELY(item_result.hyphen_shape_result)) { LayoutUnit hyphen_inline_size = item_result.HyphenInlineSize(); - line_box_.AddChild(&item_result, box->text_top, + line_box_.AddChild(item, std::move(item_result.shape_result), + item_result.TextOffset(), box->text_top, item_result.inline_size - hyphen_inline_size, box->text_height, item.BidiLevel()); PlaceHyphen(item_result, hyphen_inline_size, box); } else { - line_box_.AddChild(&item_result, box->text_top, item_result.inline_size, - box->text_height, item.BidiLevel()); + line_box_.AddChild(item, std::move(item_result.shape_result), + item_result.TextOffset(), box->text_top, + item_result.inline_size, box->text_height, + item.BidiLevel()); } has_logical_text_items = true; @@ -259,6 +266,7 @@ void NGInlineLayoutAlgorithm::CreateLine( } else if (item.Type() == NGInlineItem::kControl) { PlaceControlItem(item, *line_info, &item_result, box); + has_logical_text_items = true; } else if (item.Type() == NGInlineItem::kOpenTag) { box = HandleOpenTag(item, item_result, &line_box_, box_states_); } else if (item.Type() == NGInlineItem::kCloseTag) { @@ -319,13 +327,6 @@ void NGInlineLayoutAlgorithm::CreateLine( line_info->AvailableWidth() - line_info->TextIndent() && node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText()) || ConstraintSpace().LinesUntilClamp() == 1)) { - // TODO(kojii): |NGLineTruncator| does not support |Child|-based truncation - // yet, so create |NGPhysicalTextFragment| first. - if (has_logical_text_items) { - line_box_.CreateTextFragments(ConstraintSpace().GetWritingMode(), - line_info->ItemsData().text_content); - has_logical_text_items = false; - } NGLineTruncator truncator(*line_info); auto* input = DynamicTo<HTMLInputElement>(node_.GetLayoutBlockFlow()->GetNode()); @@ -359,16 +360,18 @@ void NGInlineLayoutAlgorithm::CreateLine( container_builder_.SetBfcLineOffset(bfc_line_offset); const NGLineHeightMetrics& line_box_metrics = - box_states_->LineBoxState().metrics; + UNLIKELY(Node().HasLineEvenIfEmpty()) + ? NGLineHeightMetrics(line_info->LineStyle()) + : box_states_->LineBoxState().metrics; // Place out-of-flow positioned objects. - // This adjusts the NGLineBoxFragmentBuilder::Child::offset member to contain + // This adjusts the NGLogicalLineItem::offset member to contain // the static position of the OOF positioned children relative to the linebox. if (has_out_of_flow_positioned_items) PlaceOutOfFlowObjects(*line_info, line_box_metrics); // Place floating objects. - // This adjusts the NGLineBoxFragmentBuilder::Child::offset member to + // This adjusts the NGLogicalLineItem::offset member to // contain the position of the float relative to the linebox. // Additionally it will perform layout on any unpositioned floats which // needed the line height to correctly determine their final position. @@ -377,6 +380,14 @@ void NGInlineLayoutAlgorithm::CreateLine( exclusion_space); } + NGAnnotationMetrics annotation_metrics; + if (Node().HasRuby() && + !RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + annotation_metrics = ComputeAnnotationOverflow(line_box_, line_box_metrics, + -line_box_metrics.ascent, + line_info->LineStyle()); + } + // Create box fragments if needed. After this point forward, |line_box_| is a // tree structure. // The individual children don't move position within the |line_box_|, rather @@ -389,11 +400,6 @@ void NGInlineLayoutAlgorithm::CreateLine( context_->SetItemIndex(line_info->ItemsData().items, line_info->EndItemIndex()); - if (UNLIKELY(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled())) { - NGFragmentItem::Create(&line_box_, line_info->ItemsData().text_content, - ConstraintSpace().GetWritingMode()); - } - // 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()) { @@ -410,12 +416,57 @@ void NGInlineLayoutAlgorithm::CreateLine( // the line box to the line top. line_box_.MoveInBlockDirection(line_box_metrics.ascent); + if (Node().HasRuby() && + RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + annotation_metrics = ComputeAnnotationOverflow( + line_box_, line_box_metrics, LayoutUnit(), line_info->LineStyle()); + } + LayoutUnit annotation_overflow_block_start; + LayoutUnit annotation_overflow_block_end; + LayoutUnit annotation_space_block_start; + LayoutUnit annotation_space_block_end; + if (!IsFlippedLinesWritingMode(line_info->LineStyle().GetWritingMode())) { + annotation_overflow_block_start = annotation_metrics.overflow_over; + annotation_overflow_block_end = annotation_metrics.overflow_under; + annotation_space_block_start = annotation_metrics.space_over; + annotation_space_block_end = annotation_metrics.space_under; + } else { + annotation_overflow_block_start = annotation_metrics.overflow_under; + annotation_overflow_block_end = annotation_metrics.overflow_over; + annotation_space_block_start = annotation_metrics.space_under; + annotation_space_block_end = annotation_metrics.space_over; + } + + LayoutUnit block_offset_shift = annotation_overflow_block_start; + // If the previous line has block-end annotation overflow and this line has + // block-start annotation space, shift up the block offset of this line. + if (ConstraintSpace().BlockStartAnnotationSpace() < LayoutUnit() && + annotation_space_block_start) { + const LayoutUnit overflow = -ConstraintSpace().BlockStartAnnotationSpace(); + block_offset_shift = -std::min(annotation_space_block_start, overflow); + } + + // If this line has block-start annotation overflow and the previous line has + // block-end annotation space, borrow the block-end space of the previous line + // and shift down the block offset by |overflow - space|. + if (annotation_overflow_block_start && + ConstraintSpace().BlockStartAnnotationSpace() > LayoutUnit()) { + block_offset_shift = (annotation_overflow_block_start - + ConstraintSpace().BlockStartAnnotationSpace()) + .ClampNegativeToZero(); + } + if (line_info->UseFirstLineStyle()) container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine); container_builder_.SetBaseDirection(line_info->BaseDirection()); container_builder_.SetInlineSize(inline_size); container_builder_.SetMetrics(line_box_metrics); - container_builder_.SetBfcBlockOffset(line_info->BfcOffset().block_offset); + container_builder_.SetBfcBlockOffset(line_info->BfcOffset().block_offset + + block_offset_shift); + if (annotation_overflow_block_end) + container_builder_.SetAnnotationOverflow(annotation_overflow_block_end); + else if (annotation_space_block_end) + container_builder_.SetBlockEndAnnotationSpace(annotation_space_block_end); } void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item, @@ -457,11 +508,10 @@ void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item, if (UNLIKELY(quirks_mode_ && !box->HasMetrics())) box->EnsureTextMetrics(*item.Style(), baseline_type_); - NGTextFragmentBuilder text_builder(ConstraintSpace().GetWritingMode()); - text_builder.SetItem(line_info.ItemsData().text_content, item_result, - box->text_height); - line_box_.AddChild(text_builder.ToTextFragment(), box->text_top, - item_result->inline_size, item.BidiLevel()); + line_box_.AddChild(item, std::move(item_result->shape_result), + item_result->TextOffset(), box->text_top, + item_result->inline_size, box->text_height, + item.BidiLevel()); } void NGInlineLayoutAlgorithm::PlaceHyphen(const NGInlineItemResult& item_result, @@ -472,15 +522,10 @@ void NGInlineLayoutAlgorithm::PlaceHyphen(const NGInlineItemResult& item_result, DCHECK(item_result.hyphen_shape_result); DCHECK_EQ(hyphen_inline_size, item_result.HyphenInlineSize()); const NGInlineItem& item = *item_result.item; - const WritingMode writing_mode = ConstraintSpace().GetWritingMode(); - NGTextFragmentBuilder builder(writing_mode); - builder.SetText( - item.GetLayoutObject(), item_result.hyphen_string, item.Style(), - /* is_ellipsis_style */ false, - ShapeResultView::Create(item_result.hyphen_shape_result.get())); - DCHECK(!box->text_metrics.IsEmpty()); - line_box_.AddChild(builder.ToTextFragment(), box->text_top, - hyphen_inline_size, item.BidiLevel()); + line_box_.AddChild( + item, ShapeResultView::Create(item_result.hyphen_shape_result.get()), + item_result.hyphen_string, box->text_top, hyphen_inline_size, + box->text_height, item.BidiLevel()); } NGInlineBoxState* NGInlineLayoutAlgorithm::PlaceAtomicInline( @@ -568,7 +613,7 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( bool has_rtl_block_level_out_of_flow_objects = false; bool is_ltr = IsLtr(line_info.BaseDirection()); - for (NGLineBoxFragmentBuilder::Child& child : line_box_) { + for (NGLogicalLineItem& child : line_box_) { has_preceding_inline_level_content |= child.HasInFlowFragment(); const LayoutObject* box = child.out_of_flow_positioned_box; @@ -608,7 +653,7 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( if (UNLIKELY(has_rtl_block_level_out_of_flow_objects)) { has_preceding_inline_level_content = false; - for (NGLineBoxFragmentBuilder::Child& child : base::Reversed(line_box_)) { + for (NGLogicalLineItem& child : base::Reversed(line_box_)) { const LayoutObject* box = child.out_of_flow_positioned_box; if (!box) { has_preceding_inline_level_content |= child.HasInFlowFragment(); @@ -648,7 +693,7 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects( ? ConstraintSpace().ExpectedBfcBlockOffset() : line_info.BfcOffset().block_offset; - for (NGLineBoxFragmentBuilder::Child& child : line_box_) { + for (NGLogicalLineItem& child : line_box_) { // We need to position any floats which should be on the "next" line now. // If this is an empty inline, all floats are positioned during the // PositionLeadingFloats step. @@ -697,22 +742,23 @@ void NGInlineLayoutAlgorithm::PlaceListMarker(const NGInlineItem& item, // Justify the line. This changes the size of items by adding spacing. // Returns false if justification failed and should fall back to start-aligned. -bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, - NGLineInfo* line_info) { +base::Optional<LayoutUnit> NGInlineLayoutAlgorithm::ApplyJustify( + LayoutUnit space, + NGLineInfo* line_info) { // Empty lines should align to start. if (line_info->IsEmptyLine()) - return false; + return base::nullopt; // Justify the end of visible text, ignoring preserved trailing spaces. unsigned end_offset = line_info->EndOffsetForJustify(); // If this line overflows, fallback to 'text-align: start'. if (space <= 0) - return false; + return base::nullopt; // Can't justify an empty string. if (end_offset == line_info->StartOffset()) - return false; + return base::nullopt; // Construct the line text to compute spacing for. StringBuilder line_text_builder; @@ -735,8 +781,32 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, ShapeResultSpacing<String> spacing(line_text); spacing.SetExpansion(space, line_info->BaseDirection(), line_info->LineStyle().GetTextJustify()); - if (!spacing.HasExpansion()) - return false; // no expansion opportunities exist. + const LayoutObject* box = Node().GetLayoutBox(); + if (!spacing.HasExpansion()) { + // See AdjustInlineDirectionLineBounds() of LayoutRubyBase and + // LayoutRubyText. + if (box && (box->IsRubyText() || box->IsRubyBase())) + return space / 2; + return base::nullopt; + } + + LayoutUnit inset; + // See AdjustInlineDirectionLineBounds() of LayoutRubyBase and + // LayoutRubyText. + if (box && (box->IsRubyText() || box->IsRubyBase())) { + unsigned count = std::min(spacing.ExpansionOppotunityCount(), + static_cast<unsigned>(LayoutUnit::Max().Floor())); + // Inset the ruby base/text by half the inter-ideograph expansion amount. + inset = space / (count + 1); + // For ruby text, inset it by no more than a full-width ruby character on + // each side. + if (box->IsRubyText()) { + inset = + std::min(LayoutUnit(2 * line_info->LineStyle().FontSize()), inset); + } + spacing.SetExpansion(space - inset, line_info->BaseDirection(), + line_info->LineStyle().GetTextJustify()); + } for (NGInlineItemResult& item_result : *line_info->MutableResults()) { if (item_result.has_only_trailing_spaces) @@ -744,10 +814,9 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, if (item_result.shape_result) { scoped_refptr<ShapeResult> shape_result = item_result.shape_result->CreateShapeResult(); - DCHECK_GE(item_result.start_offset, line_info->StartOffset()); - DCHECK_EQ(shape_result->NumCharacters(), - item_result.end_offset - item_result.start_offset); - shape_result->ApplySpacing(spacing, item_result.start_offset - + DCHECK_GE(item_result.StartOffset(), line_info->StartOffset()); + DCHECK_EQ(shape_result->NumCharacters(), item_result.Length()); + shape_result->ApplySpacing(spacing, item_result.StartOffset() - line_info->StartOffset() - shape_result->StartIndex()); item_result.inline_size = shape_result->SnappedWidth(); @@ -756,9 +825,9 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, item_result.shape_result = ShapeResultView::Create(shape_result.get()); } else if (item_result.item->Type() == NGInlineItem::kAtomicInline) { float offset = 0.f; - DCHECK_LE(line_info->StartOffset(), item_result.start_offset); + DCHECK_LE(line_info->StartOffset(), item_result.StartOffset()); unsigned line_text_offset = - item_result.start_offset - line_info->StartOffset(); + item_result.StartOffset() - line_info->StartOffset(); DCHECK_EQ(kObjectReplacementCharacter, line_text[line_text_offset]); float space = spacing.ComputeSpacing(line_text_offset, offset); item_result.inline_size += space; @@ -766,7 +835,7 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, DCHECK_EQ(offset, 0.f); } } - return true; + return inset / 2; } // Apply the 'text-align' property to |line_info|. Returns the amount to move @@ -780,10 +849,9 @@ LayoutUnit NGInlineLayoutAlgorithm::ApplyTextAlign(NGLineInfo* line_info) { 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|. - if (ApplyJustify(space, line_info)) - return LayoutUnit(); + base::Optional<LayoutUnit> offset = ApplyJustify(space, line_info); + if (offset) + return *offset; // If justification fails, fallback to 'text-align: start'. text_align = ETextAlign::kStart; @@ -826,7 +894,7 @@ LayoutUnit NGInlineLayoutAlgorithm::ComputeContentSize( scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { NGExclusionSpace initial_exclusion_space(ConstraintSpace().ExclusionSpace()); - bool is_empty_inline = Node().IsEmptyInline(); + const bool is_empty_inline = Node().IsEmptyInline(); if (is_empty_inline) { // Margins should collapse across "certain zero-height line boxes". @@ -1018,7 +1086,7 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { container_builder_.ToLineBoxFragment(); items_builder->SetCurrentLine( To<NGPhysicalLineBoxFragment>(layout_result->PhysicalFragment()), - std::move(line_box_)); + &line_box_); return layout_result; } @@ -1109,7 +1177,7 @@ void NGInlineLayoutAlgorithm::BidiReorder(TextDirection base_direction) { Vector<UBiDiLevel, 32> levels; levels.ReserveInitialCapacity(line_box_.size()); bool has_opaque_items = false; - for (NGLineBoxFragmentBuilder::Child& item : line_box_) { + for (NGLogicalLineItem& item : line_box_) { if (item.IsOpaqueToBidiReordering()) { levels.push_back(kOpaqueBidiLevel); has_opaque_items = true; @@ -1136,13 +1204,13 @@ void NGInlineLayoutAlgorithm::BidiReorder(TextDirection base_direction) { NGBidiParagraph::IndicesInVisualOrder(levels, &indices_in_visual_order); // Reorder to the visual order. - NGLineBoxFragmentBuilder::ChildList visual_items; + NGLogicalLineItems visual_items; visual_items.ReserveInitialCapacity(line_box_.size()); for (unsigned logical_index : indices_in_visual_order) { visual_items.AddChild(std::move(line_box_[logical_index])); DCHECK(!line_box_[logical_index].HasInFlowFragment() || - // |item_result| will not be null by moving. - line_box_[logical_index].item_result); + // |inline_item| will not be null by moving. + line_box_[logical_index].inline_item); } DCHECK_EQ(line_box_.size(), visual_items.size()); line_box_ = std::move(visual_items); 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 23722eca460..05b15a0147e 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 @@ -7,7 +7,7 @@ #include "third_party/blink/renderer/core/core_export.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_box_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.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_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" @@ -74,7 +74,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final NGInlineBoxState* HandleOpenTag(const NGInlineItem&, const NGInlineItemResult&, - NGLineBoxFragmentBuilder::ChildList*, + NGLogicalLineItems*, NGInlineLayoutStateStack*) const; NGInlineBoxState* HandleCloseTag(const NGInlineItem&, const NGInlineItemResult&, @@ -105,13 +105,13 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final const NGLineInfo&); LayoutUnit ApplyTextAlign(NGLineInfo*); - bool ApplyJustify(LayoutUnit space, NGLineInfo*); + base::Optional<LayoutUnit> ApplyJustify(LayoutUnit space, NGLineInfo*); LayoutUnit ComputeContentSize(const NGLineInfo&, const NGExclusionSpace&, LayoutUnit line_height); - NGLineBoxFragmentBuilder::ChildList line_box_; + NGLogicalLineItems& line_box_; NGInlineLayoutStateStack* box_states_; NGInlineChildLayoutContext* context_; 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 9bd25a79215..2157a1e6ee2 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 @@ -52,10 +52,11 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { NGConstraintSpace constraint_space = builder.ToConstraintSpace(); NGInlineChildLayoutContext context; - NGBoxFragmentBuilder container_builder(block_flow, block_flow->Style(), - block_flow->Style()->GetWritingMode(), - block_flow->Style()->Direction()); - NGFragmentItemsBuilder items_builder(inline_node); + NGBoxFragmentBuilder container_builder( + block_flow, block_flow->Style(), + block_flow->Style()->GetWritingDirection()); + NGFragmentItemsBuilder items_builder(inline_node, + container_builder.GetWritingDirection()); if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { container_builder.SetItemsBuilder(&items_builder); context.SetItemsBuilder(&items_builder); @@ -66,12 +67,14 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { EXPECT_FALSE(line1.BreakToken()->IsFinished()); // Perform 2nd layout with the break token from the 1st line. + items_builder.ClearCurrentLineForTesting(); scoped_refptr<const NGLayoutResult> layout_result2 = 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. + items_builder.ClearCurrentLineForTesting(); scoped_refptr<const NGLayoutResult> layout_result3 = inline_node.Layout(constraint_space, line2.BreakToken(), &context); const auto& line3 = layout_result3->PhysicalFragment(); 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 075cd9ebf3b..0518f8b6d7b 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,12 +7,13 @@ #include <algorithm> #include <memory> +#include "base/trace_event/trace_event.h" #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" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_text.h" +#include "third_party/blink/renderer/core/layout/list_marker.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_inline_break_token.h" @@ -94,14 +95,17 @@ class ReusingTextShaper final { ShapeResult::CreateEmpty(*reusable_shape_results.front()); unsigned offset = start_offset; for (const ShapeResult* reusable_shape_result : reusable_shape_results) { - DCHECK_LE(offset, reusable_shape_result->StartIndex()); + // In case of pre-wrap having break opportunity after leading space, + // |offset| can be greater than |reusable_shape_result->StartIndex()|. + // e.g. <div style="white-space:pre"> abc</div>, deleteChar(0, 1) + // See xternal/wpt/editing/run/delete.html?993-993 if (offset < reusable_shape_result->StartIndex()) { AppendShapeResult( *Reshape(start_item, offset, reusable_shape_result->StartIndex()), shape_result.get()); offset = shape_result->EndIndex(); } - DCHECK_EQ(offset, reusable_shape_result->StartIndex()); + DCHECK_LT(offset, reusable_shape_result->EndIndex()); DCHECK(shape_result->NumCharacters() == 0 || shape_result->EndIndex() == offset); reusable_shape_result->CopyRange( @@ -183,9 +187,9 @@ class ReusingTextShaper final { // There are also performance considerations, since template saves the overhead // for condition checking and branching. template <typename ItemsBuilder> -void CollectInlinesInternal(LayoutBlockFlow* block, - ItemsBuilder* builder, +void CollectInlinesInternal(ItemsBuilder* builder, const NGInlineNodeData* previous_data) { + LayoutBlockFlow* const block = builder->GetLayoutBlockFlow(); builder->EnterBlock(block->Style()); LayoutObject* node = GetLayoutObjectForFirstChildNode(block); @@ -215,7 +219,7 @@ void CollectInlinesInternal(LayoutBlockFlow* block, builder->ClearInlineFragment(node); } else if (node->IsAtomicInlineLevel()) { - if (node->IsListMarkerIncludingNGOutside()) { + if (node->IsBoxListMarkerIncludingNG()) { // LayoutNGListItem produces the 'outside' list marker as an inline // block. This is an out-of-flow item whose position is computed // automatically. @@ -373,7 +377,7 @@ bool NGInlineNode::IsPrepareLayoutFinished() const { return data && !data->text_content.IsNull(); } -void NGInlineNode::PrepareLayoutIfNeeded() { +void NGInlineNode::PrepareLayoutIfNeeded() const { std::unique_ptr<NGInlineNodeData> previous_data; LayoutBlockFlow* block_flow = GetLayoutBlockFlow(); if (IsPrepareLayoutFinished()) { @@ -388,7 +392,7 @@ void NGInlineNode::PrepareLayoutIfNeeded() { } void NGInlineNode::PrepareLayout( - std::unique_ptr<NGInlineNodeData> previous_data) { + std::unique_ptr<NGInlineNodeData> previous_data) const { // 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(); @@ -516,16 +520,19 @@ class NGInlineNodeDataEditor final { // Skip items in replaced range. while (it->end_offset_ < end_offset) ++it; - DCHECK_EQ(it->layout_object_, layout_text_); // Inserted text - if (inserted_text_length > 0) { - const unsigned inserted_start_offset = - items.IsEmpty() ? 0 : items.back().end_offset_; - const unsigned inserted_end_offset = - inserted_start_offset + inserted_text_length; - items.push_back(NGInlineItem(*it, inserted_start_offset, - inserted_end_offset, nullptr)); + if (it->layout_object_ == layout_text_) { + if (inserted_text_length > 0) { + const unsigned inserted_start_offset = + items.IsEmpty() ? 0 : items.back().end_offset_; + const unsigned inserted_end_offset = + inserted_start_offset + inserted_text_length; + items.push_back(NGInlineItem(*it, inserted_start_offset, + inserted_end_offset, nullptr)); + } + } else { + DCHECK_LE(inserted_text_length, 0); } // Copy part of item after replaced range. @@ -571,7 +578,6 @@ class NGInlineNodeDataEditor final { unsigned start_offset) const { DCHECK_LE(item.start_offset_, start_offset); DCHECK_LT(start_offset, item.end_offset_); - DCHECK_EQ(item.layout_object_, layout_text_); if (item.start_offset_ == start_offset) return item; const unsigned end_offset = item.end_offset_; @@ -688,11 +694,11 @@ bool NGInlineNode::SetTextWithOffset(LayoutText* layout_text, NGInlineNode node(editor.GetLayoutBlockFlow()); NGInlineNodeData* data = node.MutableData(); data->items.ReserveCapacity(previous_data->items.size()); - NGInlineItemsBuilder builder(&data->items); + NGInlineItemsBuilder builder(editor.GetLayoutBlockFlow(), &data->items); // TODO(yosin): We should reuse before/after |layout_text| during collecting // inline items. layout_text->ClearInlineItems(); - CollectInlinesInternal(node.GetLayoutBlockFlow(), &builder, previous_data); + CollectInlinesInternal(&builder, previous_data); builder.DidFinishCollectInlines(data); // Relocates |ShapeResult| in |previous_data| after |offset|+|length| editor.Run(); @@ -703,12 +709,12 @@ bool NGInlineNode::SetTextWithOffset(LayoutText* layout_text, return true; } -const NGInlineNodeData& NGInlineNode::EnsureData() { +const NGInlineNodeData& NGInlineNode::EnsureData() const { PrepareLayoutIfNeeded(); return Data(); } -const NGOffsetMapping* NGInlineNode::ComputeOffsetMappingIfNeeded() { +const NGOffsetMapping* NGInlineNode::ComputeOffsetMappingIfNeeded() const { DCHECK(!GetLayoutBlockFlow()->GetDocument().NeedsLayoutTreeUpdate()); NGInlineNodeData* data = MutableData(); @@ -732,10 +738,10 @@ void NGInlineNode::ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow, // |builder| not construct items and text content. Vector<NGInlineItem> items; items.ReserveCapacity(EstimateInlineItemsCount(*layout_block_flow)); - NGInlineItemsBuilderForOffsetMapping builder(&items); + NGInlineItemsBuilderForOffsetMapping builder(layout_block_flow, &items); builder.GetOffsetMappingBuilder().ReserveCapacity( EstimateOffsetMappingItemsCount(*layout_block_flow)); - CollectInlinesInternal(layout_block_flow, &builder, nullptr); + CollectInlinesInternal(&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 @@ -791,19 +797,19 @@ 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) const { DCHECK(data->text_content.IsNull()); DCHECK(data->items.IsEmpty()); LayoutBlockFlow* block = GetLayoutBlockFlow(); block->WillCollectInlines(); data->items.ReserveCapacity(EstimateInlineItemsCount(*block)); - NGInlineItemsBuilder builder(&data->items); - CollectInlinesInternal(block, &builder, previous_data); + NGInlineItemsBuilder builder(block, &data->items); + CollectInlinesInternal(&builder, previous_data); builder.DidFinishCollectInlines(data); } -void NGInlineNode::SegmentText(NGInlineNodeData* data) { +void NGInlineNode::SegmentText(NGInlineNodeData* data) const { SegmentBidiRuns(data); SegmentScriptRuns(data); SegmentFontOrientation(data); @@ -812,8 +818,8 @@ void NGInlineNode::SegmentText(NGInlineNodeData* data) { } // Segment NGInlineItem by script, Emoji, and orientation using RunSegmenter. -void NGInlineNode::SegmentScriptRuns(NGInlineNodeData* data) { - DCHECK_EQ(data->segments, nullptr); +void NGInlineNode::SegmentScriptRuns(NGInlineNodeData* data) const { + DCHECK_EQ(data->segments.get(), nullptr); String& text_content = data->text_content; if (text_content.IsEmpty()) { @@ -895,7 +901,7 @@ void NGInlineNode::SegmentScriptRuns(NGInlineNodeData* data) { DCHECK_EQ(range.end, text_content.length()); } -void NGInlineNode::SegmentFontOrientation(NGInlineNodeData* data) { +void NGInlineNode::SegmentFontOrientation(NGInlineNodeData* data) const { // Segment by orientation, only if vertical writing mode and items with // 'text-orientation: mixed'. if (GetLayoutBlockFlow()->IsHorizontalWritingMode()) @@ -937,7 +943,7 @@ void NGInlineNode::SegmentFontOrientation(NGInlineNodeData* data) { // Segment bidi runs by resolving bidi embedding levels. // http://unicode.org/reports/tr9/#Resolving_Embedding_Levels -void NGInlineNode::SegmentBidiRuns(NGInlineNodeData* data) { +void NGInlineNode::SegmentBidiRuns(NGInlineNodeData* data) const { if (!data->is_bidi_enabled_) { data->SetBaseDirection(TextDirection::kLtr); return; @@ -982,7 +988,8 @@ void NGInlineNode::SegmentBidiRuns(NGInlineNodeData* data) { void NGInlineNode::ShapeText(NGInlineItemsData* data, const String* previous_text, - const Vector<NGInlineItem>* previous_items) { + const Vector<NGInlineItem>* previous_items) const { + TRACE_EVENT0("blink", "NGInlineNode::ShapeText"); const String& text_content = data->text_content; Vector<NGInlineItem>* items = &data->items; @@ -1009,7 +1016,7 @@ void NGInlineNode::ShapeText(NGInlineItemsData* data, // Symbol marker is painted as graphics. Create a ShapeResult of space // glyphs with the desired size to make it less special for line breaker. if (UNLIKELY(start_item.IsSymbolMarker())) { - LayoutUnit symbol_width = LayoutListMarker::WidthOfSymbol(start_style); + LayoutUnit symbol_width = ListMarker::WidthOfSymbol(start_style); DCHECK_GT(symbol_width, 0); start_item.shape_result_ = ShapeResult::CreateForSpaces( &font, direction, start_item.StartOffset(), start_item.Length(), @@ -1155,7 +1162,7 @@ void NGInlineNode::ShapeText(NGInlineItemsData* data, } // Create Vector<NGInlineItem> with :first-line rules applied if needed. -void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) { +void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) const { // First check if the document has any :first-line rules. DCHECK(!data->first_line_items_); LayoutObject* layout_object = GetLayoutBox(); @@ -1200,7 +1207,7 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) { data->first_line_items_ = std::move(first_line_items); } -void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) { +void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) const { #if DCHECK_IS_ON() HashSet<LayoutObject*> associated_objects; #endif @@ -1283,7 +1290,7 @@ void NGInlineNode::ClearAssociatedFragments( scoped_refptr<const NGLayoutResult> NGInlineNode::Layout( const NGConstraintSpace& constraint_space, const NGBreakToken* break_token, - NGInlineChildLayoutContext* context) { + NGInlineChildLayoutContext* context) const { PrepareLayoutIfNeeded(); const auto* inline_break_token = To<NGInlineBreakToken>(break_token); @@ -1292,16 +1299,20 @@ scoped_refptr<const NGLayoutResult> NGInlineNode::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(); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + // 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. + // TODO(crbug.com/1042604): FragmentItem should save memory enough to re- + // enable the position cache. + NGInlineNodeData* data = MutableData(); + for (auto& item : data->items) { + if (item.shape_result_) + item.shape_result_->DiscardPositionData(); + } } #endif // defined(OS_ANDROID) @@ -1587,7 +1598,8 @@ static LayoutUnit ComputeContentSize( const ComputedStyle& float_style = float_node.Style(); // Floats don't intrude into floats. - MinMaxSizesInput float_input(input.percentage_resolution_block_size); + MinMaxSizesInput float_input(input.percentage_resolution_block_size, + MinMaxSizesType::kContent); MinMaxSizesResult child_result = ComputeMinAndMaxContentContribution(style, float_node, float_input); LayoutUnit child_inline_margins = @@ -1637,7 +1649,7 @@ static LayoutUnit ComputeContentSize( MinMaxSizesResult NGInlineNode::ComputeMinMaxSizes( WritingMode container_writing_mode, const MinMaxSizesInput& input, - const NGConstraintSpace* constraint_space) { + const NGConstraintSpace* constraint_space) const { PrepareLayoutIfNeeded(); // Compute the max of inline sizes of all line boxes with 0 available inline 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 c14ef2b3644..af75b0b6a9f 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 @@ -27,7 +27,8 @@ struct NGInlineItemsData; // inline nodes and their descendants. class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { public: - NGInlineNode(LayoutBlockFlow*); + explicit NGInlineNode(LayoutBlockFlow*); + explicit NGInlineNode(std::nullptr_t) : NGLayoutInputNode(nullptr) {} LayoutBlockFlow* GetLayoutBlockFlow() const { return To<LayoutBlockFlow>(box_); @@ -44,14 +45,15 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { scoped_refptr<const NGLayoutResult> Layout( const NGConstraintSpace&, const NGBreakToken*, - NGInlineChildLayoutContext* context); + NGInlineChildLayoutContext* context) const; // Computes the value of min-content and max-content for this anonymous block // box. min-content is the inline size when lines wrap at every break // opportunity, and max-content is when lines do not wrap at all. - MinMaxSizesResult ComputeMinMaxSizes(WritingMode container_writing_mode, - const MinMaxSizesInput&, - const NGConstraintSpace* = nullptr); + MinMaxSizesResult ComputeMinMaxSizes( + WritingMode container_writing_mode, + const MinMaxSizesInput&, + const NGConstraintSpace* = nullptr) const; // Instruct to re-compute |PrepareLayout| on the next layout. void InvalidatePrepareLayoutForTest() { @@ -98,7 +100,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { // Returns the DOM to text content offset mapping of this block. If it is not // computed before, compute and store it in NGInlineNodeData. // This funciton must be called with clean layout. - const NGOffsetMapping* ComputeOffsetMappingIfNeeded(); + const NGOffsetMapping* ComputeOffsetMappingIfNeeded() const; // Get |NGOffsetMapping| for the |layout_block_flow|. |layout_block_flow| // should be laid out. This function works for both new and legacy layout. @@ -108,6 +110,9 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { bool IsBidiEnabled() const { return Data().is_bidi_enabled_; } TextDirection BaseDirection() const { return Data().BaseDirection(); } + bool HasLineEvenIfEmpty() { return EnsureData().has_line_even_if_empty_; } + bool HasRuby() const { return Data().has_ruby_; } + bool IsEmptyInline() { return EnsureData().is_empty_inline_; } bool IsBlockLevel() { return EnsureData().is_block_level_; } @@ -127,7 +132,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { struct FloatingObject { DISALLOW_NEW(); - void Trace(Visitor* visitor) {} + void Trace(Visitor* visitor) const {} const ComputedStyle& float_style; const ComputedStyle& style; @@ -139,22 +144,22 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { // 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); + void PrepareLayoutIfNeeded() const; + void PrepareLayout(std::unique_ptr<NGInlineNodeData> previous_data) const; void CollectInlines(NGInlineNodeData*, - NGInlineNodeData* previous_data = nullptr); - void SegmentText(NGInlineNodeData*); - void SegmentScriptRuns(NGInlineNodeData*); - void SegmentFontOrientation(NGInlineNodeData*); - void SegmentBidiRuns(NGInlineNodeData*); + NGInlineNodeData* previous_data = nullptr) const; + void SegmentText(NGInlineNodeData*) const; + void SegmentScriptRuns(NGInlineNodeData*) const; + void SegmentFontOrientation(NGInlineNodeData*) const; + void SegmentBidiRuns(NGInlineNodeData*) const; void ShapeText(NGInlineItemsData*, const String* previous_text = nullptr, - const Vector<NGInlineItem>* previous_items = nullptr); - void ShapeTextForFirstLineIfNeeded(NGInlineNodeData*); - void AssociateItemsWithInlines(NGInlineNodeData*); + const Vector<NGInlineItem>* previous_items = nullptr) const; + void ShapeTextForFirstLineIfNeeded(NGInlineNodeData*) const; + void AssociateItemsWithInlines(NGInlineNodeData*) const; - NGInlineNodeData* MutableData() { + NGInlineNodeData* MutableData() const { return To<LayoutBlockFlow>(box_)->GetNGInlineNodeData(); } const NGInlineNodeData& Data() const { @@ -167,7 +172,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { DCHECK(IsPrepareLayoutFinished()); return *To<LayoutBlockFlow>(box_)->GetNGInlineNodeData(); } - const NGInlineNodeData& EnsureData(); + const NGInlineNodeData& EnsureData() const; static void ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow, NGInlineNodeData* data); 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 5dda66cc9f2..0636afc4b3d 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 @@ -22,6 +22,8 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData { return static_cast<TextDirection>(base_direction_); } + bool HasLineEvenIfEmpty() const { return has_line_even_if_empty_; } + bool HasRuby() const { return has_ruby_; } bool IsEmptyInline() const { return is_empty_inline_; } bool IsBlockLevel() const { return is_block_level_; } @@ -56,6 +58,14 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData { unsigned is_bidi_enabled_ : 1; unsigned base_direction_ : 1; // TextDirection + // True if there are no inline item items and the associated block is root + // editable element or having "-internal-empty-line-height:fabricated", + // e.g. <div contenteditable></div>, <input type=button value=""> + unsigned has_line_even_if_empty_ : 1; + + // The node contains <ruby>. + unsigned has_ruby_ : 1; + // We use this flag to determine if the inline node is empty, and will // produce a single zero block-size line box. If the node has text, atomic // inlines, open/close tags with margins/border/padding this will be false. 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 92ef98f42be..511fc704621 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 @@ -114,7 +114,8 @@ class NGInlineNodeTest : public NGLayoutTest { .ComputeMinMaxSizes( node.Style().GetWritingMode(), MinMaxSizesInput( - /* percentage_resolution_block_size */ LayoutUnit())) + /* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent)) .sizes; } @@ -140,13 +141,16 @@ class NGInlineNodeTest : public NGLayoutTest { return end_offsets; } - void TestFirstLineIsDirty(LayoutBlockFlow* block_flow, bool expected) { + void TestAnyItrermsAreDirty(LayoutBlockFlow* block_flow, bool expected) { const NGFragmentItems* items = block_flow->FragmentItems(); items->DirtyLinesFromNeedsLayout(block_flow); - const NGFragmentItem* end_reusable_item = items->EndOfReusableItems(); - NGInlineCursor cursor(*items); - cursor.MoveToFirstLine(); - EXPECT_EQ(cursor.Current().Item() == end_reusable_item, expected); + // Check |NGFragmentItem::IsDirty| directly without using + // |EndOfReusableItems|. This is different from the line cache logic, but + // some items may not be reusable even if |!IsDirty()|. + const bool is_any_items_dirty = + std::any_of(items->Items().begin(), items->Items().end(), + [](const NGFragmentItem& item) { return item.IsDirty(); }); + EXPECT_EQ(is_any_items_dirty, expected); } scoped_refptr<const ComputedStyle> style_; @@ -636,8 +640,8 @@ TEST_P(StyleChangeTest, NeedsCollectInlinesOnStyle) { if (data.is_line_dirty && RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - TestFirstLineIsDirty(To<LayoutBlockFlow>(container->GetLayoutObject()), - *data.is_line_dirty); + TestAnyItrermsAreDirty(To<LayoutBlockFlow>(container->GetLayoutObject()), + *data.is_line_dirty); } ForceLayout(); // Ensure running layout does not crash. @@ -1035,9 +1039,11 @@ TEST_F(NGInlineNodeTest, ClearFirstInlineFragmentOnSplitFlow) { // 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); + NGInlineCursor before_split; + before_split.MoveTo(*text->GetLayoutObject()); + EXPECT_TRUE(before_split); + scoped_refptr<const NGPaintFragment> text_fragment_before_split = + before_split.Current().PaintFragment(); // Append <div> to <span>. causing SplitFlow(). Element* outer_span = GetElementById("outer_span"); @@ -1053,23 +1059,32 @@ TEST_F(NGInlineNodeTest, ClearFirstInlineFragmentOnSplitFlow) { // destroyed, and should not be accessible. GetDocument().UpdateStyleAndLayoutTree(); EXPECT_FALSE(text->GetLayoutObject()->IsInLayoutNGInlineFormattingContext()); - scoped_refptr<NGPaintFragment> text_fragment_before_layout = - text->GetLayoutObject()->FirstInlineFragment(); - EXPECT_EQ(text_fragment_before_layout, nullptr); + EXPECT_FALSE(text->GetLayoutObject()->HasInlineFragments()); // 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); + NGInlineCursor after_layout; + after_layout.MoveTo(*text->GetLayoutObject()); + EXPECT_TRUE(after_layout); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + EXPECT_NE(text_fragment_before_split.get(), + after_layout.Current().PaintFragment()); + } // 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); + NGInlineCursor anonymous_block_cursor(*To<LayoutBlockFlow>(anonymous_block)); + anonymous_block_cursor.MoveToFirstLine(); + anonymous_block_cursor.MoveToFirstChild(); + EXPECT_TRUE(anonymous_block_cursor); + EXPECT_EQ(anonymous_block_cursor.Current().GetLayoutObject(), + text->GetLayoutObject()); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + EXPECT_EQ(anonymous_block_cursor.Current().PaintFragment(), + after_layout.Current().PaintFragment()); + } } TEST_F(NGInlineNodeTest, AddChildToSVGRoot) { 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 3ec775afee2..2cb44b83e61 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 @@ -8,6 +8,7 @@ #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_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_logical_line_item.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_text_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" @@ -40,90 +41,7 @@ void NGLineBoxFragmentBuilder::SetIsEmptyLineBox() { line_box_type_ = NGPhysicalLineBoxFragment::kEmptyLineBox; } -void NGLineBoxFragmentBuilder::ChildList::CreateTextFragments( - WritingMode writing_mode, - const String& text_content) { - NGTextFragmentBuilder text_builder(writing_mode); - for (auto& child : *this) { - if (NGInlineItemResult* item_result = child.item_result) { - DCHECK(item_result->item); - const NGInlineItem& item = *item_result->item; - DCHECK(item.Type() == NGInlineItem::kText || - item.Type() == NGInlineItem::kControl); - DCHECK(item.TextType() == NGTextType::kNormal || - item.TextType() == NGTextType::kSymbolMarker); - text_builder.SetItem(text_content, item_result, - child.rect.size.block_size); - DCHECK(!child.fragment); - child.fragment = text_builder.ToTextFragment(); - } - } -} - -NGLineBoxFragmentBuilder::Child* -NGLineBoxFragmentBuilder::ChildList::FirstInFlowChild() { - for (auto& child : *this) { - if (child.HasInFlowFragment()) - return &child; - } - return nullptr; -} - -NGLineBoxFragmentBuilder::Child* -NGLineBoxFragmentBuilder::ChildList::LastInFlowChild() { - for (auto it = rbegin(); it != rend(); it++) { - auto& child = *it; - if (child.HasInFlowFragment()) - return &child; - } - return nullptr; -} - -void NGLineBoxFragmentBuilder::ChildList::WillInsertChild( - unsigned insert_before) { - unsigned index = 0; - for (Child& child : children_) { - if (index >= insert_before) - break; - if (child.children_count && index + child.children_count > insert_before) - ++child.children_count; - ++index; - } -} - -void NGLineBoxFragmentBuilder::ChildList::InsertChild(unsigned index) { - WillInsertChild(index); - children_.insert(index, Child()); -} - -void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( - LayoutUnit delta) { - for (auto& child : children_) - child.rect.offset.inline_offset += delta; -} - -void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( - LayoutUnit delta, - unsigned start, - unsigned end) { - for (unsigned index = start; index < end; index++) - children_[index].rect.offset.inline_offset += delta; -} - -void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection( - LayoutUnit delta) { - for (auto& child : children_) - child.rect.offset.block_offset += delta; -} - -void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection(LayoutUnit delta, - unsigned start, - unsigned end) { - for (unsigned index = start; index < end; index++) - children_[index].rect.offset.block_offset += delta; -} - -void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { +void NGLineBoxFragmentBuilder::AddChildren(NGLogicalLineItems& children) { children_.ReserveCapacity(children.size()); for (auto& child : children) { @@ -143,7 +61,8 @@ void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { } } -void NGLineBoxFragmentBuilder::PropagateChildrenData(ChildList& children) { +void NGLineBoxFragmentBuilder::PropagateChildrenData( + NGLogicalLineItems& children) { for (unsigned index = 0; index < children.size(); ++index) { auto& child = children[index]; if (child.layout_result) { @@ -172,7 +91,7 @@ void NGLineBoxFragmentBuilder::PropagateChildrenData(ChildList& children) { scoped_refptr<const NGLayoutResult> NGLineBoxFragmentBuilder::ToLineBoxFragment() { - writing_mode_ = ToLineWritingMode(writing_mode_); + writing_direction_.SetWritingMode(ToLineWritingMode(GetWritingMode())); if (!break_token_) break_token_ = NGInlineBreakToken::Create(node_); 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 8c3dc2cd273..47540975fc5 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 @@ -21,7 +21,7 @@ namespace blink { class ComputedStyle; class NGInlineBreakToken; -struct NGInlineItemResult; +class NGLogicalLineItems; class CORE_EXPORT NGLineBoxFragmentBuilder final : public NGContainerFragmentBuilder { @@ -31,13 +31,13 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final NGLineBoxFragmentBuilder(NGInlineNode node, scoped_refptr<const ComputedStyle> style, const NGConstraintSpace* space, - WritingMode writing_mode, - TextDirection) - : NGContainerFragmentBuilder(node, - style, - space, - writing_mode, - TextDirection::kLtr), + WritingDirectionMode writing_direction) + : NGContainerFragmentBuilder( + node, + style, + space, + // Always use LTR because line items are in visual order. + {writing_direction.GetWritingMode(), TextDirection::kLtr}), line_box_type_(NGPhysicalLineBoxFragment::kNormalLineBox), base_direction_(TextDirection::kLtr) {} @@ -71,248 +71,13 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final break_token_ = std::move(break_token); } - // A data struct to keep NGLayoutResult or fragment until the box tree - // structures and child offsets are finalized. - struct Child { - DISALLOW_NEW(); - - scoped_refptr<NGFragmentItem> fragment_item; - scoped_refptr<const NGLayoutResult> layout_result; - scoped_refptr<const NGPhysicalTextFragment> fragment; - const NGInlineItem* inline_item = nullptr; - // |NGInlineItemResult| to create a text fragment from. - NGInlineItemResult* item_result = nullptr; - 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. - LogicalRect rect; - // The offset of a positioned float wrt. the root BFC. This should only be - // set for positioned floats. - NGBfcOffset bfc_offset; - // The inline size of the margin box. - LayoutUnit inline_size; - LayoutUnit margin_line_left; - // The index of |box_data_list_|, used in |PrepareForReorder()| and - // |UpdateAfterReorder()| to track children of boxes across BiDi reorder. - unsigned box_data_index = 0; - // For an inline box, shows the number of descendant |Child|ren, including - // empty ones. Includes itself, so 1 means no descendants. 0 if not an - // inline box. Available only after |CreateBoxFragments()|. - unsigned children_count = 0; - UBiDiLevel bidi_level = 0xff; - // The current text direction for OOF positioned items. - TextDirection container_direction = TextDirection::kLtr; - - // Empty constructor needed for |resize()|. - Child() = default; - // Create a placeholder. A placeholder does not have a fragment nor a bidi - // level. - Child(LayoutUnit block_offset, LayoutUnit block_size) - : rect(LayoutUnit(), block_offset, LayoutUnit(), block_size) {} - Child(const NGInlineItem& inline_item, - const LogicalRect& rect, - unsigned children_count) - : inline_item(&inline_item), - rect(rect), - children_count(children_count) {} - // 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, - const LogicalRect& rect, - unsigned children_count, - UBiDiLevel bidi_level) - : layout_result(std::move(layout_result)), - rect(rect), - children_count(children_count), - bidi_level(bidi_level) {} - Child(scoped_refptr<const NGLayoutResult> layout_result, - LogicalOffset offset, - LayoutUnit inline_size, - unsigned children_count, - UBiDiLevel bidi_level) - : layout_result(std::move(layout_result)), - rect(offset, LogicalSize()), - inline_size(inline_size), - children_count(children_count), - bidi_level(bidi_level) {} - // Create an in-flow text fragment. - Child(NGInlineItemResult* item_result, - LayoutUnit block_offset, - LayoutUnit inline_size, - LayoutUnit text_height, - UBiDiLevel bidi_level) - : item_result(item_result), - rect(LayoutUnit(), block_offset, LayoutUnit(), text_height), - inline_size(inline_size), - bidi_level(bidi_level) {} - Child(scoped_refptr<const NGPhysicalTextFragment> fragment, - LogicalOffset offset, - LayoutUnit inline_size, - UBiDiLevel bidi_level) - : fragment(std::move(fragment)), - rect(offset, LogicalSize()), - inline_size(inline_size), - bidi_level(bidi_level) {} - Child(scoped_refptr<const NGPhysicalTextFragment> fragment, - LayoutUnit block_offset, - LayoutUnit inline_size, - UBiDiLevel bidi_level) - : fragment(std::move(fragment)), - rect(LayoutUnit(), block_offset, LayoutUnit(), LayoutUnit()), - inline_size(inline_size), - bidi_level(bidi_level) {} - // Create an out-of-flow positioned object. - Child(LayoutObject* out_of_flow_positioned_box, - UBiDiLevel bidi_level, - TextDirection container_direction) - : out_of_flow_positioned_box(out_of_flow_positioned_box), - bidi_level(bidi_level), - container_direction(container_direction) {} - // Create an unpositioned float. - Child(LayoutObject* unpositioned_float, UBiDiLevel bidi_level) - : unpositioned_float(unpositioned_float), bidi_level(bidi_level) {} - // Create a positioned float. - Child(scoped_refptr<const NGLayoutResult> layout_result, - NGBfcOffset bfc_offset, - UBiDiLevel bidi_level) - : layout_result(std::move(layout_result)), - bfc_offset(bfc_offset), - bidi_level(bidi_level) {} - - bool HasInFlowFragment() const { - if (fragment_item) - return true; - if (fragment) - return true; - if (item_result) - return true; - if (layout_result && !layout_result->PhysicalFragment().IsFloating()) - return true; - - return false; - } - bool HasOutOfFlowFragment() const { return out_of_flow_positioned_box; } - bool HasFragment() const { - return HasInFlowFragment() || HasOutOfFlowFragment(); - } - bool HasBidiLevel() const { return bidi_level != 0xff; } - bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); } - bool IsOpaqueToBidiReordering() const { - if (IsPlaceholder()) - return true; - // Skip all inline boxes. Fragments for inline boxes maybe created earlier - // if they have no children. - if (layout_result) { - const LayoutObject* layout_object = - layout_result->PhysicalFragment().GetLayoutObject(); - DCHECK(layout_object); - if (layout_object->IsLayoutInline()) - return true; - } - return false; - } - const LogicalOffset& Offset() const { return rect.offset; } - LayoutUnit InlineOffset() const { return rect.offset.inline_offset; } - const LogicalSize& Size() const { return rect.size; } - const NGPhysicalFragment* PhysicalFragment() const { - if (layout_result) - return &layout_result->PhysicalFragment(); - return fragment.get(); - } - TextDirection ResolvedDirection() const { - // Inline boxes are not leaves that they don't have directions. - DCHECK(HasBidiLevel() || layout_result->PhysicalFragment().IsInlineBox()); - return HasBidiLevel() ? DirectionFromLevel(bidi_level) - : TextDirection::kLtr; - } - }; - - // A vector of Child. - // Unlike the fragment builder, chlidren are mutable. - // Callers can add to the fragment builder in a batch once finalized. - class ChildList { - STACK_ALLOCATED(); - - public: - ChildList() = default; - void operator=(ChildList&& other) { - children_ = std::move(other.children_); - } - - Child& operator[](wtf_size_t i) { return children_[i]; } - const Child& operator[](wtf_size_t i) const { return children_[i]; } - - wtf_size_t size() const { return children_.size(); } - bool IsEmpty() const { return children_.IsEmpty(); } - void ReserveInitialCapacity(unsigned capacity) { - children_.ReserveInitialCapacity(capacity); - } - void clear() { children_.resize(0); } - void resize(wtf_size_t size) { children_.resize(size); } - - using iterator = Vector<Child, 16>::iterator; - iterator begin() { return children_.begin(); } - iterator end() { return children_.end(); } - using const_iterator = Vector<Child, 16>::const_iterator; - const_iterator begin() const { return children_.begin(); } - const_iterator end() const { return children_.end(); } - using reverse_iterator = Vector<Child, 16>::reverse_iterator; - reverse_iterator rbegin() { return children_.rbegin(); } - reverse_iterator rend() { return children_.rend(); } - using const_reverse_iterator = Vector<Child, 16>::const_reverse_iterator; - const_reverse_iterator rbegin() const { return children_.rbegin(); } - const_reverse_iterator rend() const { return children_.rend(); } - - Child* FirstInFlowChild(); - Child* LastInFlowChild(); - - // Add a child. Accepts all constructor arguments for |Child|. - template <class... Args> - void AddChild(Args&&... args) { - children_.emplace_back(std::forward<Args>(args)...); - } - void InsertChild(unsigned index); - void InsertChild(unsigned index, - scoped_refptr<const NGLayoutResult> layout_result, - const LogicalRect& rect, - unsigned children_count) { - WillInsertChild(index); - children_.insert(index, Child(std::move(layout_result), rect, - children_count, /* bidi_level */ 0)); - } - void InsertChild(unsigned index, - const NGInlineItem& inline_item, - const LogicalRect& rect, - unsigned children_count) { - WillInsertChild(index); - children_.insert(index, Child(inline_item, rect, children_count)); - } - - void MoveInInlineDirection(LayoutUnit); - void MoveInInlineDirection(LayoutUnit, unsigned start, unsigned end); - void MoveInBlockDirection(LayoutUnit); - void MoveInBlockDirection(LayoutUnit, unsigned start, unsigned end); - - // Create |NGPhysicalTextFragment| for all text children. - void CreateTextFragments(WritingMode writing_mode, - const String& text_content); - - private: - void WillInsertChild(unsigned index); - - Vector<Child, 16> children_; - }; - // Add all items in ChildList. Skips null Child if any. - void AddChildren(ChildList&); + void AddChildren(NGLogicalLineItems&); // Propagate data in |ChildList| without adding them to this builder. When // adding children as fragment items, they appear in the container, but there // are some data that should be propagated through line box fragments. - void PropagateChildrenData(ChildList&); + void PropagateChildrenData(NGLogicalLineItems&); // Creates the fragment. Can only be called once. scoped_refptr<const NGLayoutResult> ToLineBoxFragment(); @@ -331,7 +96,4 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final } // namespace blink -WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( - blink::NGLineBoxFragmentBuilder::Child) - #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BOX_FRAGMENT_BUILDER_H_ 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 1964973a226..45a4869ac14 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 @@ -8,6 +8,7 @@ #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_ruby_utils.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" @@ -93,7 +94,7 @@ inline void ComputeCanBreakAfter(NGInlineItemResult* item_result, bool auto_wrap, const LazyLineBreakIterator& break_iterator) { item_result->can_break_after = - auto_wrap && break_iterator.IsBreakable(item_result->end_offset); + auto_wrap && break_iterator.IsBreakable(item_result->EndOffset()); } inline void RemoveLastItem(NGLineInfo* line_info) { @@ -235,8 +236,9 @@ inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item, DCHECK_LE(end_offset, item.EndOffset()); NGInlineItemResults* item_results = line_info->MutableResults(); return &item_results->emplace_back( - &item, item_index_, offset_, end_offset, break_anywhere_if_overflow_, - ShouldCreateLineBox(*item_results), HasUnpositionedFloats(*item_results)); + &item, item_index_, NGTextOffset(offset_, end_offset), + break_anywhere_if_overflow_, ShouldCreateLineBox(*item_results), + HasUnpositionedFloats(*item_results)); } inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item, @@ -350,7 +352,8 @@ void NGLineBreaker::NextLine( // line boxes. These cases need to be reviewed. bool should_create_line_box = ShouldCreateLineBox(item_results) || (has_list_marker_ && line_info->IsLastLine()) || - mode_ != NGLineBreakerMode::kContent; + mode_ != NGLineBreakerMode::kContent || + node_.HasLineEvenIfEmpty(); if (!should_create_line_box) line_info->SetIsEmptyLine(); @@ -443,7 +446,7 @@ void NGLineBreaker::BreakLine( // determine the break opportunity. NGInlineItemResult* item_result = AddItem(item, line_info); item_result->can_break_after = - break_iterator_.IsBreakable(item_result->end_offset); + break_iterator_.IsBreakable(item_result->EndOffset()); MoveToNextOf(item); } else if (item.Type() == NGInlineItem::kListMarker) { NGInlineItemResult* item_result = AddItem(item, line_info); @@ -480,23 +483,23 @@ bool NGLineBreaker::ShouldForceCanBreakAfter( DCHECK(auto_wrap_); DCHECK_EQ(item_result.item->Type(), NGInlineItem::kText); const String& text = Text(); - DCHECK_GE(text.length(), item_result.end_offset); - if (text.length() <= item_result.end_offset || - text[item_result.end_offset] != kObjectReplacementCharacter) + DCHECK_GE(text.length(), item_result.EndOffset()); + if (text.length() <= item_result.EndOffset() || + text[item_result.EndOffset()] != kObjectReplacementCharacter) return false; // This kObjectReplacementCharacter can be any objects, such as a floating or // an OOF object. Check if it's really an atomic inline. const Vector<NGInlineItem>& items = Items(); for (const NGInlineItem* item = std::next(item_result.item); item != items.end(); ++item) { - DCHECK_EQ(item->StartOffset(), item_result.end_offset); + DCHECK_EQ(item->StartOffset(), item_result.EndOffset()); if (item->Type() == NGInlineItem::kAtomicInline) { // Except when sticky images quirk was applied. if (UNLIKELY(text[item->StartOffset()] == kNoBreakSpaceCharacter)) return false; - return true; + return !item->IsRubyRun(); } - if (item->EndOffset() > item_result.end_offset) + if (item->EndOffset() > item_result.EndOffset()) break; } return false; @@ -556,6 +559,12 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, NGInlineItemResult* item_result = AddItem(item, line_info); item_result->should_create_line_box = true; + // Try to commit |pending_end_overhang_| of a prior NGInlineItemResult. + // |pending_end_overhang_| doesn't work well with bidi reordering. It's + // difficult to compute overhang after bidi reordering because it affect + // line breaking. + if (maybe_have_end_overhang_) + position_ -= CommitPendingEndOverhang(line_info); if (auto_wrap_) { if (mode_ == NGLineBreakerMode::kMinContent && @@ -582,7 +591,7 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, // If the break is at the middle of a text item, we know no trailable // items follow, only trailable spaces if any. This is very common that // shortcut to handling trailing spaces. - if (item_result->end_offset < item.EndOffset()) + if (item_result->EndOffset() < item.EndOffset()) return HandleTrailingSpaces(item, shape_result, line_info); // The break point found at the end of this text item. Continue looking @@ -608,8 +617,8 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, // If this is all trailable spaces, this item is trailable, and next item // maybe too. Don't go to |HandleOverflow()| yet. - if (IsAllBreakableSpaces(Text(), item_result->start_offset, - item_result->end_offset)) + if (IsAllBreakableSpaces(Text(), item_result->StartOffset(), + item_result->EndOffset())) return; HandleOverflow(line_info); @@ -618,8 +627,8 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, // 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()); - if (item_result->start_offset == item.StartOffset()) { + DCHECK_EQ(item_result->EndOffset(), item.EndOffset()); + if (item_result->StartOffset() == item.StartOffset()) { item_result->inline_size = shape_result.SnappedWidth().ClampNegativeToZero(); item_result->shape_result = ShapeResultView::Create(&shape_result); @@ -627,9 +636,9 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, // <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->StartOffset() >= item.StartOffset()); item_result->shape_result = ShapeResultView::Create( - &shape_result, item_result->start_offset, item_result->end_offset); + &shape_result, item_result->StartOffset(), item_result->EndOffset()); item_result->inline_size = item_result->shape_result->SnappedWidth().ClampNegativeToZero(); } @@ -652,7 +661,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( (item.Type() == NGInlineItem::kControl && Text()[item.StartOffset()] == kTabulationCharacter)); DCHECK(&item_shape_result); - item.AssertOffset(item_result->start_offset); + item.AssertOffset(item_result->StartOffset()); DCHECK_EQ(item_shape_result.StartIndex(), item.StartOffset()); DCHECK_EQ(item_shape_result.EndIndex(), item.EndOffset()); @@ -676,7 +685,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( // Use kStartShouldBeSafe if at the beginning of a line. unsigned options = ShapingLineBreaker::kDefaultOptions; - if (item_result->start_offset != line_info->StartOffset()) + if (item_result->StartOffset() != line_info->StartOffset()) options |= ShapingLineBreaker::kDontReshapeStart; // Reshaping between the last character and trailing spaces is needed only @@ -702,7 +711,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( DCHECK_LE(try_count, 2u); #endif scoped_refptr<const ShapeResultView> shape_result = breaker.ShapeLine( - item_result->start_offset, available_width.ClampNegativeToZero(), + item_result->StartOffset(), available_width.ClampNegativeToZero(), options, &result); // If this item overflows and 'break-word' is set, this line will be @@ -710,14 +719,15 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( if (!shape_result) { DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow); item_result->inline_size = available_width_with_hyphens + 1; - item_result->end_offset = item.EndOffset(); + item_result->text_offset.end = item.EndOffset(); + item_result->text_offset.AssertNotEmpty(); return kOverflow; } DCHECK_EQ(shape_result->NumCharacters(), - result.break_offset - item_result->start_offset); + result.break_offset - item_result->StartOffset()); // 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); + CHECK_GT(result.break_offset, item_result->StartOffset()); inline_size = shape_result->SnappedWidth().ClampNegativeToZero(); if (UNLIKELY(result.is_hyphenated)) { @@ -741,7 +751,8 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( item_result->hyphen_string = String(); } item_result->inline_size = inline_size; - item_result->end_offset = result.break_offset; + item_result->text_offset.end = result.break_offset; + item_result->text_offset.AssertNotEmpty(); item_result->shape_result = std::move(shape_result); break; } @@ -754,7 +765,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( // * If width > available_width: The first break opportunity does not fit. // offset is the first break opportunity, either inside, at the end, or // beyond the end. - if (item_result->end_offset < item.EndOffset()) { + if (item_result->EndOffset() < item.EndOffset()) { item_result->can_break_after = true; if (UNLIKELY(break_iterator_.BreakType() == @@ -764,9 +775,9 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( trailing_whitespace_ = WhitespaceState::kNone; } } else { - DCHECK_EQ(item_result->end_offset, item.EndOffset()); + DCHECK_EQ(item_result->EndOffset(), item.EndOffset()); item_result->can_break_after = - break_iterator_.IsBreakable(item_result->end_offset); + break_iterator_.IsBreakable(item_result->EndOffset()); if (!item_result->can_break_after && item.Type() == NGInlineItem::kText && ShouldForceCanBreakAfter(*item_result)) item_result->can_break_after = true; @@ -783,7 +794,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( } // Breaks the text item at the previous break opportunity from -// |item_result->end_offset|. Returns false if there were no previous break +// |item_result->text_offset.end|. Returns false if there were no previous break // opportunities. bool NGLineBreaker::BreakTextAtPreviousBreakOpportunity( NGInlineItemResult* item_result) { @@ -794,13 +805,14 @@ bool NGLineBreaker::BreakTextAtPreviousBreakOpportunity( DCHECK(item.Style() && item.Style()->AutoWrap()); unsigned break_opportunity = break_iterator_.PreviousBreakOpportunity( - item_result->end_offset - 1, item_result->start_offset); - if (break_opportunity <= item_result->start_offset) + item_result->EndOffset() - 1, item_result->StartOffset()); + if (break_opportunity <= item_result->StartOffset()) return false; - item_result->end_offset = break_opportunity; - item_result->shape_result = - ShapeResultView::Create(item.TextShapeResult(), item_result->start_offset, - item_result->end_offset); + item_result->text_offset.end = break_opportunity; + item_result->text_offset.AssertNotEmpty(); + item_result->shape_result = ShapeResultView::Create( + item.TextShapeResult(), item_result->StartOffset(), + item_result->EndOffset()); item_result->inline_size = item_result->shape_result->SnappedWidth().ClampNegativeToZero(); item_result->can_break_after = true; @@ -832,7 +844,7 @@ bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result, // If this is the first part of the text, it may form a word with the previous // item. Fallback to |HandleText()|. - unsigned start_offset = item_result->start_offset; + unsigned start_offset = item_result->StartOffset(); DCHECK_LT(start_offset, item.EndOffset()); if (start_offset != line_info->StartOffset() && start_offset == item.StartOffset()) @@ -895,7 +907,8 @@ bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result, return false; // Create an NGInlineItemResult that has the max of widths of all words. - item_result->end_offset = last_end_offset; + item_result->text_offset.end = last_end_offset; + item_result->text_offset.AssertNotEmpty(); item_result->inline_size = LayoutUnit::FromFloatCeil(min_width); item_result->can_break_after = true; @@ -945,7 +958,7 @@ scoped_refptr<ShapeResultView> NGLineBreaker::TruncateLineEndResult( const NGInlineItem& item = *item_result.item; // Check given offsets require to truncate |item_result.shape_result|. - const unsigned start_offset = item_result.start_offset; + const unsigned start_offset = item_result.StartOffset(); const ShapeResultView* source_result = item_result.shape_result.get(); DCHECK(source_result); DCHECK_GE(start_offset, source_result->StartIndex()); @@ -978,7 +991,7 @@ void NGLineBreaker::UpdateShapeResult(const NGLineInfo& line_info, NGInlineItemResult* item_result) { DCHECK(item_result); item_result->shape_result = - TruncateLineEndResult(line_info, *item_result, item_result->end_offset); + TruncateLineEndResult(line_info, *item_result, item_result->EndOffset()); DCHECK(item_result->shape_result); item_result->inline_size = item_result->shape_result->SnappedWidth(); } @@ -1038,8 +1051,8 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item, NGInlineItemResult* item_result = AddItem(item, end, line_info); item_result->has_only_trailing_spaces = true; item_result->shape_result = ShapeResultView::Create(&shape_result); - if (item_result->start_offset == item.StartOffset() && - item_result->end_offset == item.EndOffset()) + if (item_result->StartOffset() == item.StartOffset() && + item_result->EndOffset() == item.EndOffset()) item_result->inline_size = item_result->shape_result->SnappedWidth(); else UpdateShapeResult(*line_info, item_result); @@ -1079,7 +1092,7 @@ void NGLineBreaker::RemoveTrailingCollapsibleSpace(NGLineInfo* line_info) { if (end_index < item_results.size()) { const NGInlineItemResult& end_item_result = item_results[end_index]; unsigned end_item_index = end_item_result.item_index; - unsigned end_offset = end_item_result.start_offset; + unsigned end_offset = end_item_result.StartOffset(); ResetRewindLoopDetector(); Rewind(end_index, line_info); item_index_ = end_item_index; @@ -1100,8 +1113,8 @@ void NGLineBreaker::RemoveTrailingCollapsibleSpace(NGLineInfo* line_info) { position_ -= item_result->inline_size; if (scoped_refptr<const ShapeResultView>& collapsed_shape_result = trailing_collapsible_space_->collapsed_shape_result) { - DCHECK_GE(item_result->end_offset, item_result->start_offset + 2); - --item_result->end_offset; + --item_result->text_offset.end; + item_result->text_offset.AssertNotEmpty(); item_result->shape_result = collapsed_shape_result; item_result->inline_size = item_result->shape_result->SnappedWidth(); position_ += item_result->inline_size; @@ -1152,9 +1165,9 @@ void NGLineBreaker::ComputeTrailingCollapsibleSpace(NGLineInfo* line_info) { if (item.EndCollapseType() == NGInlineItem::kOpaqueToCollapsing) continue; if (item.Type() == NGInlineItem::kText) { - DCHECK_GT(item_result.end_offset, 0u); + DCHECK_GT(item_result.EndOffset(), 0u); DCHECK(item.Style()); - if (!IsBreakableSpace(text[item_result.end_offset - 1])) + if (!IsBreakableSpace(text[item_result.EndOffset() - 1])) break; if (!item.Style()->CollapseWhiteSpace()) { trailing_whitespace_ = WhitespaceState::kPreserved; @@ -1169,10 +1182,10 @@ void NGLineBreaker::ComputeTrailingCollapsibleSpace(NGLineInfo* line_info) { trailing_collapsible_space_->item_result != &item_result) { trailing_collapsible_space_.emplace(); trailing_collapsible_space_->item_result = &item_result; - if (item_result.end_offset - 1 > item_result.start_offset) { + if (item_result.EndOffset() - 1 > item_result.StartOffset()) { trailing_collapsible_space_->collapsed_shape_result = TruncateLineEndResult(*line_info, item_result, - item_result.end_offset - 1); + item_result.EndOffset() - 1); } } trailing_whitespace_ = WhitespaceState::kCollapsible; @@ -1377,7 +1390,8 @@ void NGLineBreaker::HandleAtomicInline( } else { DCHECK(mode_ == NGLineBreakerMode::kMinContent || !max_size_cache_); NGBlockNode child(ToLayoutBox(item.GetLayoutObject())); - MinMaxSizesInput input(percentage_resolution_block_size_for_min_max); + MinMaxSizesInput input(percentage_resolution_block_size_for_min_max, + MinMaxSizesType::kContent); MinMaxSizesResult result = ComputeMinAndMaxContentContribution(node_.Style(), child, input); if (mode_ == NGLineBreakerMode::kMinContent) { @@ -1405,6 +1419,25 @@ void NGLineBreaker::HandleAtomicInline( auto_wrap_ && !(sticky_images_quirk_ && item.IsImage()); position_ += item_result->inline_size; + + if (item.IsRubyRun()) { + // Overrides can_break_after. + ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_); + + NGAnnotationOverhang overhang = GetOverhang(*item_result); + if (overhang.end > LayoutUnit()) { + item_result->pending_end_overhang = overhang.end; + maybe_have_end_overhang_ = true; + } + + if (CanApplyStartOverhang(*line_info, overhang.start)) { + DCHECK_EQ(item_result->margins.inline_start, LayoutUnit()); + item_result->margins.inline_start = -overhang.start; + item_result->inline_size -= overhang.start; + position_ -= overhang.start; + } + } + trailing_whitespace_ = WhitespaceState::kNone; MoveToNextOf(item); } @@ -1640,8 +1673,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, // iterator cannot compute this because it considers break opportunities are // before a run of spaces. const String& text = Text(); - if (item_result->end_offset < text.length() && - IsBreakableSpace(text[item_result->end_offset])) { + if (item_result->EndOffset() < text.length() && + IsBreakableSpace(text[item_result->EndOffset()])) { item_result->can_break_after = true; return; } @@ -1720,7 +1753,7 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { BreakText(item_result, item, *item.TextShapeResult(), std::min(item_available_width, min_available_width), item_available_width, line_info); - DCHECK_LE(item_result->end_offset, item_result_before.end_offset); + DCHECK_LE(item_result->EndOffset(), item_result_before.EndOffset()); #if DCHECK_IS_ON() item_result->CheckConsistency(true); #endif @@ -1728,8 +1761,8 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { // If BreakText() changed this item small enough to fit, break here. if (item_result->can_break_after && item_result->inline_size <= item_available_width && - item_result->end_offset < item_result_before.end_offset) { - DCHECK_LT(item_result->end_offset, item.EndOffset()); + item_result->EndOffset() < item_result_before.EndOffset()) { + DCHECK_LT(item_result->EndOffset(), item.EndOffset()); // If this is the last item, adjust it to accommodate the change. const unsigned new_end = i + 1; @@ -1739,7 +1772,7 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { available_width + width_to_rewind + item_result->inline_size; DCHECK_EQ(position_, line_info->ComputeWidth()); item_index_ = item_result->item_index; - offset_ = item_result->end_offset; + offset_ = item_result->EndOffset(); items_data_.AssertOffset(item_index_, offset_); HandleTrailingSpaces(item, line_info); return; @@ -1818,11 +1851,11 @@ void NGLineBreaker::RewindOverflow(unsigned new_end, NGLineInfo* line_info) { const EWhiteSpace white_space = item.Style()->WhiteSpace(); if (ComputedStyle::AutoWrap(white_space) && white_space != EWhiteSpace::kBreakSpaces && - IsBreakableSpace(text[item_result.start_offset])) { + IsBreakableSpace(text[item_result.StartOffset()])) { // If all characters are trailable spaces, check the next item. if (item_result.shape_result && - IsAllBreakableSpaces(text, item_result.start_offset + 1, - item_result.end_offset)) { + IsAllBreakableSpaces(text, item_result.StartOffset() + 1, + item_result.EndOffset())) { continue; } // If this item starts with spaces followed by non-space characters, @@ -1842,7 +1875,7 @@ void NGLineBreaker::RewindOverflow(unsigned new_end, NGLineInfo* line_info) { // All control characters except newline are trailable if auto_wrap. We // should not have rewound if there was a newline, so safe to assume all // controls are trailable. - DCHECK_NE(text[item_result.start_offset], kNewlineCharacter); + DCHECK_NE(text[item_result.StartOffset()], kNewlineCharacter); DCHECK(item.Style()); EWhiteSpace white_space = item.Style()->WhiteSpace(); if (ComputedStyle::AutoWrap(white_space) && @@ -1953,8 +1986,9 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) { // When rewinding all items, use |results[0].start_offset|. const NGInlineItemResult& first_remove = item_results[new_end]; item_index_ = first_remove.item_index; - offset_ = first_remove.start_offset; + offset_ = first_remove.StartOffset(); trailing_whitespace_ = WhitespaceState::kLeading; + maybe_have_end_overhang_ = false; } SetCurrentStyle(ComputeCurrentStyle(new_end, line_info)); @@ -2077,7 +2111,7 @@ void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) { } void NGLineBreaker::MoveToNextOf(const NGInlineItemResult& item_result) { - offset_ = item_result.end_offset; + offset_ = item_result.EndOffset(); item_index_ = item_result.item_index; DCHECK(item_result.item); if (offset_ == item_result.item->EndOffset()) 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 05481028298..32b872488dc 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 @@ -261,6 +261,9 @@ class CORE_EXPORT NGLineBreaker { // between images, and between text and images. bool sticky_images_quirk_ = false; + // True if the resultant line contains a RubyRun with inline-end overhang. + bool maybe_have_end_overhang_ = false; + const NGInlineItemsData& items_data_; // The text content of this node. This is same as |items_data_.text_content| 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 38160e6e800..54b6f646a29 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 @@ -22,8 +22,7 @@ String ToString(NGInlineItemResults line, NGInlineNode node) { const String& text = node.ItemsData(false).text_content; for (const auto& item_result : line) { builder.Append( - StringView(text, item_result.start_offset, - item_result.end_offset - item_result.start_offset)); + StringView(text, item_result.StartOffset(), item_result.Length())); } return builder.ToString(); } @@ -535,11 +534,12 @@ TEST_F(NGLineBreakerTest, MinMaxWithTrailingSpaces) { <div id=container>12345 6789 </div> )HTML"); - auto sizes = node.ComputeMinMaxSizes( - WritingMode::kHorizontalTb, - MinMaxSizesInput(/* percentage_resolution_block_size */ ( - LayoutUnit()))) - .sizes; + auto sizes = + node.ComputeMinMaxSizes( + WritingMode::kHorizontalTb, + MinMaxSizesInput(/* percentage_resolution_block_size */ + LayoutUnit(), MinMaxSizesType::kContent)) + .sizes; EXPECT_EQ(sizes.min_size, LayoutUnit(60)); EXPECT_EQ(sizes.max_size, LayoutUnit(110)); } @@ -563,10 +563,38 @@ TEST_F(NGLineBreakerTest, TableCellWidthCalculationQuirkOutOfFlow) { node.ComputeMinMaxSizes( WritingMode::kHorizontalTb, - MinMaxSizesInput(/* percentage_resolution_block_size */ LayoutUnit())); + MinMaxSizesInput(/* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent)); // Pass if |ComputeMinMaxSize| doesn't hit DCHECK failures. } +// crbug.com/1091359 +TEST_F(NGLineBreakerTest, RewindRubyRun) { + NGInlineNode node = CreateInlineNode(R"HTML( +<div id="container"> +<style> +* { + -webkit-text-security:square; + font-size:16px; +} +</style> +<big style="word-wrap: break-word">a +<ruby dir="rtl"> +<rt> +B AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +<svg></svg> +<b> +</rt> +</ruby> + )HTML"); + + node.ComputeMinMaxSizes( + WritingMode::kHorizontalTb, + MinMaxSizesInput(/* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent)); + // This test passes if no CHECK failures. +} + #undef MAYBE_OverflowAtomicInline } // namespace } // namespace blink 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 0306094c1f4..98b3cf7d46b 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 @@ -6,6 +6,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.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_logical_line_item.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/platform/fonts/font_baseline.h" @@ -38,6 +39,7 @@ NGLineTruncator::NGLineTruncator(const NGLineInfo& line_info) const ComputedStyle& NGLineTruncator::EllipsisStyle() const { // The ellipsis is styled according to the line style. // https://drafts.csswg.org/css-ui/#ellipsing-details + DCHECK(line_style_); return *line_style_; } @@ -57,20 +59,16 @@ void NGLineTruncator::SetupEllipsis() { } LayoutUnit NGLineTruncator::PlaceEllipsisNextTo( - NGLineBoxFragmentBuilder::ChildList* line_box, - NGLineBoxFragmentBuilder::Child* ellipsized_child) { + NGLogicalLineItems* line_box, + NGLogicalLineItem* ellipsized_child) { // Create the ellipsis, associating it with the ellipsized child. DCHECK(ellipsized_child->HasInFlowFragment()); LayoutObject* ellipsized_layout_object = - ellipsized_child->PhysicalFragment()->GetMutableLayoutObject(); + ellipsized_child->GetMutableLayoutObject(); DCHECK(ellipsized_layout_object); DCHECK(ellipsized_layout_object->IsInline()); DCHECK(ellipsized_layout_object->IsText() || ellipsized_layout_object->IsAtomicInlineLevel()); - NGTextFragmentBuilder builder(line_style_->GetWritingMode()); - builder.SetText(ellipsized_layout_object, ellipsis_text_, &EllipsisStyle(), - true /* is_ellipsis_style */, - std::move(ellipsis_shape_result_)); // Now the offset of the ellpisis is determined. Place the ellpisis into the // line box. @@ -78,17 +76,23 @@ LayoutUnit NGLineTruncator::PlaceEllipsisNextTo( IsLtr(line_direction_) ? ellipsized_child->InlineOffset() + ellipsized_child->inline_size : ellipsized_child->InlineOffset() - ellipsis_width_; - LayoutUnit ellpisis_ascent; + NGLineHeightMetrics ellipsis_metrics; DCHECK(ellipsis_font_data_); if (ellipsis_font_data_) { - FontBaseline baseline_type = line_style_->GetFontBaseline(); - NGLineHeightMetrics ellipsis_metrics(ellipsis_font_data_->GetFontMetrics(), - baseline_type); - ellpisis_ascent = ellipsis_metrics.ascent; + ellipsis_metrics = NGLineHeightMetrics( + ellipsis_font_data_->GetFontMetrics(), line_style_->GetFontBaseline()); } - line_box->AddChild(builder.ToTextFragment(), - LogicalOffset{ellipsis_inline_offset, -ellpisis_ascent}, - ellipsis_width_, 0); + + DCHECK(ellipsis_text_); + DCHECK(ellipsis_shape_result_.get()); + NGTextFragmentBuilder builder(line_style_->GetWritingMode()); + builder.SetText(ellipsized_layout_object, ellipsis_text_, &EllipsisStyle(), + NGStyleVariant::kEllipsis, std::move(ellipsis_shape_result_), + {ellipsis_width_, ellipsis_metrics.LineHeight()}); + line_box->AddChild( + builder.ToTextFragment(), + LogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent}, + ellipsis_width_, 0); return ellipsis_inline_offset; } @@ -97,12 +101,13 @@ wtf_size_t NGLineTruncator::AddTruncatedChild( bool leave_one_character, LayoutUnit position, TextDirection edge, - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, NGInlineLayoutStateStack* box_states) { - NGLineBoxFragmentBuilder::ChildList& line = *line_box; - + NGLogicalLineItems& line = *line_box; + const NGLogicalLineItem& source_item = line[source_index]; + DCHECK(source_item.shape_result); scoped_refptr<ShapeResult> shape_result = - line[source_index].fragment->TextShapeResult()->CreateShapeResult(); + source_item.shape_result->CreateShapeResult(); unsigned text_offset = shape_result->OffsetToFit(position, edge); if (IsLtr(edge) ? IsLeftMostOffset(*shape_result, text_offset) : IsRightMostOffset(*shape_result, text_offset)) { @@ -116,51 +121,41 @@ wtf_size_t NGLineTruncator::AddTruncatedChild( edge); } - const auto& fragment = line[source_index].fragment; - const bool keep_start = edge == fragment->ResolvedDirection(); - scoped_refptr<const NGPhysicalTextFragment> truncated_fragment = - keep_start ? fragment->TrimText(fragment->StartOffset(), - fragment->StartOffset() + text_offset) - : fragment->TrimText(fragment->StartOffset() + text_offset, - fragment->EndOffset()); - wtf_size_t new_index = line.size(); - line.AddChild(); + const wtf_size_t new_index = line.size(); + line.AddChild(TruncateText(source_item, *shape_result, text_offset, edge)); box_states->ChildInserted(new_index); - line[new_index] = line[source_index]; - line[new_index].inline_size = line_style_->IsHorizontalWritingMode() - ? truncated_fragment->Size().width - : truncated_fragment->Size().height; - line[new_index].fragment = std::move(truncated_fragment); return new_index; } -LayoutUnit NGLineTruncator::TruncateLine( - LayoutUnit line_width, - NGLineBoxFragmentBuilder::ChildList* line_box, - NGInlineLayoutStateStack* box_states) { +LayoutUnit NGLineTruncator::TruncateLine(LayoutUnit line_width, + NGLogicalLineItems* line_box, + NGInlineLayoutStateStack* box_states) { + DCHECK(std::all_of(line_box->begin(), line_box->end(), + [](const auto& item) { return !item.fragment; })); + // Shape the ellipsis and compute its inline size. SetupEllipsis(); // Loop children from the logical last to the logical first to determine where // to place the ellipsis. Children maybe truncated or moved as part of the // process. - NGLineBoxFragmentBuilder::Child* ellipsized_child = nullptr; - scoped_refptr<const NGPhysicalTextFragment> truncated_fragment; + NGLogicalLineItem* ellipsized_child = nullptr; + base::Optional<NGLogicalLineItem> truncated_child; if (IsLtr(line_direction_)) { - NGLineBoxFragmentBuilder::Child* first_child = line_box->FirstInFlowChild(); + NGLogicalLineItem* first_child = line_box->FirstInFlowChild(); for (auto it = line_box->rbegin(); it != line_box->rend(); it++) { auto& child = *it; if (EllipsizeChild(line_width, ellipsis_width_, &child == first_child, - &child, &truncated_fragment)) { + &child, &truncated_child)) { ellipsized_child = &child; break; } } } else { - NGLineBoxFragmentBuilder::Child* first_child = line_box->LastInFlowChild(); + NGLogicalLineItem* first_child = line_box->LastInFlowChild(); for (auto& child : *line_box) { if (EllipsizeChild(line_width, ellipsis_width_, &child == first_child, - &child, &truncated_fragment)) { + &child, &truncated_child)) { ellipsized_child = &child; break; } @@ -172,28 +167,23 @@ LayoutUnit NGLineTruncator::TruncateLine( return line_width; // Truncate the text fragment if needed. - if (truncated_fragment) { - DCHECK(ellipsized_child->fragment); + if (truncated_child) { // In order to preserve layout information before truncated, hide the // original fragment and insert a truncated one. size_t child_index_to_truncate = ellipsized_child - line_box->begin(); - line_box->InsertChild(child_index_to_truncate + 1); + line_box->InsertChild(child_index_to_truncate + 1, + std::move(*truncated_child)); box_states->ChildInserted(child_index_to_truncate + 1); - NGLineBoxFragmentBuilder::Child* child_to_truncate = + NGLogicalLineItem* child_to_truncate = &(*line_box)[child_index_to_truncate]; ellipsized_child = std::next(child_to_truncate); - *ellipsized_child = *child_to_truncate; + HideChild(child_to_truncate); - LayoutUnit new_inline_size = line_style_->IsHorizontalWritingMode() - ? truncated_fragment->Size().width - : truncated_fragment->Size().height; - DCHECK_LE(new_inline_size, ellipsized_child->inline_size); + DCHECK_LE(ellipsized_child->inline_size, child_to_truncate->inline_size); if (UNLIKELY(IsRtl(line_direction_))) { ellipsized_child->rect.offset.inline_offset += - ellipsized_child->inline_size - new_inline_size; + child_to_truncate->inline_size - ellipsized_child->inline_size; } - ellipsized_child->inline_size = new_inline_size; - ellipsized_child->fragment = std::move(truncated_fragment); } // Create the ellipsis, associating it with the ellipsized child. @@ -212,24 +202,27 @@ LayoutUnit NGLineTruncator::TruncateLine( // Children with IsPlaceholder() can appear anywhere. LayoutUnit NGLineTruncator::TruncateLineInTheMiddle( LayoutUnit line_width, - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, NGInlineLayoutStateStack* box_states) { // Shape the ellipsis and compute its inline size. SetupEllipsis(); - NGLineBoxFragmentBuilder::ChildList& line = *line_box; + NGLogicalLineItems& line = *line_box; wtf_size_t initial_index_left = kNotFound; wtf_size_t initial_index_right = kNotFound; for (wtf_size_t i = 0; i < line_box->size(); ++i) { auto& child = line[i]; - if (!child.fragment && child.IsPlaceholder()) + if (child.IsPlaceholder()) continue; - if (child.HasOutOfFlowFragment() || !child.fragment || - !child.fragment->TextShapeResult()) { + if (!child.shape_result) { if (initial_index_right != kNotFound) break; continue; } + // Skip pseudo elements like ::before. + if (!child.GetNode()) + continue; + if (initial_index_left == kNotFound) initial_index_left = i; initial_index_right = i; @@ -378,7 +371,7 @@ LayoutUnit NGLineTruncator::TruncateLineInTheMiddle( // Hide this child from being painted. Leaves a hidden fragment so that layout // queries such as |offsetWidth| work as if it is not truncated. -void NGLineTruncator::HideChild(NGLineBoxFragmentBuilder::Child* child) { +void NGLineTruncator::HideChild(NGLogicalLineItem* child) { DCHECK(child->HasInFlowFragment()); if (const NGPhysicalTextFragment* text = child->fragment.get()) { @@ -393,22 +386,15 @@ void NGLineTruncator::HideChild(NGLineBoxFragmentBuilder::Child* child) { if (fragment.HasOutOfFlowPositionedDescendants()) return; - // If this child has self painting layer, not producing fragments will not - // suppress painting because layers are painted separately. Move it out of - // the clipping area. - if (fragment.HasSelfPaintingLayer()) { - // |available_width_| may not be enough when the containing block has - // paddings, because clipping is at the content box but ellipsizing is at - // the padding box. Just move to the max because we don't know paddings, - // and max should do what we need. - child->rect.offset.inline_offset = LayoutUnit::NearlyMax(); - return; - } - child->layout_result = fragment.CloneAsHiddenForPaint(); return; } + if (child->inline_item) { + child->is_hidden_for_paint = true; + return; + } + NOTREACHED(); } @@ -419,9 +405,9 @@ bool NGLineTruncator::EllipsizeChild( LayoutUnit line_width, LayoutUnit ellipsis_width, bool is_first_child, - NGLineBoxFragmentBuilder::Child* child, - scoped_refptr<const NGPhysicalTextFragment>* truncated_fragment) { - DCHECK(truncated_fragment && !*truncated_fragment); + NGLogicalLineItem* child, + base::Optional<NGLogicalLineItem>* truncated_child) { + DCHECK(truncated_child && !*truncated_child); // Leave out-of-flow children as is. if (!child->HasInFlowFragment()) @@ -429,8 +415,7 @@ bool NGLineTruncator::EllipsizeChild( // Inline boxes should not be ellipsized. Usually they will be created in the // later phase, but empty inline box are already created. - if (child->layout_result && - child->layout_result->PhysicalFragment().IsInlineBox()) + if (child->IsInlineBox()) return false; // Can't place ellipsis if this child is completely outside of the box. @@ -449,19 +434,20 @@ bool NGLineTruncator::EllipsizeChild( } // At least part of this child is in the box. - // If not all of this child can fit, try to truncate. + // If |child| can fit in the space, truncate this line at the end of |child|. space_for_child -= ellipsis_width; - if (space_for_child < child->inline_size && - !TruncateChild(space_for_child, is_first_child, *child, - truncated_fragment)) { - // This child is partially in the box, but it should not be visible because - // earlier sibling will be truncated and ellipsized. - if (!is_first_child) - HideChild(child); - return false; - } + if (space_for_child >= child->inline_size) + return true; - return true; + // If not all of this child can fit, try to truncate. + if (TruncateChild(space_for_child, is_first_child, *child, truncated_child)) + return true; + + // This child is partially in the box, but it can't be truncated to fit. It + // should not be visible because earlier sibling will be truncated. + if (!is_first_child) + HideChild(child); + return false; } // Truncate the specified child. Returns true if truncated successfully, false @@ -474,50 +460,53 @@ bool NGLineTruncator::EllipsizeChild( bool NGLineTruncator::TruncateChild( LayoutUnit space_for_child, bool is_first_child, - const NGLineBoxFragmentBuilder::Child& child, - scoped_refptr<const NGPhysicalTextFragment>* truncated_fragment) { - DCHECK(truncated_fragment && !*truncated_fragment); + const NGLogicalLineItem& child, + base::Optional<NGLogicalLineItem>* truncated_child) { + DCHECK(truncated_child && !*truncated_child); + DCHECK(!child.fragment); // If the space is not enough, try the next child. if (space_for_child <= 0 && !is_first_child) return false; // Only text fragments can be truncated. - if (!child.fragment) - return is_first_child; - auto& fragment = To<NGPhysicalTextFragment>(*child.fragment); - - // No need to truncate empty results. - if (!fragment.TextShapeResult()) + if (!child.shape_result) return is_first_child; // TODO(layout-dev): Add support for OffsetToFit to ShapeResultView to avoid // this copy. - scoped_refptr<blink::ShapeResult> shape_result = - fragment.TextShapeResult()->CreateShapeResult(); - if (!shape_result) - return is_first_child; - + scoped_refptr<ShapeResult> shape_result = + child.shape_result->CreateShapeResult(); + DCHECK(shape_result); + const NGTextOffset original_offset = child.text_offset; // Compute the offset to truncate. - unsigned new_length = shape_result->OffsetToFit( + unsigned offset_to_fit = shape_result->OffsetToFit( IsLtr(line_direction_) ? space_for_child : shape_result->Width() - space_for_child, line_direction_); - DCHECK_LE(new_length, fragment.TextLength()); - if (!new_length || new_length == fragment.TextLength()) { + DCHECK_LE(offset_to_fit, original_offset.Length()); + if (!offset_to_fit || offset_to_fit == original_offset.Length()) { if (!is_first_child) return false; - new_length = !new_length ? 1 : new_length - 1; + offset_to_fit = !offset_to_fit ? 1 : offset_to_fit - 1; } - - // Truncate the text fragment. - *truncated_fragment = - line_direction_ == shape_result->Direction() - ? fragment.TrimText(fragment.StartOffset(), - fragment.StartOffset() + new_length) - : fragment.TrimText(fragment.StartOffset() + new_length, - fragment.EndOffset()); + *truncated_child = + TruncateText(child, *shape_result, offset_to_fit, line_direction_); return true; } +NGLogicalLineItem NGLineTruncator::TruncateText(const NGLogicalLineItem& item, + const ShapeResult& shape_result, + unsigned offset_to_fit, + TextDirection direction) { + const NGTextOffset new_text_offset = + direction == shape_result.Direction() + ? NGTextOffset(item.StartOffset(), item.StartOffset() + offset_to_fit) + : NGTextOffset(item.StartOffset() + offset_to_fit, item.EndOffset()); + scoped_refptr<ShapeResultView> new_shape_result = ShapeResultView::Create( + &shape_result, new_text_offset.start, new_text_offset.end); + DCHECK(item.inline_item); + return NGLogicalLineItem(item, std::move(new_shape_result), new_text_offset); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h index c65fa3ce5a4..024e2d3cfec 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h @@ -14,6 +14,8 @@ namespace blink { class NGInlineLayoutStateStack; class NGLineInfo; +class NGLogicalLineItems; +struct NGLogicalLineItem; // A class to truncate lines and place ellipsis, invoked by the CSS // 'text-overflow: ellipsis' property. @@ -30,13 +32,12 @@ class CORE_EXPORT NGLineTruncator final { // |line_box| should be after bidi reorder, but before box fragments are // created. LayoutUnit TruncateLine(LayoutUnit line_width, - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, NGInlineLayoutStateStack* box_states); - LayoutUnit TruncateLineInTheMiddle( - LayoutUnit line_width, - NGLineBoxFragmentBuilder::ChildList* line_box, - NGInlineLayoutStateStack* box_states); + LayoutUnit TruncateLineInTheMiddle(LayoutUnit line_width, + NGLogicalLineItems* line_box, + NGInlineLayoutStateStack* box_states); private: const ComputedStyle& EllipsisStyle() const; @@ -45,9 +46,8 @@ class CORE_EXPORT NGLineTruncator final { void SetupEllipsis(); // Add a child for ellipsis next to |ellipsized_child|. - LayoutUnit PlaceEllipsisNextTo( - NGLineBoxFragmentBuilder::ChildList* line_box, - NGLineBoxFragmentBuilder::Child* ellipsized_child); + LayoutUnit PlaceEllipsisNextTo(NGLogicalLineItems* line_box, + NGLogicalLineItem* ellipsized_child); static constexpr wtf_size_t kDidNotAddChild = WTF::kNotFound; // Add a child with truncated text of (*line_box)[source_index]. @@ -65,20 +65,25 @@ class CORE_EXPORT NGLineTruncator final { bool leave_one_character, LayoutUnit position, TextDirection edge, - NGLineBoxFragmentBuilder::ChildList* line_box, + NGLogicalLineItems* line_box, NGInlineLayoutStateStack* box_states); - bool EllipsizeChild( - LayoutUnit line_width, - LayoutUnit ellipsis_width, - bool is_first_child, - NGLineBoxFragmentBuilder::Child*, - scoped_refptr<const NGPhysicalTextFragment>* truncated_fragment); - bool TruncateChild( - LayoutUnit space_for_this_child, - bool is_first_child, - const NGLineBoxFragmentBuilder::Child& child, - scoped_refptr<const NGPhysicalTextFragment>* truncated_fragment); - void HideChild(NGLineBoxFragmentBuilder::Child* child); + bool EllipsizeChild(LayoutUnit line_width, + LayoutUnit ellipsis_width, + bool is_first_child, + NGLogicalLineItem*, + base::Optional<NGLogicalLineItem>* truncated_child); + bool TruncateChild(LayoutUnit space_for_this_child, + bool is_first_child, + const NGLogicalLineItem& child, + base::Optional<NGLogicalLineItem>* truncated_child); + // Create |NGLogicalLineItem| by truncating text |item| at |offset_to_fit|. + // |direction| specifies which side of the text is trimmed; if |kLtr|, it + // keeps the left end and trims the right end. + NGLogicalLineItem TruncateText(const NGLogicalLineItem& item, + const ShapeResult& shape_result, + unsigned offset_to_fit, + TextDirection direction); + void HideChild(NGLogicalLineItem* child); scoped_refptr<const ComputedStyle> line_style_; LayoutUnit available_width_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.cc new file mode 100644 index 00000000000..246b6c54f1b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.cc @@ -0,0 +1,121 @@ +// 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/inline/ng_logical_line_item.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_text_fragment_builder.h" + +namespace blink { + +const LayoutObject* NGLogicalLineItem::GetLayoutObject() const { + if (inline_item) + return inline_item->GetLayoutObject(); + if (const NGPhysicalFragment* fragment = PhysicalFragment()) + return fragment->GetLayoutObject(); + return nullptr; +} + +LayoutObject* NGLogicalLineItem::GetMutableLayoutObject() const { + if (inline_item) + return inline_item->GetLayoutObject(); + if (const NGPhysicalFragment* fragment = PhysicalFragment()) + return fragment->GetMutableLayoutObject(); + return nullptr; +} + +const Node* NGLogicalLineItem::GetNode() const { + if (const LayoutObject* layout_object = GetLayoutObject()) + return layout_object->GetNode(); + return nullptr; +} + +const ComputedStyle* NGLogicalLineItem::Style() const { + if (const auto* fragment = PhysicalFragment()) + return &fragment->Style(); + if (inline_item) + return inline_item->Style(); + return nullptr; +} + +void NGLogicalLineItems::CreateTextFragments(WritingMode writing_mode, + const String& text_content) { + NGTextFragmentBuilder text_builder(writing_mode); + for (auto& child : *this) { + if (const NGInlineItem* inline_item = child.inline_item) { + if (UNLIKELY(child.text_content)) { + // Create a generated text fragmment. + text_builder.SetText(inline_item->GetLayoutObject(), child.text_content, + inline_item->Style(), inline_item->StyleVariant(), + std::move(child.shape_result), child.MarginSize()); + } else { + // Create a regular text fragmment. + DCHECK((inline_item->Type() == NGInlineItem::kText && + (inline_item->TextType() == NGTextType::kNormal || + inline_item->TextType() == NGTextType::kSymbolMarker)) || + inline_item->Type() == NGInlineItem::kControl); + text_builder.SetItem(text_content, *inline_item, + std::move(child.shape_result), child.text_offset, + child.MarginSize()); + } + text_builder.SetIsHiddenForPaint(child.is_hidden_for_paint); + DCHECK(!child.fragment); + child.fragment = text_builder.ToTextFragment(); + } + } +} + +NGLogicalLineItem* NGLogicalLineItems::FirstInFlowChild() { + for (auto& child : *this) { + if (child.HasInFlowFragment()) + return &child; + } + return nullptr; +} + +NGLogicalLineItem* NGLogicalLineItems::LastInFlowChild() { + for (auto it = rbegin(); it != rend(); it++) { + auto& child = *it; + if (child.HasInFlowFragment()) + return &child; + } + return nullptr; +} + +void NGLogicalLineItems::WillInsertChild(unsigned insert_before) { + unsigned index = 0; + for (NGLogicalLineItem& child : children_) { + if (index >= insert_before) + break; + if (child.children_count && index + child.children_count > insert_before) + ++child.children_count; + ++index; + } +} + +void NGLogicalLineItems::MoveInInlineDirection(LayoutUnit delta) { + for (auto& child : children_) + child.rect.offset.inline_offset += delta; +} + +void NGLogicalLineItems::MoveInInlineDirection(LayoutUnit delta, + unsigned start, + unsigned end) { + for (unsigned index = start; index < end; index++) + children_[index].rect.offset.inline_offset += delta; +} + +void NGLogicalLineItems::MoveInBlockDirection(LayoutUnit delta) { + for (auto& child : children_) + child.rect.offset.block_offset += delta; +} + +void NGLogicalLineItems::MoveInBlockDirection(LayoutUnit delta, + unsigned start, + unsigned end) { + for (unsigned index = start; index < end; index++) + children_[index].rect.offset.block_offset += delta; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.h new file mode 100644 index 00000000000..92eaa4b0eaa --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.h @@ -0,0 +1,301 @@ +// 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 THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LOGICAL_LINE_ITEM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LOGICAL_LINE_ITEM_H_ + +#include "third_party/blink/renderer/core/layout/geometry/logical_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_physical_text_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" +#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" + +namespace blink { + +class LayoutObject; + +// This class represents an item in a line, after line break, but still mutable +// and in the logical coordinate system. +struct NGLogicalLineItem { + DISALLOW_NEW(); + + // Empty constructor needed for |resize()|. + NGLogicalLineItem() = default; + // Create a placeholder. A placeholder does not have a fragment nor a bidi + // level. + NGLogicalLineItem(LayoutUnit block_offset, LayoutUnit block_size) + : rect(LayoutUnit(), block_offset, LayoutUnit(), block_size) {} + // Crete a bidi control. A bidi control does not have a fragment, but has + // bidi level and affects bidi reordering. + explicit NGLogicalLineItem(UBiDiLevel bidi_level) : bidi_level(bidi_level) {} + // Create an in-flow |NGLayoutResult|. + NGLogicalLineItem(scoped_refptr<const NGLayoutResult> layout_result, + const LogicalRect& rect, + unsigned children_count, + UBiDiLevel bidi_level) + : layout_result(std::move(layout_result)), + rect(rect), + children_count(children_count), + bidi_level(bidi_level) {} + NGLogicalLineItem(scoped_refptr<const NGLayoutResult> layout_result, + LogicalOffset offset, + LayoutUnit inline_size, + unsigned children_count, + UBiDiLevel bidi_level) + : layout_result(std::move(layout_result)), + rect(offset, LogicalSize()), + inline_size(inline_size), + children_count(children_count), + bidi_level(bidi_level) {} + // Create an in-flow text fragment. + NGLogicalLineItem(const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const NGTextOffset& text_offset, + LayoutUnit block_offset, + LayoutUnit inline_size, + LayoutUnit text_height, + UBiDiLevel bidi_level) + : inline_item(&inline_item), + shape_result(std::move(shape_result)), + text_offset(text_offset), + rect(LayoutUnit(), block_offset, LayoutUnit(), text_height), + inline_size(inline_size), + bidi_level(bidi_level) {} + NGLogicalLineItem(const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const String& text_content, + LayoutUnit block_offset, + LayoutUnit inline_size, + LayoutUnit text_height, + UBiDiLevel bidi_level) + : inline_item(&inline_item), + shape_result(std::move(shape_result)), + text_offset( + {this->shape_result->StartIndex(), this->shape_result->EndIndex()}), + text_content(text_content), + rect(LayoutUnit(), block_offset, LayoutUnit(), text_height), + inline_size(inline_size), + bidi_level(bidi_level) {} + NGLogicalLineItem(const NGLogicalLineItem& source_item, + scoped_refptr<const ShapeResultView> shape_result, + const NGTextOffset& text_offset) + : inline_item(source_item.inline_item), + shape_result(std::move(shape_result)), + text_offset(text_offset), + text_content(source_item.text_content), + rect(source_item.rect), + inline_size(this->shape_result->SnappedWidth()), + bidi_level(source_item.bidi_level) {} + NGLogicalLineItem(scoped_refptr<const NGPhysicalTextFragment> fragment, + LogicalOffset offset, + LayoutUnit inline_size, + UBiDiLevel bidi_level) + : fragment(std::move(fragment)), + rect(offset, LogicalSize()), + inline_size(inline_size), + bidi_level(bidi_level) {} + NGLogicalLineItem(scoped_refptr<const NGPhysicalTextFragment> fragment, + LayoutUnit block_offset, + LayoutUnit inline_size, + UBiDiLevel bidi_level) + : fragment(std::move(fragment)), + rect(LayoutUnit(), block_offset, LayoutUnit(), LayoutUnit()), + inline_size(inline_size), + bidi_level(bidi_level) {} + // Create an out-of-flow positioned object. + NGLogicalLineItem(LayoutObject* out_of_flow_positioned_box, + UBiDiLevel bidi_level, + TextDirection container_direction) + : out_of_flow_positioned_box(out_of_flow_positioned_box), + bidi_level(bidi_level), + container_direction(container_direction) {} + // Create an unpositioned float. + NGLogicalLineItem(LayoutObject* unpositioned_float, UBiDiLevel bidi_level) + : unpositioned_float(unpositioned_float), bidi_level(bidi_level) {} + // Create a positioned float. + NGLogicalLineItem(scoped_refptr<const NGLayoutResult> layout_result, + NGBfcOffset bfc_offset, + UBiDiLevel bidi_level) + : layout_result(std::move(layout_result)), + bfc_offset(bfc_offset), + bidi_level(bidi_level) {} + + bool IsInlineBox() const { + return layout_result && layout_result->PhysicalFragment().IsInlineBox(); + } + bool HasInFlowFragment() const { + return fragment || inline_item || + (layout_result && !layout_result->PhysicalFragment().IsFloating()); + } + bool HasInFlowOrFloatingFragment() const { + return fragment || inline_item || layout_result; + } + bool HasOutOfFlowFragment() const { return out_of_flow_positioned_box; } + bool HasFragment() const { + return HasInFlowOrFloatingFragment() || HasOutOfFlowFragment(); + } + bool IsControl() const { + return inline_item && inline_item->Type() == NGInlineItem::kControl; + } + bool CanCreateFragmentItem() const { return HasInFlowOrFloatingFragment(); } + bool HasBidiLevel() const { return bidi_level != 0xff; } + bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); } + bool IsOpaqueToBidiReordering() const { + if (IsPlaceholder()) + return true; + // Skip all inline boxes. Fragments for inline boxes maybe created earlier + // if they have no children. + if (layout_result) { + const LayoutObject* layout_object = + layout_result->PhysicalFragment().GetLayoutObject(); + DCHECK(layout_object); + if (layout_object->IsLayoutInline()) + return true; + } + return false; + } + + const LogicalOffset& Offset() const { return rect.offset; } + LayoutUnit InlineOffset() const { return rect.offset.inline_offset; } + LayoutUnit BlockOffset() const { return rect.offset.block_offset; } + LayoutUnit BlockEndOffset() const { return rect.BlockEndOffset(); } + const LogicalSize& Size() const { return rect.size; } + LogicalSize MarginSize() const { return {inline_size, Size().block_size}; } + + const NGPhysicalFragment* PhysicalFragment() const { + if (layout_result) + return &layout_result->PhysicalFragment(); + return fragment.get(); + } + const LayoutObject* GetLayoutObject() const; + LayoutObject* GetMutableLayoutObject() const; + const Node* GetNode() const; + const ComputedStyle* Style() const; + + unsigned StartOffset() const { return text_offset.start; } + unsigned EndOffset() const { return text_offset.end; } + + TextDirection ResolvedDirection() const { + // Inline boxes are not leaves that they don't have directions. + DCHECK(HasBidiLevel() || IsInlineBox()); + return HasBidiLevel() ? DirectionFromLevel(bidi_level) + : TextDirection::kLtr; + } + + scoped_refptr<const NGLayoutResult> layout_result; + scoped_refptr<const NGPhysicalTextFragment> fragment; + + // Data to create a text fragment from. + const NGInlineItem* inline_item = nullptr; + scoped_refptr<const ShapeResultView> shape_result; + NGTextOffset text_offset; + + // Data to create a generated text fragment. + String text_content; + + 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. + LogicalRect rect; + // The offset of a positioned float wrt. the root BFC. This should only be + // set for positioned floats. + NGBfcOffset bfc_offset; + // The inline size of the margin box. + LayoutUnit inline_size; + LayoutUnit margin_line_left; + // The index of |box_data_list_|, used in |PrepareForReorder()| and + // |UpdateAfterReorder()| to track children of boxes across BiDi reorder. + unsigned box_data_index = 0; + // For an inline box, shows the number of descendant |Child|ren, including + // empty ones. Includes itself, so 1 means no descendants. 0 if not an + // inline box. Available only after |CreateBoxFragments()|. + unsigned children_count = 0; + UBiDiLevel bidi_level = 0xff; + // The current text direction for OOF positioned items. + TextDirection container_direction = TextDirection::kLtr; + + bool is_hidden_for_paint = false; +}; + +// A vector of Child. +// Unlike the fragment builder, chlidren are mutable. +// Callers can add to the fragment builder in a batch once finalized. +class NGLogicalLineItems { + STACK_ALLOCATED(); + + public: + NGLogicalLineItems() = default; + void operator=(NGLogicalLineItems&& other) { + children_ = std::move(other.children_); + } + + NGLogicalLineItem& operator[](wtf_size_t i) { return children_[i]; } + const NGLogicalLineItem& operator[](wtf_size_t i) const { + return children_[i]; + } + + wtf_size_t size() const { return children_.size(); } + bool IsEmpty() const { return children_.IsEmpty(); } + void ReserveInitialCapacity(unsigned capacity) { + children_.ReserveInitialCapacity(capacity); + } + void Shrink(wtf_size_t size) { children_.Shrink(size); } + + using iterator = Vector<NGLogicalLineItem, 16>::iterator; + iterator begin() { return children_.begin(); } + iterator end() { return children_.end(); } + using const_iterator = Vector<NGLogicalLineItem, 16>::const_iterator; + const_iterator begin() const { return children_.begin(); } + const_iterator end() const { return children_.end(); } + using reverse_iterator = Vector<NGLogicalLineItem, 16>::reverse_iterator; + reverse_iterator rbegin() { return children_.rbegin(); } + reverse_iterator rend() { return children_.rend(); } + using const_reverse_iterator = + Vector<NGLogicalLineItem, 16>::const_reverse_iterator; + const_reverse_iterator rbegin() const { return children_.rbegin(); } + const_reverse_iterator rend() const { return children_.rend(); } + + NGLogicalLineItem* FirstInFlowChild(); + NGLogicalLineItem* LastInFlowChild(); + + // Add a child. Accepts all constructor arguments for |NGLogicalLineItem|. + template <class... Args> + void AddChild(Args&&... args) { + children_.emplace_back(std::forward<Args>(args)...); + } + void InsertChild(unsigned index, NGLogicalLineItem&& item) { + WillInsertChild(index); + children_.insert(index, item); + } + void InsertChild(unsigned index, + scoped_refptr<const NGLayoutResult> layout_result, + const LogicalRect& rect, + unsigned children_count) { + WillInsertChild(index); + children_.insert( + index, NGLogicalLineItem(std::move(layout_result), rect, children_count, + /* bidi_level */ 0)); + } + + void MoveInInlineDirection(LayoutUnit); + void MoveInInlineDirection(LayoutUnit, unsigned start, unsigned end); + void MoveInBlockDirection(LayoutUnit); + void MoveInBlockDirection(LayoutUnit, unsigned start, unsigned end); + + // Create |NGPhysicalTextFragment| for all text children. + void CreateTextFragments(WritingMode writing_mode, + const String& text_content); + + private: + void WillInsertChild(unsigned index); + + Vector<NGLogicalLineItem, 16> children_; +}; + +} // namespace blink + +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGLogicalLineItem) + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LOGICAL_LINE_ITEM_H_ 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 5fb2fef7142..a199d2c241b 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 @@ -513,6 +513,35 @@ Position NGOffsetMapping::GetFirstPosition(unsigned offset) const { return CreatePositionForOffsetMapping(node, dom_offset); } +const NGOffsetMappingUnit* NGOffsetMapping::GetFirstMappingUnit( + unsigned offset) const { + // Find the first unit where |unit.TextContentEnd() <= offset| + if (units_.IsEmpty() || units_.front().TextContentStart() > offset) + return nullptr; + const NGOffsetMappingUnit* result = + std::lower_bound(units_.begin(), units_.end(), offset, + [](const NGOffsetMappingUnit& unit, unsigned offset) { + return unit.TextContentEnd() < offset; + }); + if (result == units_.end()) + return nullptr; + const NGOffsetMappingUnit* next_unit = std::next(result); + if (next_unit != units_.end() && next_unit->TextContentStart() == offset) { + // For offset=2, returns [1] instead of [0]. + // For offset=3, returns [3] instead of [2], + // in below example: + // text_content = "ab\ncd" + // offset mapping unit: + // [0] I DOM:0-2 TC:0-2 "ab" + // [1] C DOM:2-3 TC:2-2 + // [2] I DOM:3-4 TC:2-3 "\n" + // [3] C DOM:4-5 TC:3-3 + // [4] I DOM:5-7 TC:3-5 "cd" + return next_unit; + } + return result; +} + const NGOffsetMappingUnit* NGOffsetMapping::GetLastMappingUnit( unsigned offset) const { // Find the last unit where |unit.TextContentStart() <= offset| 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 d5869ad6ea9..c4ee39eb926 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 @@ -214,6 +214,10 @@ class CORE_EXPORT NGOffsetMapping { base::span<const NGOffsetMappingUnit> GetMappingUnitsForTextContentOffsetRange(unsigned start, unsigned end) const; + // Returns the first |NGOffsetMappingUnit| where |TextContentStart() >= + // offset| including unit for generated content. + const NGOffsetMappingUnit* GetFirstMappingUnit(unsigned offset) const; + // Returns the last |NGOffsetMappingUnit| where |TextContentStart() >= offset| // including unit for generated content. const NGOffsetMappingUnit* GetLastMappingUnit(unsigned offset) const; 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 da14a44e464..d31c0787812 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 @@ -46,12 +46,12 @@ bool operator!=(const NGOffsetMappingUnit& unit, return !operator==(unit, other); } -void PrintTo(const NGOffsetMappingUnit& unit, std::ostream& ostream) { +void PrintTo(const NGOffsetMappingUnit& unit, std::ostream* ostream) { static const char* kTypeNames[] = {"Identity", "Collapsed", "Expanded"}; - ostream << "{" << kTypeNames[static_cast<unsigned>(unit.GetType())] << " " - << unit.GetLayoutObject() << " dom=" << unit.DOMStart() << "-" - << unit.DOMEnd() << " tc=" << unit.TextContentStart() << "-" - << unit.TextContentEnd() << "}"; + *ostream << "{" << kTypeNames[static_cast<unsigned>(unit.GetType())] << " " + << unit.GetLayoutObject() << " dom=" << unit.DOMStart() << "-" + << unit.DOMEnd() << " tc=" << unit.TextContentStart() << "-" + << unit.TextContentEnd() << "}"; } bool operator==(const Vector<NGOffsetMappingUnit>& units1, @@ -72,19 +72,19 @@ bool operator==(const Vector<NGOffsetMappingUnit>& units, return units == ToVector(range); } -void PrintTo(const Vector<NGOffsetMappingUnit>& units, std::ostream& ostream) { - ostream << "["; +void PrintTo(const Vector<NGOffsetMappingUnit>& units, std::ostream* ostream) { + *ostream << "["; const char* comma = ""; for (const auto& unit : units) { - ostream << comma; + *ostream << comma; PrintTo(unit, ostream); comma = ", "; } - ostream << "]"; + *ostream << "]"; } void PrintTo(const base::span<const NGOffsetMappingUnit>& range, - std::ostream& ostream) { + std::ostream* ostream) { PrintTo(ToVector(range), ostream); } @@ -145,6 +145,17 @@ class NGOffsetMappingTest : public NGLayoutTest { return result.ToString(); } + Vector<NGOffsetMappingUnit> GetFirstLast(const std::string& caret_text) { + const auto offset = caret_text.find('|'); + return {*GetOffsetMapping().GetFirstMappingUnit(offset), + *GetOffsetMapping().GetLastMappingUnit(offset)}; + } + + Vector<NGOffsetMappingUnit> GetUnits(wtf_size_t index1, wtf_size_t index2) { + const auto& units = GetOffsetMapping().GetUnits(); + return {units[index1], units[index2]}; + } + String TestCollapsingWithCSSWhiteSpace(String text, String whitespace) { StringBuilder html; html.Append("<div id=t style=\"white-space:"); @@ -1081,6 +1092,10 @@ TEST_F(NGOffsetMappingTest, Table) { ASSERT_EQ(1u, result.GetRanges().size()); TEST_RANGE(result.GetRanges(), foo_node, 0u, 3u); + + EXPECT_EQ(GetUnits(1, 1), GetFirstLast("|foo")); + EXPECT_EQ(GetUnits(1, 1), GetFirstLast("f|oo")); + EXPECT_EQ(GetUnits(2, 2), GetFirstLast("foo|")); } TEST_F(NGOffsetMappingTest, GetMappingForInlineBlock) { @@ -1134,6 +1149,35 @@ TEST_F(NGOffsetMappingTest, NoWrapSpaceAndCollapsibleSpace) { 1u, 5u, 5u); TEST_UNIT(mapping.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, bar, 1u, 4u, 5u, 8u); + + EXPECT_EQ(GetUnits(0, 0), GetFirstLast("|foo Xbar")); + EXPECT_EQ(GetUnits(0, 0), GetFirstLast("foo| Xbar")); + EXPECT_EQ(GetUnits(0, 0), GetFirstLast("foo |Xbar")); + EXPECT_EQ(GetUnits(2, 2), GetFirstLast("foo X|bar")); +} + +TEST_F(NGOffsetMappingTest, PreLine) { + InsertStyleElement("#t { white-space: pre-line; }"); + SetupHtml("t", "<div id=t>ab \n cd</div>"); + const LayoutObject& text_ab_n_cd = *layout_object_; + const NGOffsetMapping& result = GetOffsetMapping(); + + EXPECT_EQ("ab\ncd", result.GetText()); + + EXPECT_EQ((Vector<NGOffsetMappingUnit>{ + NGOffsetMappingUnit(kIdentity, text_ab_n_cd, 0u, 2u, 0u, 2u), + NGOffsetMappingUnit(kCollapsed, text_ab_n_cd, 2u, 3u, 2u, 2u), + NGOffsetMappingUnit(kIdentity, text_ab_n_cd, 3u, 4u, 2u, 3u), + NGOffsetMappingUnit(kCollapsed, text_ab_n_cd, 4u, 5u, 3u, 3u), + NGOffsetMappingUnit(kIdentity, text_ab_n_cd, 5u, 7u, 3u, 5u)}), + result.GetUnits()); + + EXPECT_EQ(GetUnits(0, 0), GetFirstLast("|ab\ncd")); + EXPECT_EQ(GetUnits(0, 0), GetFirstLast("a|b\ncd")); + EXPECT_EQ(GetUnits(1, 2), GetFirstLast("ab|\ncd")); + EXPECT_EQ(GetUnits(3, 4), GetFirstLast("ab\n|cd")); + EXPECT_EQ(GetUnits(4, 4), GetFirstLast("ab\nc|d")); + EXPECT_EQ(GetUnits(4, 4), GetFirstLast("ab\ncd|")); } TEST_F(NGOffsetMappingTest, BiDiAroundForcedBreakInPreLine) { 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 ac726c4dea4..c61c09c99f9 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 @@ -71,15 +71,21 @@ NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics() const { namespace { // Include the inline-size of the line-box in the overflow. +// Do not update block offset and block size of |overflow|. inline void AddInlineSizeToOverflow(const PhysicalRect& rect, const WritingMode container_writing_mode, PhysicalRect* overflow) { PhysicalRect inline_rect; inline_rect.offset = rect.offset; - if (IsHorizontalWritingMode(container_writing_mode)) + if (IsHorizontalWritingMode(container_writing_mode)) { inline_rect.size.width = rect.size.width; - else + inline_rect.offset.top = overflow->offset.top; + inline_rect.size.height = overflow->size.height; + } else { inline_rect.size.height = rect.size.height; + inline_rect.offset.left = overflow->offset.left; + inline_rect.size.width = overflow->size.width; + } overflow->UniteEvenIfEmpty(inline_rect); } 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 f7993f84dd7..4d256625f13 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 @@ -52,7 +52,7 @@ NGPhysicalTextFragment::NGPhysicalTextFragment( DCHECK_LE(text_offset_.end, source.EndOffset()); DCHECK(shape_result_ || IsFlowControl()) << *this; base_or_resolved_direction_ = source.base_or_resolved_direction_; - ink_overflow_computed_ = false; + ink_overflow_computed_or_mathml_paint_info_ = false; } NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder) @@ -60,12 +60,12 @@ NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder) kFragmentText, static_cast<unsigned>(builder->text_type_)), text_(builder->text_), - text_offset_({builder->start_offset_, builder->end_offset_}), + text_offset_(builder->text_offset_), shape_result_(std::move(builder->shape_result_)) { DCHECK(shape_result_ || IsFlowControl()) << *this; base_or_resolved_direction_ = static_cast<unsigned>(builder->ResolvedDirection()); - ink_overflow_computed_ = false; + ink_overflow_computed_or_mathml_paint_info_ = false; } bool NGPhysicalTextFragment::IsGeneratedText() const { @@ -78,10 +78,8 @@ LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset( unsigned offset, LayoutUnit (*round_function)(float), AdjustMidCluster adjust_mid_cluster) const { - scoped_refptr<NGFragmentItem> item = - base::MakeRefCounted<NGFragmentItem>(*this); - return item->InlinePositionForOffset(Text(), offset, round_function, - adjust_mid_cluster); + return NGFragmentItem(*this).InlinePositionForOffset( + Text(), offset, round_function, adjust_mid_cluster); } // TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to @@ -125,17 +123,14 @@ LayoutUnit NGFragmentItem::InlinePositionForOffset(StringView text, LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset( unsigned offset) const { - scoped_refptr<NGFragmentItem> item = - base::MakeRefCounted<NGFragmentItem>(*this); - return item->InlinePositionForOffset(Text(), offset); + return NGFragmentItem(*this).InlinePositionForOffset(Text(), offset); } std::pair<LayoutUnit, LayoutUnit> NGPhysicalTextFragment::LineLeftAndRightForOffsets(unsigned start_offset, unsigned end_offset) const { - scoped_refptr<NGFragmentItem> item = - base::MakeRefCounted<NGFragmentItem>(*this); - return item->LineLeftAndRightForOffsets(Text(), start_offset, end_offset); + return NGFragmentItem(*this).LineLeftAndRightForOffsets(Text(), start_offset, + end_offset); } // TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to @@ -162,9 +157,7 @@ std::pair<LayoutUnit, LayoutUnit> NGFragmentItem::LineLeftAndRightForOffsets( PhysicalRect NGPhysicalTextFragment::LocalRect(unsigned start_offset, unsigned end_offset) const { - scoped_refptr<NGFragmentItem> item = - base::MakeRefCounted<NGFragmentItem>(*this); - return item->LocalRect(Text(), start_offset, end_offset); + return NGFragmentItem(*this).LocalRect(Text(), start_offset, end_offset); } // TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to @@ -194,7 +187,7 @@ PhysicalRect NGFragmentItem::LocalRect(StringView text, } PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const { - if (!ink_overflow_computed_) + if (!ink_overflow_computed_or_mathml_paint_info_) ComputeSelfInkOverflow(); if (ink_overflow_) return ink_overflow_->self_ink_overflow; @@ -202,7 +195,7 @@ PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const { } void NGPhysicalTextFragment::ComputeSelfInkOverflow() const { - ink_overflow_computed_ = true; + ink_overflow_computed_or_mathml_paint_info_ = true; if (UNLIKELY(!shape_result_)) { ink_overflow_ = nullptr; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.cc new file mode 100644 index 00000000000..dcb51cdf645 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.cc @@ -0,0 +1,305 @@ +// Copyright 2020 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_ruby_utils.h" + +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_logical_line_item.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + +namespace blink { + +// TODO(layout-dev): Using ScrollableOverflow() is same as legacy +// LayoutRubyRun. However its result is not good with some fonts/platforms. +// See crbug.com/1082087. +LayoutUnit LastLineTextLogicalBottom(const NGPhysicalBoxFragment& container, + LayoutUnit default_value) { + const ComputedStyle& container_style = container.Style(); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + if (!container.Items()) + return default_value; + NGInlineCursor cursor(*container.Items()); + cursor.MoveToLastLine(); + const auto* line_item = cursor.CurrentItem(); + if (!line_item) + return default_value; + DCHECK_EQ(line_item->Type(), NGFragmentItem::kLine); + DCHECK(line_item->LineBoxFragment()); + PhysicalRect line_rect = + line_item->LineBoxFragment()->ScrollableOverflowForLine( + container, container_style, *line_item, cursor); + return container.ConvertChildToLogical(line_rect).BlockEndOffset(); + } + + const NGPhysicalLineBoxFragment* last_line = nullptr; + PhysicalOffset last_line_offset; + for (const auto& child_link : container.PostLayoutChildren()) { + if (const auto* maybe_line = + DynamicTo<NGPhysicalLineBoxFragment>(*child_link)) { + last_line = maybe_line; + last_line_offset = child_link.offset; + } + } + if (!last_line) + return default_value; + PhysicalRect line_rect = + last_line->ScrollableOverflow(container, container_style); + line_rect.Move(last_line_offset); + return container.ConvertChildToLogical(line_rect).BlockEndOffset(); +} + +// TODO(layout-dev): Using ScrollableOverflow() is same as legacy +// LayoutRubyRun. However its result is not good with some fonts/platforms. +// See crbug.com/1082087. +LayoutUnit FirstLineTextLogicalTop(const NGPhysicalBoxFragment& container, + LayoutUnit default_value) { + const ComputedStyle& container_style = container.Style(); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + if (!container.Items()) + return default_value; + NGInlineCursor cursor(*container.Items()); + cursor.MoveToFirstLine(); + const auto* line_item = cursor.CurrentItem(); + if (!line_item) + return default_value; + DCHECK_EQ(line_item->Type(), NGFragmentItem::kLine); + DCHECK(line_item->LineBoxFragment()); + PhysicalRect line_rect = + line_item->LineBoxFragment()->ScrollableOverflowForLine( + container, container_style, *line_item, cursor); + return container.ConvertChildToLogical(line_rect).offset.block_offset; + } + + for (const auto& child_link : container.PostLayoutChildren()) { + if (const auto* line = DynamicTo<NGPhysicalLineBoxFragment>(*child_link)) { + PhysicalRect line_rect = + line->ScrollableOverflow(container, container_style); + line_rect.Move(child_link.offset); + return container.ConvertChildToLogical(line_rect).offset.block_offset; + } + } + return default_value; +} + +// See LayoutRubyRun::GetOverhang(). +NGAnnotationOverhang GetOverhang(const NGInlineItemResult& item) { + DCHECK(RuntimeEnabledFeatures::LayoutNGRubyEnabled()); + NGAnnotationOverhang overhang; + if (!item.layout_result) + return overhang; + + const auto& run_fragment = + To<NGPhysicalContainerFragment>(item.layout_result->PhysicalFragment()); + LayoutUnit start_overhang = LayoutUnit::Max(); + LayoutUnit end_overhang = LayoutUnit::Max(); + bool found_line = false; + const ComputedStyle* ruby_text_style = nullptr; + for (const auto& child_link : run_fragment.PostLayoutChildren()) { + const NGPhysicalFragment& child_fragment = *child_link.get(); + const LayoutObject* layout_object = child_fragment.GetLayoutObject(); + if (!layout_object) + continue; + if (layout_object->IsRubyText()) { + ruby_text_style = layout_object->Style(); + continue; + } + if (layout_object->IsRubyBase()) { + const ComputedStyle& base_style = child_fragment.Style(); + const WritingMode writing_mode = base_style.GetWritingMode(); + const LayoutUnit base_inline_size = + NGFragment(writing_mode, child_fragment).InlineSize(); + // RubyBase's inline_size is always same as RubyRun's inline_size. + // Overhang values are offsets from RubyBase's inline edges to + // the outmost text. + for (const auto& base_child_link : + To<NGPhysicalContainerFragment>(child_fragment) + .PostLayoutChildren()) { + const LayoutUnit line_inline_size = + NGFragment(writing_mode, *base_child_link).InlineSize(); + if (line_inline_size == LayoutUnit()) + continue; + found_line = true; + const LayoutUnit start = + base_child_link.offset + .ConvertToLogical(writing_mode, base_style.Direction(), + child_fragment.Size(), + base_child_link.get()->Size()) + .inline_offset; + const LayoutUnit end = base_inline_size - start - line_inline_size; + start_overhang = std::min(start_overhang, start); + end_overhang = std::min(end_overhang, end); + } + } + } + + if (!found_line || !ruby_text_style) + return overhang; + DCHECK_NE(start_overhang, LayoutUnit::Max()); + DCHECK_NE(end_overhang, LayoutUnit::Max()); + // We allow overhang up to the half of ruby text font size. + const LayoutUnit half_width_of_ruby_font = + LayoutUnit(ruby_text_style->FontSize()) / 2; + overhang.start = std::min(start_overhang, half_width_of_ruby_font); + overhang.end = std::min(end_overhang, half_width_of_ruby_font); + return overhang; +} + +// See LayoutRubyRun::GetOverhang(). +bool CanApplyStartOverhang(const NGLineInfo& line_info, + LayoutUnit& start_overhang) { + if (start_overhang <= LayoutUnit()) + return false; + DCHECK(RuntimeEnabledFeatures::LayoutNGRubyEnabled()); + const NGInlineItemResults& items = line_info.Results(); + // Requires at least the current item and the previous item. + if (items.size() < 2) + return false; + // Find a previous item other than kOpenTag/kCloseTag. + // Searching items in the logical order doesn't work well with bidi + // reordering. However, it's difficult to compute overhang after bidi + // reordering because it affects line breaking. + wtf_size_t previous_index = items.size() - 2; + while ((items[previous_index].item->Type() == NGInlineItem::kOpenTag || + items[previous_index].item->Type() == NGInlineItem::kCloseTag) && + previous_index > 0) + --previous_index; + const NGInlineItemResult& previous_item = items[previous_index]; + if (previous_item.item->Type() != NGInlineItem::kText) + return false; + const NGInlineItem& current_item = *items.back().item; + if (previous_item.item->Style()->FontSize() > + current_item.Style()->FontSize()) + return false; + start_overhang = std::min(start_overhang, previous_item.inline_size); + return true; +} + +// See LayoutRubyRun::GetOverhang(). +LayoutUnit CommitPendingEndOverhang(NGLineInfo* line_info) { + DCHECK(RuntimeEnabledFeatures::LayoutNGRubyEnabled()); + DCHECK(line_info); + NGInlineItemResults* items = line_info->MutableResults(); + if (items->size() < 2U) + return LayoutUnit(); + const NGInlineItemResult& text_item = items->back(); + DCHECK_EQ(text_item.item->Type(), NGInlineItem::kText); + wtf_size_t i = items->size() - 2; + while ((*items)[i].item->Type() != NGInlineItem::kAtomicInline) { + const auto type = (*items)[i].item->Type(); + if (type != NGInlineItem::kOpenTag && type != NGInlineItem::kCloseTag) + return LayoutUnit(); + if (i-- == 0) + return LayoutUnit(); + } + NGInlineItemResult& atomic_inline_item = (*items)[i]; + if (!atomic_inline_item.layout_result->PhysicalFragment().IsRubyRun()) + return LayoutUnit(); + if (atomic_inline_item.pending_end_overhang <= LayoutUnit()) + return LayoutUnit(); + if (atomic_inline_item.item->Style()->FontSize() < + text_item.item->Style()->FontSize()) + return LayoutUnit(); + // Ideally we should refer to inline_size of |text_item| instead of the width + // of the NGInlineItem's ShapeResult. However it's impossible to compute + // inline_size of |text_item| before calling BreakText(), and BreakText() + // requires precise |position_| which takes |end_overhang| into account. + LayoutUnit end_overhang = + std::min(atomic_inline_item.pending_end_overhang, + LayoutUnit(text_item.item->TextShapeResult()->Width())); + DCHECK_EQ(atomic_inline_item.margins.inline_end, LayoutUnit()); + atomic_inline_item.margins.inline_end = -end_overhang; + atomic_inline_item.inline_size -= end_overhang; + atomic_inline_item.pending_end_overhang = LayoutUnit(); + return end_overhang; +} + +NGAnnotationMetrics ComputeAnnotationOverflow( + const NGLogicalLineItems& logical_line, + const NGLineHeightMetrics& line_box_metrics, + LayoutUnit line_over, + const ComputedStyle& line_style) { + DCHECK(RuntimeEnabledFeatures::LayoutNGRubyEnabled()); + // Min/max position of content without line-height. + LayoutUnit content_over = line_over + line_box_metrics.ascent; + LayoutUnit content_under = content_over; + + // Min/max position of annotations. + LayoutUnit annotation_over = content_over; + LayoutUnit annotation_under = content_over; + + const LayoutUnit line_under = line_over + line_box_metrics.LineHeight(); + bool has_over_emphasis = false; + bool has_under_emphasis = false; + for (const NGLogicalLineItem& item : logical_line) { + if (item.HasInFlowFragment()) { + if (!item.IsControl()) { + content_over = std::min(content_over, item.BlockOffset()); + content_under = std::max(content_under, item.BlockEndOffset()); + } + if (const auto* style = item.Style()) { + if (style->GetTextEmphasisMark() != TextEmphasisMark::kNone) { + if (style->GetTextEmphasisLineLogicalSide() == LineLogicalSide::kOver) + has_over_emphasis = true; + else + has_under_emphasis = true; + } + } + } + + // Accumulate |AnnotationOverflow| from ruby runs. All ruby run items have + // |layout_result|. + const NGLayoutResult* layout_result = item.layout_result.get(); + if (!layout_result) + continue; + LayoutUnit overflow = layout_result->AnnotationOverflow(); + if (IsFlippedLinesWritingMode(line_style.GetWritingMode())) + overflow = -overflow; + if (overflow < LayoutUnit()) { + annotation_over = + std::min(annotation_over, item.rect.offset.block_offset + overflow); + } else if (overflow > LayoutUnit()) { + const LayoutUnit logical_bottom = + item.rect.offset.block_offset + + layout_result->PhysicalFragment() + .Size() + .ConvertToLogical(line_style.GetWritingMode()) + .block_size; + annotation_under = std::max(annotation_under, logical_bottom + overflow); + } + } + + // Probably this is an empty line. We should secure font-size space. + const LayoutUnit font_size(line_style.ComputedFontSize()); + if (content_under - content_over < font_size) { + LayoutUnit half_leading = (line_box_metrics.LineHeight() - font_size) / 2; + half_leading = half_leading.ClampNegativeToZero(); + content_over = line_over + half_leading; + content_under = line_under - half_leading; + } + + // Don't provide annotation space if text-emphasis exists. + // TODO(layout-dev): If the text-emphasis is in [line_over, line_under], + // this line can provide annotation space. + if (has_over_emphasis) + content_over = line_over; + if (has_under_emphasis) + content_under = line_under; + + const LayoutUnit overflow_over = + (line_over - annotation_over).ClampNegativeToZero(); + const LayoutUnit overflow_under = + (annotation_under - line_under).ClampNegativeToZero(); + return {overflow_over, overflow_under, + // With some fonts, text fragment sizes can exceed line-height. + // We need ClampNegativeToZero(). + overflow_over ? LayoutUnit() + : (content_over - line_over).ClampNegativeToZero(), + overflow_under ? LayoutUnit() + : (line_under - content_under).ClampNegativeToZero()}; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.h new file mode 100644 index 00000000000..9d3afe91ff3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.h @@ -0,0 +1,85 @@ +// Copyright 2020 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_RUBY_UTILS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_RUBY_UTILS_H_ + +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" + +namespace blink { + +class ComputedStyle; +class NGLineInfo; +class NGLogicalLineItems; +class NGPhysicalBoxFragment; +struct NGInlineItemResult; +struct NGLineHeightMetrics; + +// Returns the logical bottom offset of the last line text, relative to +// |container| origin. This is used to decide ruby annotation box position. +// +// See NGBlockLayoutAlgorithm::LayoutRubyText(). +LayoutUnit LastLineTextLogicalBottom(const NGPhysicalBoxFragment& container, + LayoutUnit default_value); + +// Returns the logical top offset of the first line text, relative to +// |container| origin. This is used to decide ruby annotation box position. +// +// See NGBlockLayoutAlgorithm::LayoutRubyText(). +LayoutUnit FirstLineTextLogicalTop(const NGPhysicalBoxFragment& container, + LayoutUnit default_value); + +struct NGAnnotationOverhang { + LayoutUnit start; + LayoutUnit end; +}; + +// Returns overhang values of the specified NGInlineItemResult representing +// LayoutNGRubyRun. +// +// This is used by NGLineBreaker. +NGAnnotationOverhang GetOverhang(const NGInlineItemResult& item); + +// Returns true if |start_overhang| is applied to a previous item, and +// clamp |start_overhang| to the width of the previous item. +// +// This is used by NGLineBreaker. +bool CanApplyStartOverhang(const NGLineInfo& line_info, + LayoutUnit& start_overhang); + +// This should be called after NGInlineItemResult for a text is added in +// NGLineBreaker::HandleText(). +// +// This function may update a NGInlineItemResult representing RubyRun +// in |line_info| +LayoutUnit CommitPendingEndOverhang(NGLineInfo* line_info); + +// Stores ComputeAnnotationOverflow() results. +// +// |overflow_over| and |space_over| are exclusive. Only one of them can be +// non-zero. |overflow_under| and |space_under| are exclusive too. +// All fields never be negative. +struct NGAnnotationMetrics { + // The amount of annotation overflow at the line-over side. + LayoutUnit overflow_over; + // The amount of annotation overflow at the line-under side. + LayoutUnit overflow_under; + // The amount of annotation space which the next line at the line-over + // side can consume. + LayoutUnit space_over; + // The amount of annotation space which the next line at the line-under + // side can consume. + LayoutUnit space_under; +}; + +// Compute over/under annotation overflow/space for the specified line. +NGAnnotationMetrics ComputeAnnotationOverflow( + const NGLogicalLineItems& logical_line, + const NGLineHeightMetrics& line_box_metrics, + LayoutUnit line_over, + const ComputedStyle& line_style); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_RUBY_UTILS_H_ 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 bd872d67343..e6b086e8968 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 @@ -17,51 +17,47 @@ NGTextFragmentBuilder::NGTextFragmentBuilder( const NGPhysicalTextFragment& fragment) : NGFragmentBuilder(fragment), text_(fragment.text_), - start_offset_(fragment.StartOffset()), - end_offset_(fragment.EndOffset()), + text_offset_(fragment.TextOffset()), shape_result_(fragment.TextShapeResult()), text_type_(fragment.TextType()) {} -void NGTextFragmentBuilder::SetItem(const String& text_content, - NGInlineItemResult* item_result, - LayoutUnit line_height) { - DCHECK(item_result); - const NGInlineItem* item = item_result->item; - DCHECK(item); - DCHECK_NE(item->TextType(), NGTextType::kLayoutGenerated) +void NGTextFragmentBuilder::SetItem( + const String& text_content, + const NGInlineItem& item, + scoped_refptr<const ShapeResultView> shape_result, + const NGTextOffset& text_offset, + const LogicalSize& size) { + DCHECK_NE(item.TextType(), NGTextType::kLayoutGenerated) << "Please use SetText() instead."; - DCHECK(item->Style()); + DCHECK(item.Style()); - text_type_ = item->TextType(); + text_type_ = item.TextType(); text_ = text_content; - start_offset_ = item_result->start_offset; - end_offset_ = item_result->end_offset; - resolved_direction_ = item->Direction(); - SetStyle(item->Style(), item->StyleVariant()); - size_ = {item_result->inline_size, line_height}; - shape_result_ = std::move(item_result->shape_result); - layout_object_ = item->GetLayoutObject(); + text_offset_ = text_offset; + resolved_direction_ = item.Direction(); + SetStyle(item.Style(), item.StyleVariant()); + size_ = size; + shape_result_ = std::move(shape_result); + layout_object_ = item.GetLayoutObject(); } void NGTextFragmentBuilder::SetText( LayoutObject* layout_object, const String& text, scoped_refptr<const ComputedStyle> style, - bool is_ellipsis_style, - scoped_refptr<const ShapeResultView> shape_result) { + NGStyleVariant style_variant, + scoped_refptr<const ShapeResultView> shape_result, + const LogicalSize& size) { DCHECK(layout_object); DCHECK(style); DCHECK(shape_result); text_type_ = NGTextType::kLayoutGenerated; text_ = text; - start_offset_ = shape_result->StartIndex(); - end_offset_ = shape_result->EndIndex(); + text_offset_ = {shape_result->StartIndex(), shape_result->EndIndex()}; resolved_direction_ = shape_result->Direction(); - SetStyle(style, is_ellipsis_style ? NGStyleVariant::kEllipsis - : NGStyleVariant::kStandard); - size_ = {shape_result->SnappedWidth(), - NGLineHeightMetrics(*style).LineHeight()}; + SetStyle(style, style_variant); + size_ = size; shape_result_ = std::move(shape_result); layout_object_ = layout_object; } 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 1d028144f27..7e90f264e2f 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 @@ -15,14 +15,13 @@ namespace blink { class LayoutObject; class ShapeResultView; -struct NGInlineItemResult; class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder { STACK_ALLOCATED(); public: - NGTextFragmentBuilder(WritingMode writing_mode) - : NGFragmentBuilder(writing_mode, TextDirection::kLtr) {} + explicit NGTextFragmentBuilder(WritingMode writing_mode) + : NGFragmentBuilder({writing_mode, TextDirection::kLtr}) {} NGTextFragmentBuilder(const NGPhysicalTextFragment& fragment); @@ -30,23 +29,25 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder { // NOTE: Takes ownership of the shape result within the item result. void SetItem(const String& text_content, - NGInlineItemResult*, - LayoutUnit line_height); + const NGInlineItem& inline_item, + scoped_refptr<const ShapeResultView> shape_result, + const NGTextOffset& text_offset, + const LogicalSize& size); // Set text for generated text, e.g. hyphen and ellipsis. void SetText(LayoutObject*, const String& text, scoped_refptr<const ComputedStyle>, - bool is_ellipsis_style, - scoped_refptr<const ShapeResultView>); + NGStyleVariant style_variant, + scoped_refptr<const ShapeResultView>, + const LogicalSize& size); // Creates the fragment. Can only be called once. scoped_refptr<const NGPhysicalTextFragment> ToTextFragment(); private: String text_; - unsigned start_offset_; - unsigned end_offset_; + NGTextOffset text_offset_; scoped_refptr<const ShapeResultView> shape_result_; NGTextType text_type_ = NGTextType::kNormal; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h index 0d6c7251095..8e196f9850f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h @@ -5,7 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_OFFSET_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_OFFSET_H_ -#include "base/logging.h" +#include "base/check_op.h" #include "third_party/blink/renderer/core/core_export.h" namespace blink { @@ -23,6 +23,7 @@ struct CORE_EXPORT NGTextOffset { } void AssertValid() const { DCHECK_GE(end, start); } + void AssertNotEmpty() const { DCHECK_GT(end, start); } unsigned start; unsigned end; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc index a7904987197..2d0f8c53b64 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc @@ -7,6 +7,7 @@ #include <memory> #include <utility> +#include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/editing/position_with_affinity.h" #include "third_party/blink/renderer/core/layout/hit_test_location.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" @@ -252,6 +253,33 @@ bool LayoutNGBlockFlowMixin<Base>::NodeAtPoint( accumulated_offset, action); } +// Move specified position to start/end of non-editable region. +// If it can be found, we prefer a visually equivalent position that is +// editable. +// See also LayoutObject::CreatePositionWithAffinity() +// Example: +// <editable><non-editable>|abc</non-editable></editable> +// => +// <editable>|<non-editable>abc</non-editable></editable> +static PositionWithAffinity AdjustForEditingBoundary( + const PositionWithAffinity& position_with_affinity) { + if (position_with_affinity.IsNull()) + return position_with_affinity; + const Position& position = position_with_affinity.GetPosition(); + const Node& node = *position.ComputeContainerNode(); + if (HasEditableStyle(node)) + return position_with_affinity; + const Position& forward = + MostForwardCaretPosition(position, kCanCrossEditingBoundary); + if (HasEditableStyle(*forward.ComputeContainerNode())) + return PositionWithAffinity(forward); + const Position& backward = + MostBackwardCaretPosition(position, kCanCrossEditingBoundary); + if (HasEditableStyle(*backward.ComputeContainerNode())) + return PositionWithAffinity(backward); + return position_with_affinity; +} + template <typename Base> PositionWithAffinity LayoutNGBlockFlowMixin<Base>::PositionForPoint( const PhysicalOffset& point) const { @@ -272,7 +300,7 @@ PositionWithAffinity LayoutNGBlockFlowMixin<Base>::PositionForPoint( Base::OffsetForContents(point_in_contents); if (const PositionWithAffinity position = paint_fragment->PositionForPoint(point_in_contents)) - return position; + return AdjustForEditingBoundary(position); } else if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { if (const NGFragmentItems* items = fragment->Items()) { // The given offset is relative to this |LayoutBlockFlow|. Convert to the @@ -283,7 +311,7 @@ PositionWithAffinity LayoutNGBlockFlowMixin<Base>::PositionForPoint( if (const PositionWithAffinity position = cursor.PositionForPointInInlineFormattingContext( point_in_contents, *fragment)) - return position; + return AdjustForEditingBoundary(position); } } 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 1c82b8a6480..32822417c99 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 @@ -92,7 +92,8 @@ MinMaxSizes LayoutNGMixin<Base>::ComputeIntrinsicLogicalWidths() const { NGConstraintSpace space = ConstraintSpaceForMinMaxSizes(); MinMaxSizes sizes = node.ComputeMinMaxSizes(node.Style().GetWritingMode(), - MinMaxSizesInput(available_logical_height), + MinMaxSizesInput(available_logical_height, + MinMaxSizesType::kContent), &space) .sizes; @@ -154,8 +155,7 @@ void LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout() { NGBlockNode container_node(container); NGBoxFragmentBuilder container_builder( container_node, scoped_refptr<const ComputedStyle>(container_style), - /* space */ nullptr, container_style->GetWritingMode(), - container_style->Direction()); + /* space */ nullptr, container_style->GetWritingDirection()); container_builder.SetIsNewFormattingContext( container_node.CreatesNewFormattingContext()); 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 index 5702a72422c..8d1e30248e4 100644 --- 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 @@ -7,7 +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/list/list_marker.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" namespace blink { 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 c3a6d3f0b9f..c45686e298d 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 @@ -4,7 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" -#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h deleted file mode 100644 index 86bdb5ec592..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h +++ /dev/null @@ -1,31 +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 THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_IMAGE_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_IMAGE_H_ - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/layout_image.h" - -namespace blink { - -class Document; - -class CORE_EXPORT LayoutNGListMarkerImage final : public LayoutImage { - public: - explicit LayoutNGListMarkerImage(Element*); - static LayoutNGListMarkerImage* CreateAnonymous(Document*); - - bool IsLayoutNGObject() const override { return true; } - - private: - bool IsOfType(LayoutObjectType) const override; - - void ComputeIntrinsicSizingInfoByDefaultSize(IntrinsicSizingInfo&) const; - void ComputeIntrinsicSizingInfo(IntrinsicSizingInfo&) const final; -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_IMAGE_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h index 3b30f46349b..fa7cb43175a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h @@ -7,8 +7,8 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/list_marker.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h" -#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" namespace blink { 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 e0e08726a1f..491b22f50ba 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 @@ -4,7 +4,6 @@ #include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h" -#include "third_party/blink/renderer/core/layout/layout_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" @@ -36,7 +35,7 @@ bool NGUnpositionedListMarker::IsImage() const { LayoutUnit NGUnpositionedListMarker::InlineOffset( const LayoutUnit marker_inline_size) const { DCHECK(marker_layout_object_); - auto margins = LayoutListMarker::InlineMarginsForOutside( + auto margins = ListMarker::InlineMarginsForOutside( marker_layout_object_->StyleRef(), IsImage(), marker_inline_size); return margins.first; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc index 831821243aa..01c72e4fa4c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc @@ -124,10 +124,7 @@ FractionStackParameters GetFractionStackParameters(const ComputedStyle& style) { NGMathFractionLayoutAlgorithm::NGMathFractionLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - border_scrollbar_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding + - params.fragment_geometry.scrollbar) { + : NGLayoutAlgorithm(params) { DCHECK(params.space.IsNewFormattingContext()); container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); @@ -142,8 +139,7 @@ void NGMathFractionLayoutAlgorithm::GatherChildren(NGBlockNode* numerator, NGBlockNode block_child = To<NGBlockNode>(child); if (child.IsOutOfFlowPositioned()) { container_builder_.AddOutOfFlowChildCandidate( - block_child, {border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start}); + block_child, BorderScrollbarPadding().StartOffset()); continue; } if (!*numerator) { @@ -169,17 +165,14 @@ scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() { NGBlockNode denominator = nullptr; GatherChildren(&numerator, &denominator); - const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - auto child_available_size = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); auto numerator_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size, ConstraintSpace(), numerator); + Node(), ChildAvailableSize(), ConstraintSpace(), numerator); scoped_refptr<const NGLayoutResult> numerator_layout_result = numerator.Layout(numerator_space); auto numerator_margins = ComputeMarginsFor(numerator_space, numerator.Style(), ConstraintSpace()); auto denominator_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size, ConstraintSpace(), denominator); + Node(), ChildAvailableSize(), ConstraintSpace(), denominator); scoped_refptr<const NGLayoutResult> denominator_layout_result = denominator.Layout(denominator_space); auto denominator_margins = ComputeMarginsFor( @@ -238,8 +231,8 @@ scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() { LayoutUnit fraction_descent = std::max(-numerator_shift + numerator_descent, denominator_shift + denominator_descent); - fraction_ascent += border_scrollbar_padding_.block_start; - fraction_descent += border_scrollbar_padding_.block_end; + fraction_ascent += BorderScrollbarPadding().block_start; + fraction_descent += BorderScrollbarPadding().block_end; LayoutUnit total_block_size = fraction_ascent + fraction_descent; container_builder_.SetBaseline(fraction_ascent); @@ -247,14 +240,13 @@ scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() { LogicalOffset numerator_offset; LogicalOffset denominator_offset; numerator_offset.inline_offset = - border_scrollbar_padding_.inline_start + numerator_margins.inline_start + - (child_available_size.inline_size - + BorderScrollbarPadding().inline_start + numerator_margins.inline_start + + (ChildAvailableSize().inline_size - (numerator_fragment.InlineSize() + numerator_margins.InlineSum())) / 2; denominator_offset.inline_offset = - border_scrollbar_padding_.inline_start + - denominator_margins.inline_start + - (child_available_size.inline_size - + BorderScrollbarPadding().inline_start + denominator_margins.inline_start + + (ChildAvailableSize().inline_size - (denominator_fragment.InlineSize() + denominator_margins.InlineSum())) / 2; @@ -274,11 +266,11 @@ scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() { denominator.StoreMargins(ConstraintSpace(), denominator_margins); LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_scrollbar_padding_, total_block_size, - border_box_size.inline_size); + ConstraintSpace(), Style(), BorderPadding(), total_block_size, + container_builder_.InitialBorderBoxSize().inline_size); container_builder_.SetIntrinsicBlockSize(total_block_size); - container_builder_.SetBlockSize(block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), container_builder_.Borders(), &container_builder_) @@ -290,7 +282,7 @@ scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() { MinMaxSizesResult NGMathFractionLayoutAlgorithm::ComputeMinMaxSizes( const MinMaxSizesInput& child_input) const { if (auto result = CalculateMinMaxSizesIgnoringChildren( - Node(), border_scrollbar_padding_)) + Node(), BorderScrollbarPadding())) return *result; MinMaxSizes sizes; @@ -310,7 +302,7 @@ MinMaxSizesResult NGMathFractionLayoutAlgorithm::ComputeMinMaxSizes( child_result.depends_on_percentage_block_size; } - sizes += border_scrollbar_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, depends_on_percentage_block_size}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h index 83640d5826a..05b8761c6f6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h @@ -22,7 +22,6 @@ class CORE_EXPORT NGMathFractionLayoutAlgorithm MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const final; void GatherChildren(NGBlockNode* numerator, NGBlockNode* denominator); - const NGBoxStrut border_scrollbar_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc index a4f4ef38039..851c08fc2fc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc @@ -29,11 +29,7 @@ inline LayoutUnit InlineOffsetForDisplayMathCentering( NGMathRowLayoutAlgorithm::NGMathRowLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding), - border_scrollbar_padding_(border_padding_ + - params.fragment_geometry.scrollbar) { + : NGLayoutAlgorithm(params) { DCHECK(params.space.IsNewFormattingContext()); DCHECK(!ConstraintSpace().HasBlockFragmentation()); container_builder_.SetIsNewFormattingContext( @@ -42,7 +38,7 @@ NGMathRowLayoutAlgorithm::NGMathRowLayoutAlgorithm( } void NGMathRowLayoutAlgorithm::LayoutRowItems( - NGContainerFragmentBuilder::ChildrenVector* children, + ChildrenVector* children, LayoutUnit* max_row_block_baseline, LogicalSize* row_total_size) { LayoutUnit inline_offset, max_row_ascent, max_row_descent; @@ -53,13 +49,12 @@ void NGMathRowLayoutAlgorithm::LayoutRowItems( // absolutely positioned". // Issue: https://github.com/mathml-refresh/mathml/issues/16 container_builder_.AddOutOfFlowChildCandidate( - To<NGBlockNode>(child), {border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start}); + To<NGBlockNode>(child), BorderScrollbarPadding().StartOffset()); continue; } const ComputedStyle& child_style = child.Style(); NGConstraintSpace child_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size_, ConstraintSpace(), child); + Node(), ChildAvailableSize(), ConstraintSpace(), child); scoped_refptr<const NGLayoutResult> result = To<NGBlockNode>(child).Layout(child_space, nullptr /* break token */); const NGPhysicalContainerFragment& physical_fragment = @@ -79,8 +74,9 @@ void NGMathRowLayoutAlgorithm::LayoutRowItems( // TODO(rbuis): Operators can add lspace and rspace. children->emplace_back( + To<NGBlockNode>(child), margins, LogicalOffset{inline_offset, margins.block_start - ascent}, - &physical_fragment); + std::move(&physical_fragment)); inline_offset += fragment.InlineSize() + margins.inline_end; @@ -103,10 +99,8 @@ scoped_refptr<const NGLayoutResult> NGMathRowLayoutAlgorithm::Layout() { LayoutUnit max_row_block_baseline; const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - child_available_size_ = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - NGContainerFragmentBuilder::ChildrenVector children; + ChildrenVector children; LayoutRowItems(&children, &max_row_block_baseline, &max_row_size); // Add children taking into account centering, baseline and @@ -114,23 +108,24 @@ scoped_refptr<const NGLayoutResult> NGMathRowLayoutAlgorithm::Layout() { LayoutUnit center_offset = InlineOffsetForDisplayMathCentering( is_display_math, container_builder_.InlineSize(), max_row_size.inline_size); - LogicalOffset adjust_offset( - border_scrollbar_padding_.inline_start + center_offset, - border_scrollbar_padding_.block_start + max_row_block_baseline); - for (auto& child : children) { - child.offset += adjust_offset; + + LogicalOffset adjust_offset = BorderScrollbarPadding().StartOffset(); + adjust_offset += LogicalOffset{center_offset, max_row_block_baseline}; + for (auto& child_data : children) { + child_data.offset += adjust_offset; container_builder_.AddChild( - To<NGPhysicalContainerFragment>(*child.fragment), child.offset); + To<NGPhysicalContainerFragment>(*child_data.fragment), + child_data.offset); + child_data.child.StoreMargins(ConstraintSpace(), child_data.margins); } - container_builder_.SetBaseline(border_scrollbar_padding_.block_start + - max_row_block_baseline); + container_builder_.SetBaseline(adjust_offset.block_offset); auto block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, - max_row_size.block_size + border_scrollbar_padding_.BlockSum(), + ConstraintSpace(), Style(), BorderPadding(), + max_row_size.block_size + BorderScrollbarPadding().BlockSum(), border_box_size.inline_size); - container_builder_.SetBlockSize(block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); NGOutOfFlowLayoutPart( Node(), ConstraintSpace(), @@ -144,7 +139,7 @@ scoped_refptr<const NGLayoutResult> NGMathRowLayoutAlgorithm::Layout() { MinMaxSizesResult NGMathRowLayoutAlgorithm::ComputeMinMaxSizes( const MinMaxSizesInput& child_input) const { if (auto result = CalculateMinMaxSizesIgnoringChildren( - Node(), border_scrollbar_padding_)) + Node(), BorderScrollbarPadding())) return *result; MinMaxSizes sizes; @@ -171,7 +166,7 @@ MinMaxSizesResult NGMathRowLayoutAlgorithm::ComputeMinMaxSizes( sizes.Encompass(LayoutUnit()); DCHECK_LE(sizes.min_size, sizes.max_size); - sizes += border_scrollbar_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, depends_on_percentage_block_size}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h index 1463e6256f6..478fc8167c0 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h @@ -19,21 +19,34 @@ class CORE_EXPORT NGMathRowLayoutAlgorithm NGBoxFragmentBuilder, NGBlockBreakToken> { public: - NGMathRowLayoutAlgorithm(const NGLayoutAlgorithmParams& params); - - protected: - void LayoutRowItems(NGContainerFragmentBuilder::ChildrenVector*, - LayoutUnit* max_row_block_baseline, - LogicalSize* row_total_size); + explicit NGMathRowLayoutAlgorithm(const NGLayoutAlgorithmParams& params); + + struct ChildWithOffsetAndMargins { + DISALLOW_NEW(); + ChildWithOffsetAndMargins(const NGBlockNode& child, + const NGBoxStrut& margins, + LogicalOffset offset, + scoped_refptr<const NGPhysicalFragment> fragment) + : child(child), + margins(margins), + offset(offset), + fragment(std::move(fragment)) {} + + NGBlockNode child; + NGBoxStrut margins; + LogicalOffset offset; + scoped_refptr<const NGPhysicalFragment> fragment; + }; + typedef Vector<ChildWithOffsetAndMargins, 4> ChildrenVector; private: scoped_refptr<const NGLayoutResult> Layout() final; MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const final; - LogicalSize child_available_size_; - const NGBoxStrut border_padding_; - const NGBoxStrut border_scrollbar_padding_; + void LayoutRowItems(ChildrenVector*, + LayoutUnit* max_row_block_baseline, + LogicalSize* row_total_size); }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.cc index afdfca8c5b8..31b2a7a4f77 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.cc @@ -79,16 +79,11 @@ ScriptsVerticalParameters GetScriptsVerticalParameters( NGMathScriptsLayoutAlgorithm::NGMathScriptsLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - border_scrollbar_padding_(params.fragment_geometry.border + - params.fragment_geometry.scrollbar + - params.fragment_geometry.padding) { + : NGLayoutAlgorithm(params) { DCHECK(params.space.IsNewFormattingContext()); container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); - child_available_size_ = ShrinkAvailableSize( - container_builder_.InitialBorderBoxSize(), border_scrollbar_padding_); } void NGMathScriptsLayoutAlgorithm::GatherChildren( @@ -103,8 +98,7 @@ void NGMathScriptsLayoutAlgorithm::GatherChildren( if (child.IsOutOfFlowPositioned()) { if (container_builder) { container_builder->AddOutOfFlowChildCandidate( - block_child, {border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start}); + block_child, BorderScrollbarPadding().StartOffset()); } continue; } @@ -232,7 +226,7 @@ NGMathScriptsLayoutAlgorithm::ChildAndMetrics NGMathScriptsLayoutAlgorithm::LayoutAndGetMetrics(NGBlockNode child) const { ChildAndMetrics child_and_metrics; auto constraint_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size_, ConstraintSpace(), child); + Node(), ChildAvailableSize(), ConstraintSpace(), child); child_and_metrics.result = child.Layout(constraint_space, nullptr /*break_token*/); NGBoxFragment fragment( @@ -265,14 +259,17 @@ scoped_refptr<const NGLayoutResult> NGMathScriptsLayoutAlgorithm::Layout() { VerticalMetrics metrics = GetVerticalMetrics(base_metrics, sub_metrics, sup_metrics); + const LogicalOffset content_start_offset = + BorderScrollbarPadding().StartOffset(); + LayoutUnit ascent = std::max(base_metrics.ascent, metrics.ascent + metrics.sup_shift) + - border_scrollbar_padding_.block_start; + content_start_offset.block_offset; LayoutUnit descent = std::max(base_metrics.descent, metrics.descent + metrics.sub_shift); // TODO(rbuis): take into account italic correction. - LayoutUnit inline_offset = border_scrollbar_padding_.inline_start + - base_metrics.margins.inline_start; + LayoutUnit inline_offset = + content_start_offset.inline_offset + base_metrics.margins.inline_start; LogicalOffset base_offset( inline_offset, @@ -302,15 +299,14 @@ scoped_refptr<const NGLayoutResult> NGMathScriptsLayoutAlgorithm::Layout() { container_builder_.SetBaseline(ascent); LayoutUnit intrinsic_block_size = - ascent + descent + border_scrollbar_padding_.block_end; + ascent + descent + BorderScrollbarPadding().block_end; LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_scrollbar_padding_, - intrinsic_block_size, + ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size, container_builder_.InitialBorderBoxSize().inline_size); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); - container_builder_.SetBlockSize(block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), container_builder_.Borders(), &container_builder_) @@ -322,7 +318,7 @@ scoped_refptr<const NGLayoutResult> NGMathScriptsLayoutAlgorithm::Layout() { MinMaxSizesResult NGMathScriptsLayoutAlgorithm::ComputeMinMaxSizes( const MinMaxSizesInput& child_input) const { if (auto result = CalculateMinMaxSizesIgnoringChildren( - Node(), border_scrollbar_padding_)) + Node(), BorderScrollbarPadding())) return *result; NGBlockNode base = nullptr; @@ -384,7 +380,7 @@ MinMaxSizesResult NGMathScriptsLayoutAlgorithm::ComputeMinMaxSizes( NOTREACHED(); break; } - sizes += border_scrollbar_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, depends_on_percentage_block_size}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.h index 8d8f34ea40c..ce65ecd1df9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.h @@ -57,9 +57,6 @@ class CORE_EXPORT NGMathScriptsLayoutAlgorithm const ChildAndMetrics& sup_metrics) const; scoped_refptr<const NGLayoutResult> Layout() final; - - LogicalSize child_available_size_; - const NGBoxStrut border_scrollbar_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc index 6dad93301a8..51be5e7fb8b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc @@ -11,9 +11,7 @@ namespace blink { NGMathSpaceLayoutAlgorithm::NGMathSpaceLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding) { + : NGLayoutAlgorithm(params) { DCHECK(params.fragment_geometry.scrollbar.IsEmpty()); container_builder_.SetIsNewFormattingContext(true); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); @@ -22,27 +20,28 @@ NGMathSpaceLayoutAlgorithm::NGMathSpaceLayoutAlgorithm( scoped_refptr<const NGLayoutResult> NGMathSpaceLayoutAlgorithm::Layout() { DCHECK(!BreakToken()); + LayoutUnit intrinsic_block_size = BorderScrollbarPadding().BlockSum(); LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, border_padding_.BlockSum(), + ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size, container_builder_.InitialBorderBoxSize().inline_size); - container_builder_.SetIntrinsicBlockSize(border_padding_.BlockSum()); - container_builder_.SetBlockSize(block_size); + container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); container_builder_.SetBaseline( - border_padding_.block_start + + BorderScrollbarPadding().block_start + ValueForLength(Style().GetMathBaseline(), LayoutUnit())); return container_builder_.ToBoxFragment(); } MinMaxSizesResult NGMathSpaceLayoutAlgorithm::ComputeMinMaxSizes( const MinMaxSizesInput&) const { - if (auto result = - CalculateMinMaxSizesIgnoringChildren(Node(), border_padding_)) + if (auto result = CalculateMinMaxSizesIgnoringChildren( + Node(), BorderScrollbarPadding())) return *result; MinMaxSizes sizes; - sizes += border_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, /* depends_on_percentage_block_size */ false}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h index 838d058f895..5306bf446cf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h @@ -21,8 +21,6 @@ class CORE_EXPORT NGMathSpaceLayoutAlgorithm scoped_refptr<const NGLayoutResult> Layout() final; MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const final; - - const NGBoxStrut border_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.cc index 1b419ddb83e..872196de21a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.cc @@ -75,10 +75,7 @@ UnderOverVerticalParameters GetUnderOverVerticalParameters( NGMathUnderOverLayoutAlgorithm::NGMathUnderOverLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - border_scrollbar_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding + - params.fragment_geometry.scrollbar) { + : NGLayoutAlgorithm(params) { DCHECK(params.space.IsNewFormattingContext()); container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); @@ -94,8 +91,7 @@ void NGMathUnderOverLayoutAlgorithm::GatherChildren(NGBlockNode* base, NGBlockNode block_child = To<NGBlockNode>(child); if (child.IsOutOfFlowPositioned()) { container_builder_.AddOutOfFlowChildCandidate( - block_child, {border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start}); + block_child, BorderScrollbarPadding().StartOffset()); continue; } if (!*base) { @@ -135,10 +131,11 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { GatherChildren(&base, &over, &under); const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - auto child_available_size = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - LayoutUnit block_offset = border_scrollbar_padding_.block_start; + const LogicalOffset content_start_offset = + BorderScrollbarPadding().StartOffset(); + + LayoutUnit block_offset = content_start_offset.block_offset; UnderOverVerticalParameters parameters = GetUnderOverVerticalParameters(Style()); // TODO(rbuis): handle stretchy operators. @@ -148,7 +145,7 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { // therefore centered relative to themselves). if (over) { auto over_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size, ConstraintSpace(), over); + Node(), ChildAvailableSize(), ConstraintSpace(), over); scoped_refptr<const NGLayoutResult> over_layout_result = over.Layout(over_space); NGBoxStrut over_margins = @@ -158,8 +155,8 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { To<NGPhysicalBoxFragment>(over_layout_result->PhysicalFragment())); block_offset += parameters.over_extra_ascender + over_margins.block_start; LogicalOffset over_offset = { - border_scrollbar_padding_.inline_start + over_margins.inline_start + - (child_available_size.inline_size - + content_start_offset.inline_offset + over_margins.inline_start + + (ChildAvailableSize().inline_size - (over_fragment.InlineSize() + over_margins.InlineSum())) / 2, block_offset}; @@ -180,7 +177,7 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { } auto base_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size, ConstraintSpace(), base); + Node(), ChildAvailableSize(), ConstraintSpace(), base); auto base_layout_result = base.Layout(base_space); auto base_margins = ComputeMarginsFor(base_space, base.Style(), ConstraintSpace()); @@ -191,8 +188,8 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { block_offset += base_margins.block_start; LogicalOffset base_offset = { - border_scrollbar_padding_.inline_start + base_margins.inline_start + - (child_available_size.inline_size - + content_start_offset.inline_offset + base_margins.inline_start + + (ChildAvailableSize().inline_size - (base_fragment.InlineSize() + base_margins.InlineSum())) / 2, block_offset}; @@ -203,7 +200,7 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { if (under) { auto under_space = CreateConstraintSpaceForMathChild( - Node(), child_available_size, ConstraintSpace(), under); + Node(), ChildAvailableSize(), ConstraintSpace(), under); scoped_refptr<const NGLayoutResult> under_layout_result = under.Layout(under_space); NGBoxStrut under_margins = @@ -221,8 +218,8 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { parameters.under_shift_min - under_ascent); } LogicalOffset under_offset = { - border_scrollbar_padding_.inline_start + under_margins.inline_start + - (child_available_size.inline_size - + content_start_offset.inline_offset + under_margins.inline_start + + (ChildAvailableSize().inline_size - (under_fragment.InlineSize() + under_margins.InlineSum())) / 2, block_offset}; @@ -238,14 +235,14 @@ scoped_refptr<const NGLayoutResult> NGMathUnderOverLayoutAlgorithm::Layout() { base_fragment.Baseline().value_or(base_fragment.BlockSize()); container_builder_.SetBaseline(base_offset.block_offset + base_ascent); - block_offset += border_scrollbar_padding_.block_end; + block_offset += BorderScrollbarPadding().block_end; - LayoutUnit block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_scrollbar_padding_, block_offset, - border_box_size.inline_size); + LayoutUnit block_size = + ComputeBlockSizeForFragment(ConstraintSpace(), Style(), BorderPadding(), + block_offset, border_box_size.inline_size); container_builder_.SetIntrinsicBlockSize(block_offset); - container_builder_.SetBlockSize(block_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), container_builder_.Borders(), &container_builder_) @@ -259,7 +256,7 @@ MinMaxSizesResult NGMathUnderOverLayoutAlgorithm::ComputeMinMaxSizes( DCHECK(IsValidMathMLScript(Node())); if (auto result = CalculateMinMaxSizesIgnoringChildren( - Node(), border_scrollbar_padding_)) + Node(), BorderScrollbarPadding())) return *result; MinMaxSizes sizes; @@ -279,7 +276,7 @@ MinMaxSizesResult NGMathUnderOverLayoutAlgorithm::ComputeMinMaxSizes( child_result.depends_on_percentage_block_size; } - sizes += border_scrollbar_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, depends_on_percentage_block_size}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.h index a324e1b6032..c5d7d931943 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.h @@ -26,8 +26,6 @@ class CORE_EXPORT NGMathUnderOverLayoutAlgorithm void GatherChildren(NGBlockNode* base, NGBlockNode* second, NGBlockNode* third); - - const NGBoxStrut border_scrollbar_padding_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_mathml_paint_info.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_mathml_paint_info.h new file mode 100644 index 00000000000..157a3f09651 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_mathml_paint_info.h @@ -0,0 +1,28 @@ +// Copyright 2020 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_MATHML_NG_MATHML_PAINT_INFO_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATHML_PAINT_INFO_H_ + +#include <unicode/uchar.h> +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" + +namespace blink { + +class ShapeResultView; + +struct CORE_EXPORT NGMathMLPaintInfo { + USING_FAST_MALLOC(NGMathMLPaintInfo); + + public: + scoped_refptr<const ShapeResultView> operator_shape_result_view; + LayoutUnit operator_inline_size; + LayoutUnit operator_ascent; + LayoutUnit operator_descent; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATHML_PAINT_INFO_H_ 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 fd2180c4c50..f8acd6fe3ed 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 @@ -369,15 +369,21 @@ base::Optional<LayoutUnit> ComputeAbsoluteDialogYPosition( return base::nullopt; } - auto* scrollable_area = dialog.GetDocument().View()->LayoutViewport(); + auto& document = dialog.GetDocument(); + auto* scrollable_area = document.View()->LayoutViewport(); LayoutUnit top = LayoutUnit((dialog.Style()->GetPosition() == EPosition::kFixed) ? 0 : scrollable_area->ScrollOffsetInt().Height()); - int visible_height = dialog.GetDocument().View()->Height(); + if (top) + UseCounter::Count(document, WebFeature::kDialogWithNonZeroScrollOffset); + + int visible_height = document.View()->Height(); if (height < visible_height) top += (visible_height - height) / 2; + else if (height > visible_height) + UseCounter::Count(document, WebFeature::kDialogHeightLargerThanViewport); dialog_node->SetCentered(top); return top; } 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 eea8efbb3da..dc210bf78e6 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 @@ -27,13 +27,15 @@ NGBlockBreakToken::NGBlockBreakToken( unsigned sequence_number, const NGBreakTokenVector& child_break_tokens, NGBreakAppeal break_appeal, - bool has_seen_all_children) + bool has_seen_all_children, + bool is_at_block_end) : NGBreakToken(kBlockBreakToken, kUnfinished, node), consumed_block_size_(consumed_block_size), sequence_number_(sequence_number), num_children_(child_break_tokens.size()) { break_appeal_ = break_appeal; has_seen_all_children_ = has_seen_all_children; + is_at_block_end_ = is_at_block_end; for (wtf_size_t i = 0; i < child_break_tokens.size(); ++i) { child_break_tokens_[i] = child_break_tokens[i].get(); child_break_tokens_[i]->AddRef(); 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 ee2bcc92fba..917d753b120 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 @@ -30,7 +30,8 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { unsigned sequence_number, const NGBreakTokenVector& child_break_tokens, NGBreakAppeal break_appeal, - bool has_seen_all_children) { + bool has_seen_all_children, + bool is_at_block_end) { // We store the children list inline in the break token 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. @@ -38,9 +39,10 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { sizeof(NGBlockBreakToken) + child_break_tokens.size() * sizeof(NGBreakToken*), ::WTF::GetStringWithTypeName<NGBlockBreakToken>()); - new (data) NGBlockBreakToken(PassKey(), node, consumed_block_size, - sequence_number, child_break_tokens, - break_appeal, has_seen_all_children); + new (data) + NGBlockBreakToken(PassKey(), node, consumed_block_size, sequence_number, + child_break_tokens, break_appeal, + has_seen_all_children, is_at_block_end); return base::AdoptRef(static_cast<NGBlockBreakToken*>(data)); } @@ -95,6 +97,30 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { // have one (since all children are either finished, or have a break token). bool HasSeenAllChildren() const { return has_seen_all_children_; } + // Return true if layout was past the block-end border edge of the node when + // it fragmented. This typically means that something is overflowing the node, + // and that establishes a parallel flow [1]. Subsequent content may be put + // into the same fragmentainer as a fragment whose break token is in this + // state, as long as it fits. + // + // [1] https://www.w3.org/TR/css-break-3/#parallel-flows + // + // <div style="columns:2; column-fill:auto; height:100px;"> + // <div id="a" style="height:100px;"> + // <div id="inner" style="height:200px;"></div> + // </div> + // <div id="b" style="margin-top:-30px; height:30px;"></div> + // </div> + // + // #a and #b will be in the first column, while #inner will be in both the + // first and second one. The important detail here is that we're at the end of + // #a exactly at the bottom of the first column - even if #a broke inside + // because of #child. This means that we have no space left as such, but we're + // not ready to proceed to the next column. Anything that can fit at the + // bottom of a column (either because it actually has 0 height, or e.g. a + // negative top margin) will be put into that column, not the next. + bool IsAtBlockEnd() const { return is_at_block_end_; } + // The break tokens for children of the layout node. // // Each child we have visited previously in the block-flow layout algorithm @@ -125,7 +151,8 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { unsigned sequence_number, const NGBreakTokenVector& child_break_tokens, NGBreakAppeal break_appeal, - bool has_seen_all_children); + bool has_seen_all_children, + bool is_at_block_end); explicit NGBlockBreakToken(PassKey, NGLayoutInputNode node); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h index 3b7636ad530..d75eb17c8d5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h @@ -43,9 +43,6 @@ class CORE_EXPORT NGBlockChildIterator { Entry NextChild( const NGInlineBreakToken* previous_inline_break_token = nullptr); - // Return true if there are no more children to process. - bool IsAtEnd() const { return !child_; } - private: NGLayoutInputNode child_; const NGBlockBreakToken* break_token_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc index 01b46282762..91a0e741387 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc @@ -59,19 +59,19 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { NGBreakTokenVector empty_tokens_list; scoped_refptr<NGBreakToken> child_token1 = NGBlockBreakToken::Create( node1, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); scoped_refptr<NGBreakToken> child_token2 = NGBlockBreakToken::Create( node2, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); scoped_refptr<NGBreakToken> child_token3 = NGBlockBreakToken::Create( node3, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); NGBreakTokenVector child_break_tokens; child_break_tokens.push_back(child_token1); scoped_refptr<NGBlockBreakToken> parent_token = NGBlockBreakToken::Create( container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); NGBlockChildIterator iterator(node1, parent_token.get()); ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()), @@ -87,7 +87,7 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { child_break_tokens.push_back(child_token2); parent_token = NGBlockBreakToken::Create( container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); iterator = NGBlockChildIterator(node1, parent_token.get()); ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()), @@ -104,7 +104,7 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { child_break_tokens.push_back(child_token3); parent_token = NGBlockBreakToken::Create( container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); iterator = NGBlockChildIterator(node1, parent_token.get()); ASSERT_EQ(NGBlockChildIterator::Entry(node2, child_token2.get()), @@ -120,7 +120,7 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { child_break_tokens.push_back(child_token3); parent_token = NGBlockBreakToken::Create( container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); iterator = NGBlockChildIterator(node1, parent_token.get()); ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()), @@ -146,13 +146,13 @@ TEST_F(NGBlockChildIteratorTest, SeenAllChildren) { NGBreakTokenVector empty_tokens_list; scoped_refptr<NGBreakToken> child_token1 = NGBlockBreakToken::Create( node1, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, - /* has_seen_all_children */ false); + /* has_seen_all_children */ false, /* is_at_block_end */ false); NGBreakTokenVector child_break_tokens; child_break_tokens.push_back(child_token1); scoped_refptr<NGBlockBreakToken> parent_token = NGBlockBreakToken::Create( container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, - /* has_seen_all_children */ true); + /* has_seen_all_children */ true, /* is_at_block_end */ false); // We have a break token for #child1, but have seen all children. This happens // e.g. when #child1 has overflow into a new fragmentainer, while #child2 was @@ -167,7 +167,7 @@ TEST_F(NGBlockChildIteratorTest, SeenAllChildren) { child_break_tokens.clear(); parent_token = NGBlockBreakToken::Create( container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, - /* has_seen_all_children */ true); + /* has_seen_all_children */ true, /* is_at_block_end */ false); // We have no break tokens, but have seen all children. This happens e.g. when // we have a large container with fixed block-size, with empty space at the 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 ab6b62882b8..d2a1a60c73d 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 @@ -14,6 +14,8 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.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_ruby_utils.h" +#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.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" @@ -38,96 +40,24 @@ namespace blink { namespace { -// Returns the logical bottom offset of the last line text, relative to -// |container| origin. This is used to decide ruby annotation box position. -// -// TODO(layout-dev): Using ScrollableOverflow() is same as legacy -// LayoutRubyRun. However its result is not good with some fonts/platforms. -LayoutUnit LastLineTextLogicalBottom(const NGPhysicalBoxFragment& container, - LayoutUnit default_value) { - const ComputedStyle& container_style = container.Style(); - if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - if (!container.Items()) - return default_value; - NGInlineCursor cursor(*container.Items()); - cursor.MoveToLastLine(); - const auto* line_item = cursor.CurrentItem(); - if (!line_item) - return default_value; - DCHECK_EQ(line_item->Type(), NGFragmentItem::kLine); - DCHECK(line_item->LineBoxFragment()); - PhysicalRect line_rect = - line_item->LineBoxFragment()->ScrollableOverflowForLine( - container, container_style, *line_item, cursor); - return line_rect - .ConvertToLogical(container_style.GetWritingMode(), - container_style.Direction(), container.Size(), - cursor.Current().Size()) - .BlockEndOffset(); - } - - const NGPhysicalLineBoxFragment* last_line = nullptr; - PhysicalOffset last_line_offset; - for (const auto& child_link : container.PostLayoutChildren()) { - if (const auto* maybe_line = - DynamicTo<NGPhysicalLineBoxFragment>(*child_link)) { - last_line = maybe_line; - last_line_offset = child_link.offset; - } - } - if (!last_line) - return default_value; - PhysicalRect line_rect = - last_line->ScrollableOverflow(container, container_style); - line_rect.Move(last_line_offset); - return line_rect - .ConvertToLogical(container_style.GetWritingMode(), - container_style.Direction(), container.Size(), - last_line->Size()) - .BlockEndOffset(); -} - -// Returns the logical top offset of the first line text, relative to -// |container| origin. This is used to decide ruby annotation box position. -// -// TODO(layout-dev): Using ScrollableOverflow() is same as legacy -// LayoutRubyRun. However its result is not good with some fonts/platforms. -LayoutUnit FirstLineTextLogicalTop(const NGPhysicalBoxFragment& container, - LayoutUnit default_value) { - const ComputedStyle& container_style = container.Style(); - if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - if (!container.Items()) - return default_value; - NGInlineCursor cursor(*container.Items()); - cursor.MoveToFirstLine(); - const auto* line_item = cursor.CurrentItem(); - if (!line_item) - return default_value; - DCHECK_EQ(line_item->Type(), NGFragmentItem::kLine); - DCHECK(line_item->LineBoxFragment()); - PhysicalRect line_rect = - line_item->LineBoxFragment()->ScrollableOverflowForLine( - container, container_style, *line_item, cursor); - return line_rect - .ConvertToLogical(container_style.GetWritingMode(), - container_style.Direction(), container.Size(), - cursor.Current().Size()) - .offset.block_offset; - } - - for (const auto& child_link : container.PostLayoutChildren()) { - if (const auto* line = DynamicTo<NGPhysicalLineBoxFragment>(*child_link)) { - PhysicalRect line_rect = - line->ScrollableOverflow(container, container_style); - line_rect.Move(child_link.offset); - return line_rect - .ConvertToLogical(container_style.GetWritingMode(), - container_style.Direction(), container.Size(), - line->Size()) - .offset.block_offset; - } - } - return default_value; +bool HasLineEvenIfEmpty(LayoutBox* box) { + LayoutBlockFlow* const block_flow = DynamicTo<LayoutBlockFlow>(box); + if (!block_flow) + return false; + // Note: |block_flow->NeedsCollectInline()| is true after removing all + // children from block[1]. + // [1] editing/inserting/insert_after_delete.html + LayoutObject* const child = GetLayoutObjectForFirstChildNode(block_flow); + if (!child) { + // Note: |block_flow->ChildrenInline()| can be both true or false: + // - true: just after construction, <div></div> + // - true: one of child is inline them remove all, <div>abc</div> + // - false: all children are block then remove all, <div><p></p></div> + return block_flow->HasLineIfEmpty(); + } + if (!AreNGBlockFlowChildrenInline(block_flow)) + return false; + return NGInlineNode(block_flow).HasLineEvenIfEmpty(); } inline scoped_refptr<const NGLayoutResult> LayoutBlockChild( @@ -268,16 +198,11 @@ inline bool IsEarlyBreakpoint(const NGEarlyBreak& breakpoint, NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm( const NGLayoutAlgorithmParams& params) : NGLayoutAlgorithm(params), - border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding), - border_scrollbar_padding_(border_padding_ + - params.fragment_geometry.scrollbar), previous_result_(params.previous_result), is_resuming_(IsResumingLayout(params.break_token)), exclusion_space_(params.space.ExclusionSpace()), lines_until_clamp_(params.space.LinesUntilClamp()), early_break_(params.early_break) { - AdjustForFragmentation(BreakToken(), &border_scrollbar_padding_); container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); @@ -293,8 +218,8 @@ void NGBlockLayoutAlgorithm::SetBoxType(NGPhysicalFragment::NGBoxType type) { MinMaxSizesResult NGBlockLayoutAlgorithm::ComputeMinMaxSizes( const MinMaxSizesInput& input) const { - if (auto result = CalculateMinMaxSizesIgnoringChildren( - node_, border_scrollbar_padding_)) + if (auto result = + CalculateMinMaxSizesIgnoringChildren(node_, BorderScrollbarPadding())) return *result; MinMaxSizes sizes; @@ -336,7 +261,8 @@ MinMaxSizesResult NGBlockLayoutAlgorithm::ComputeMinMaxSizes( float_right_inline_size = LayoutUnit(); } - MinMaxSizesInput child_input(input.percentage_resolution_block_size); + MinMaxSizesInput child_input(input.percentage_resolution_block_size, + input.type); if (child.IsInline() || child.IsAnonymousBlock()) { child_input.float_left_inline_size = float_left_inline_size; child_input.float_right_inline_size = float_right_inline_size; @@ -434,7 +360,7 @@ MinMaxSizesResult NGBlockLayoutAlgorithm::ComputeMinMaxSizes( DCHECK_GE(sizes.min_size, LayoutUnit()); DCHECK_LE(sizes.min_size, sizes.max_size) << Node().ToString(); - sizes += border_scrollbar_padding_.InlineSum(); + sizes += BorderScrollbarPadding().InlineSum(); return {sizes, depends_on_percentage_block_size}; } @@ -499,7 +425,8 @@ NOINLINE scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::LayoutWithItemsBuilder( const NGInlineNode& first_child, NGInlineChildLayoutContext* context) { - NGFragmentItemsBuilder items_builder(first_child); + NGFragmentItemsBuilder items_builder( + first_child, container_builder_.GetWritingDirection()); container_builder_.SetItemsBuilder(&items_builder); context->SetItemsBuilder(&items_builder); scoped_refptr<const NGLayoutResult> result = Layout(context); @@ -542,22 +469,14 @@ NGBlockLayoutAlgorithm::RelayoutIgnoringLineClamp() { inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( NGInlineChildLayoutContext* inline_child_layout_context) { - const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - child_available_size_ = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - child_percentage_size_ = CalculateChildPercentageSize( - ConstraintSpace(), Node(), child_available_size_); + ConstraintSpace(), Node(), ChildAvailableSize()); replaced_child_percentage_size_ = CalculateReplacedChildPercentageSize( - ConstraintSpace(), Node(), child_available_size_, - border_scrollbar_padding_, border_padding_); + ConstraintSpace(), Node(), ChildAvailableSize(), BorderScrollbarPadding(), + BorderPadding()); - // All of the above calculations with border_scrollbar_padding_ shouldn't - // include the table cell's intrinsic padding. We can now add this. - if (ConstraintSpace().IsTableCell()) { - border_scrollbar_padding_ += ComputeIntrinsicPadding( - ConstraintSpace(), Style(), container_builder_.Scrollbar()); - } + container_builder_.AdjustBorderScrollbarPaddingForFragmentation(BreakToken()); + container_builder_.AdjustBorderScrollbarPaddingForTableCell(); DCHECK_EQ(!!inline_child_layout_context, Node().IsInlineFormattingContextRoot()); @@ -581,14 +500,19 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( } if (RuntimeEnabledFeatures::BlockFlowHandlesWebkitLineClampEnabled() && - Style().IsDeprecatedWebkitBoxWithVerticalLineClamp() && - !ignore_line_clamp_) - lines_until_clamp_ = Style().LineClamp(); + Style().IsDeprecatedWebkitBoxWithVerticalLineClamp()) { + if (!ignore_line_clamp_) + lines_until_clamp_ = Style().LineClamp(); + } else if (Style().HasLineClamp()) { + UseCounter::Count(Node().GetDocument(), + WebFeature::kWebkitLineClampWithoutWebkitBox); + } - LayoutUnit content_edge = border_scrollbar_padding_.block_start; + LayoutUnit content_edge = BorderScrollbarPadding().block_start; NGPreviousInflowPosition previous_inflow_position = { LayoutUnit(), ConstraintSpace().MarginStrut(), + is_resuming_ ? LayoutUnit() : container_builder_.Padding().block_start, /* self_collapsing_child_had_clearance */ false}; // Do not collapse margins between parent and its child if: @@ -601,11 +525,10 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // D: We're forced to stop margin collapsing by a CSS property // // In all those cases we can and must resolve the BFC block offset now. - if (border_scrollbar_padding_.block_start || is_resuming_ || + if (content_edge || is_resuming_ || ConstraintSpace().IsNewFormattingContext()) { bool discard_subsequent_margins = - previous_inflow_position.margin_strut.discard_margins && - !border_scrollbar_padding_.block_start; + previous_inflow_position.margin_strut.discard_margins && !content_edge; if (!ResolveBfcBlockOffset(&previous_inflow_position)) { // There should be no preceding content that depends on the BFC block // offset of a new formatting context block, and likewise when resuming @@ -724,7 +647,7 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( } container_builder_.AddBreakBeforeChild(child, kBreakAppealPerfect, /* is_forced_break */ false); - SetFragmentainerOutOfSpace(&previous_inflow_position); + ConsumeRemainingFragmentainerSpace(&previous_inflow_position); break; } @@ -751,10 +674,13 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( return container_builder_.Abort(status); } if (ConstraintSpace().HasBlockFragmentation()) { - if (container_builder_.DidBreak() && - IsFragmentainerOutOfSpace( - previous_inflow_position.logical_block_offset)) + // A child break in a parallel flow doesn't affect whether we should + // break here or not. + if (container_builder_.HasInflowChildBreakInside()) { + // But if the break happened in the same flow, we'll now just finish + // layout of the fragment. No more siblings should be processed. break; + } // We need to propagate the initial break-before value up our container // chain, until we reach a container that's not a first child. If we get @@ -783,7 +709,7 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( NGLayoutResult::kNeedsRelayoutWithNoForcedTruncateAtLineClamp); } - if (child_iterator.IsAtEnd()) { + if (!child_iterator.NextChild(previous_inline_break_token.get()).node) { // We've gone through all the children. This doesn't necessarily mean that // we're done fragmenting, as there may be parallel flows [1] (visible // overflow) still needing more space than what the current fragmentainer @@ -800,6 +726,37 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // offset, as that could give us a negative content box size. intrinsic_block_size_ = content_edge; + // Add line height for empty content editable or button with empty label, e.g. + // <div contenteditable></div>, <input type="button" value=""> + if (container_builder_.HasSeenAllChildren() && + HasLineEvenIfEmpty(Node().GetLayoutBox())) { + intrinsic_block_size_ += + std::max(intrinsic_block_size_, + Node().GetLayoutBox()->LogicalHeightForEmptyLine()); + // Test [1][2] require baseline offset for empty editable. + // [1] css3/flexbox/baseline-for-empty-line.html + // [2] inline-block/contenteditable-baseline.html + const LayoutBlock* const layout_block = + To<LayoutBlock>(Node().GetLayoutBox()); + if (auto baseline_offset = layout_block->BaselineForEmptyLine( + layout_block->IsHorizontalWritingMode() ? kHorizontalLine + : kVerticalLine)) + container_builder_.SetBaseline(*baseline_offset); + } + + // Collapse annotation overflow and padding. + // logical_block_offset already contains block-end annotation overflow. + // However, if the container has non-zero block-end padding, the annotation + // can extend on the padding. So we decrease logical_block_offset by + // shareable part of the annotation overflow and the padding. + if (previous_inflow_position.block_end_annotation_space < LayoutUnit()) { + DCHECK(RuntimeEnabledFeatures::LayoutNGRubyEnabled()); + const LayoutUnit annotation_overflow = + -previous_inflow_position.block_end_annotation_space; + previous_inflow_position.logical_block_offset -= + std::min(container_builder_.Padding().block_end, annotation_overflow); + } + // To save space of the stack when we recurse into children, the rest of this // function is continued within |FinishLayout|. However it should be read as // one function. @@ -822,9 +779,10 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( // intrinsic block-size at the time of the clamp. if (intrinsic_block_size_when_clamped_) { DCHECK(container_builder_.BfcBlockOffset()); - intrinsic_block_size_ = *intrinsic_block_size_when_clamped_; + intrinsic_block_size_ = *intrinsic_block_size_when_clamped_ + + BorderScrollbarPadding().block_end; end_margin_strut = NGMarginStrut(); - } else if (border_scrollbar_padding_.block_end || + } else if (BorderScrollbarPadding().block_end || previous_inflow_position->self_collapsing_child_had_clearance || ConstraintSpace().IsNewFormattingContext()) { // The end margin strut of an in-flow fragment contributes to the size of @@ -833,16 +791,7 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( // - There was a self-collapsing child affected by clearance. // - We are a new formatting context. // Additionally this fragment produces no end margin strut. - // - // If we are a quirky container, we ignore any quirky margins and - // just consider normal margins to extend our size. Other UAs - // perform this calculation differently, e.g. by just ignoring the - // *last* quirky margin. - // TODO: revisit previous implementation to avoid changing behavior and - // https://html.spec.whatwg.org/C/#margin-collapsing-quirks - LayoutUnit margin_strut_sum = node_.IsQuirkyContainer() - ? end_margin_strut.QuirkyContainerSum() - : end_margin_strut.Sum(); + if (!container_builder_.BfcBlockOffset()) { // If we have collapsed through the block start and all children (if any), // now is the time to determine the BFC block offset, because finally we @@ -857,6 +806,22 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( } DCHECK(container_builder_.BfcBlockOffset()); } else { + // If we are a quirky container, we ignore any quirky margins and just + // consider normal margins to extend our size. Other UAs perform this + // calculation differently, e.g. by just ignoring the *last* quirky + // margin. + LayoutUnit margin_strut_sum = node_.IsQuirkyContainer() + ? end_margin_strut.QuirkyContainerSum() + : end_margin_strut.Sum(); + + if (ConstraintSpace().HasKnownFragmentainerBlockSize()) { + LayoutUnit bfc_block_offset = + *container_builder_.BfcBlockOffset() + + previous_inflow_position->logical_block_offset; + margin_strut_sum = AdjustedMarginAfterFinalChildFragment( + ConstraintSpace(), bfc_block_offset, margin_strut_sum); + } + // The trailing margin strut will be part of our intrinsic block size, but // only if there is something that separates the end margin strut from the // input margin strut (typically child content, block start @@ -870,7 +835,7 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( previous_inflow_position->logical_block_offset + margin_strut_sum); } - intrinsic_block_size_ += border_scrollbar_padding_.block_end; + intrinsic_block_size_ += BorderScrollbarPadding().block_end; end_margin_strut = NGMarginStrut(); } else { // Update our intrinsic block size to be just past the block-end border edge @@ -884,15 +849,20 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( container_builder_.SetOverflowBlockSize(intrinsic_block_size_); intrinsic_block_size_ = ClampIntrinsicBlockSize( - ConstraintSpace(), Node(), border_scrollbar_padding_, + ConstraintSpace(), Node(), BorderScrollbarPadding(), intrinsic_block_size_, CalculateQuirkyBodyMarginBlockSum(end_margin_strut)); + LayoutUnit previously_consumed_block_size; + if (UNLIKELY(BreakToken())) + previously_consumed_block_size = BreakToken()->ConsumedBlockSize(); + // 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(), Style(), BorderPadding(), + previously_consumed_block_size + intrinsic_block_size_, border_box_size.inline_size); - container_builder_.SetBlockSize(border_box_size.block_size); + container_builder_.SetFragmentsTotalBlockSize(border_box_size.block_size); // If our BFC block-offset is still unknown, we check: // - If we have a non-zero block-size (margins don't collapse through us). @@ -1005,14 +975,22 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( To<NGPhysicalBoxFragment>(previous_result_->PhysicalFragment()); const NGFragmentItems* previous_items = previous_fragment.Items(); DCHECK(previous_items); + + // Find reusable lines. Fail if no items are reusable. previous_items->DirtyLinesFromNeedsLayout(inline_node.GetLayoutBlockFlow()); + const NGFragmentItem* end_item = previous_items->EndOfReusableItems(); + DCHECK(end_item); + if (!end_item || end_item == &previous_items->front()) + return false; const auto& children = container_builder_.Children(); const wtf_size_t children_before = children.size(); + NGFragmentItemsBuilder* items_builder = container_builder_.ItemsBuilder(); const NGConstraintSpace& space = ConstraintSpace(); - const auto result = container_builder_.ItemsBuilder()->AddPreviousItems( - *previous_items, space.GetWritingMode(), space.Direction(), - previous_fragment.Size(), &container_builder_, /* stop_at_dirty */ true); + DCHECK_EQ(items_builder->GetWritingMode(), space.GetWritingMode()); + DCHECK_EQ(items_builder->Direction(), space.Direction()); + const auto result = items_builder->AddPreviousItems( + *previous_items, previous_fragment.Size(), &container_builder_, end_item); if (UNLIKELY(!result.succeeded)) { DCHECK_EQ(children.size(), children_before); @@ -1038,7 +1016,7 @@ void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned( const NGPreviousInflowPosition& previous_inflow_position, NGBlockNode child) { DCHECK(child.IsOutOfFlowPositioned()); - LogicalOffset static_offset = {border_scrollbar_padding_.inline_start, + LogicalOffset static_offset = {BorderScrollbarPadding().inline_start, previous_inflow_position.logical_block_offset}; // We only include the margin strut in the OOF static-position if we know we @@ -1064,12 +1042,12 @@ void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned( NGBfcOffset origin_bfc_offset = { ConstraintSpace().BfcOffset().line_offset + - border_scrollbar_padding_.LineLeft(Style().Direction()), + BorderScrollbarPadding().LineLeft(Style().Direction()), origin_bfc_block_offset}; static_offset.inline_offset += CalculateOutOfFlowStaticInlineLevelOffset( Style(), origin_bfc_offset, exclusion_space_, - child_available_size_.inline_size); + ChildAvailableSize().inline_size); } container_builder_.AddOutOfFlowChildCandidate(child, static_offset); @@ -1083,27 +1061,17 @@ void NGBlockLayoutAlgorithm::HandleFloat( DCHECK(!IsResumingLayout(child_break_token) || container_builder_.BfcBlockOffset()); - if (broke_before_float_) { - // We have already broken before a float. This means that we cannot place - // any more floats now, as a float isn't allowed to start before any - // preceding float. - DCHECK(!child_break_token); - container_builder_.AddBreakBeforeChild(child, base::nullopt, - /* is_forced_break */ false); - return; - } - // If we don't have a BFC block-offset yet, the "expected" BFC block-offset // is used to optimistically place floats. NGBfcOffset origin_bfc_offset = { ConstraintSpace().BfcOffset().line_offset + - border_scrollbar_padding_.LineLeft(ConstraintSpace().Direction()), + BorderScrollbarPadding().LineLeft(ConstraintSpace().Direction()), container_builder_.BfcBlockOffset() ? NextBorderEdge(previous_inflow_position) : ConstraintSpace().ExpectedBfcBlockOffset()}; NGUnpositionedFloat unpositioned_float( - child, child_break_token, child_available_size_, child_percentage_size_, + child, child_break_token, ChildAvailableSize(), child_percentage_size_, replaced_child_percentage_size_, origin_bfc_offset, ConstraintSpace(), Style()); @@ -1127,39 +1095,27 @@ void NGBlockLayoutAlgorithm::HandleFloat( // TODO(mstensho): Handle abortions caused by block fragmentation. DCHECK_EQ(layout_result.Status(), NGLayoutResult::kSuccess); - const auto& physical_fragment = layout_result.PhysicalFragment(); - if (const NGBreakToken* token = physical_fragment.BreakToken()) { + if (positioned_float.need_break_before) { DCHECK(ConstraintSpace().HasBlockFragmentation()); - if (!child_break_token && token->BreakAppeal() != kBreakAppealPerfect) { - LayoutUnit fragmentainer_block_offset = - ConstraintSpace().FragmentainerOffsetAtBfc() + - positioned_float.bfc_offset.block_offset; - if (fragmentainer_block_offset > LayoutUnit()) { - // The float broke inside, and not at an ideal breakpoint. Break before - // the float instead. Note that we don't check if we're at a valid class - // A or C breakpoint (we only check that we're not at the start of the - // fragmentainer (in which case breaking typically wouldn't eliminate - // the unappealing break inside the float)). While no other browsers do - // this either, we should consider doing this in the future. For now, - // don't let the float affect the appeal of breaking inside this - // container. - BreakBeforeChild(ConstraintSpace(), child, layout_result, - fragmentainer_block_offset, - /* appeal */ base::nullopt, - /* is_forced_break */ false, &container_builder_); - - // Then carry on with layout of this container. The float constitutes a - // parallel flow, and there may be siblings that could still fit in the - // current fragmentainer. - broke_before_float_ = true; - return; - } - } + LayoutUnit fragmentainer_block_offset = + ConstraintSpace().FragmentainerOffsetAtBfc() + + positioned_float.bfc_offset.block_offset; + BreakBeforeChild(ConstraintSpace(), child, *positioned_float.layout_result, + fragmentainer_block_offset, + /* appeal */ base::nullopt, + /* is_forced_break */ false, &container_builder_); + + // After breaking before the float, carry on with layout of this + // container. The float constitutes a parallel flow, and there may be + // siblings that could still fit in the current fragmentainer. + return; } // TODO(mstensho): There should be a class A breakpoint between a float and // another float, and also between a float and an in-flow block. + const NGPhysicalFragment& physical_fragment = + positioned_float.layout_result->PhysicalFragment(); LayoutUnit float_inline_size = NGFragment(ConstraintSpace().GetWritingMode(), physical_fragment) .InlineSize(); @@ -1193,7 +1149,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleNewFormattingContext( LayoutUnit child_origin_line_offset = ConstraintSpace().BfcOffset().line_offset + - border_scrollbar_padding_.LineLeft(direction); + BorderScrollbarPadding().LineLeft(direction); // If the child has a block-start margin, and the BFC block offset is still // unresolved, and we have preceding adjoining floats, things get complicated @@ -1379,7 +1335,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleNewFormattingContext( // The margins we store will be used by e.g. getComputedStyle(). // When calculating these values, ignore any floats that might have // affected the child. This is what Edge does. - ResolveInlineMargins(child_style, Style(), child_available_size_.inline_size, + ResolveInlineMargins(child_style, Style(), ChildAvailableSize().inline_size, fragment.InlineSize(), &child_data.margins); To<NGBlockNode>(child).StoreMargins(ConstraintSpace(), child_data.margins); @@ -1411,8 +1367,8 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( DCHECK(container_builder_.BfcBlockOffset()); LayoutOpportunityVector opportunities = - exclusion_space_.AllLayoutOpportunities( - origin_offset, child_available_size_.inline_size); + exclusion_space_.AllLayoutOpportunities(origin_offset, + ChildAvailableSize().inline_size); // We should always have at least one opportunity. DCHECK_GT(opportunities.size(), 0u); @@ -1444,14 +1400,14 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( bool can_expand_outside_opportunity = opportunity.rect.start_offset.line_offset == origin_offset.line_offset && - opportunity.rect.InlineSize() == child_available_size_.inline_size; + opportunity.rect.InlineSize() == ChildAvailableSize().inline_size; if (can_expand_outside_opportunity) { // No floats have affected the available inline-size, adjust the // available inline-size by the margins. DCHECK_EQ(line_left_offset, origin_offset.line_offset); DCHECK_EQ(line_right_offset, - origin_offset.line_offset + child_available_size_.inline_size); + origin_offset.line_offset + ChildAvailableSize().inline_size); line_left_offset += line_left_margin; line_right_offset -= line_right_margin; } else { @@ -1463,7 +1419,7 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( origin_offset.line_offset + line_left_margin.ClampNegativeToZero()); line_right_offset = std::min(line_right_offset, origin_offset.line_offset + - child_available_size_.inline_size - + ChildAvailableSize().inline_size - line_right_margin.ClampNegativeToZero()); } LayoutUnit opportunity_size = @@ -1480,7 +1436,7 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( NGConstraintSpace child_space = CreateConstraintSpaceForChild( child, child_data, - {child_available_inline_size, child_available_size_.block_size}, + {child_available_inline_size, ChildAvailableSize().block_size}, /* is_new_fc */ true, opportunity.rect.start_offset.block_offset); // All formatting context roots (like this child) should start with an empty @@ -1616,8 +1572,9 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleInflow( 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); + child, child_data, ChildAvailableSize(), /* is_new_fc */ false, + forced_bfc_block_offset, has_clearance_past_adjoining_floats, + previous_inflow_position->block_end_annotation_space); scoped_refptr<const NGLayoutResult> layout_result = LayoutInflow(child_space, child_break_token, early_break_, &child, inline_child_layout_context); @@ -1795,7 +1752,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( self_collapsing_child_needs_relayout) && child_bfc_block_offset) { NGConstraintSpace new_child_space = CreateConstraintSpaceForChild( - child, *child_data, child_available_size_, /* is_new_fc */ false, + child, *child_data, ChildAvailableSize(), /* is_new_fc */ false, child_bfc_block_offset); layout_result = LayoutInflow(new_child_space, child_break_token, early_break_, &child, @@ -1810,7 +1767,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( child_bfc_block_offset = layout_result->BfcBlockOffset(); DCHECK(child_bfc_block_offset); new_child_space = CreateConstraintSpaceForChild( - child, *child_data, child_available_size_, /* is_new_fc */ false, + child, *child_data, ChildAvailableSize(), /* is_new_fc */ false, child_bfc_block_offset); layout_result = LayoutInflow(new_child_space, child_break_token, early_break_, &child, @@ -1892,7 +1849,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( // required by e.g. getComputedStyle()). if (!child_data->margins_fully_resolved) { ResolveInlineMargins(child.Style(), Style(), - child_available_size_.inline_size, + ChildAvailableSize().inline_size, fragment.InlineSize(), &child_data->margins); child_data->margins_fully_resolved = true; } @@ -1914,7 +1871,10 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( if (UNLIKELY(ConstraintSpace().IsInColumnBfc())) { if (NGBlockNode spanner_node = layout_result->ColumnSpanner()) { container_builder_.SetColumnSpanner(spanner_node); - if (!container_builder_.DidBreak()) { + // TODO(mstensho): DidBreakSelf() is always false here, so this check is + // wrong. Still, no failing tests! Please investigate. + // HasInflowChildBreakInside() ought to be a better choice. + if (!container_builder_.DidBreakSelf()) { // If we still haven't found a descendant at which to resume column // layout after the spanner, look for one now. if (NGLayoutInputNode next = child.NextSibling()) { @@ -1939,8 +1899,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( // If line-clamping occurred save the intrinsic block-size, as this // becomes the final intrinsic block-size. intrinsic_block_size_when_clamped_ = - previous_inflow_position->logical_block_offset + - border_scrollbar_padding_.block_end; + previous_inflow_position->logical_block_offset; } } return NGLayoutResult::kSuccess; @@ -1986,7 +1945,7 @@ NGInflowChildData NGBlockLayoutAlgorithm::ComputeChildData( NGBfcOffset child_bfc_offset = { ConstraintSpace().BfcOffset().line_offset + - border_scrollbar_padding_.LineLeft(ConstraintSpace().Direction()) + + BorderScrollbarPadding().LineLeft(ConstraintSpace().Direction()) + margins.LineLeft(ConstraintSpace().Direction()), BfcBlockOffset() + logical_block_offset}; @@ -2065,7 +2024,15 @@ NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( if (!container_builder_.BfcBlockOffset()) DCHECK_EQ(logical_block_offset, LayoutUnit()); } else { - logical_block_offset = logical_offset.block_offset + fragment.BlockSize(); + // We add AnnotationOverflow unconditionally here. Then, we cancel it if + // - The next line box has block-start annotation space, or + // - There are no following child boxes and this container has block-end + // padding. + // + // See NGInlineLayoutAlgorithm::CreateLine() and + // BlockLayoutAlgorithm::Layout(). + logical_block_offset = logical_offset.block_offset + fragment.BlockSize() + + layout_result.AnnotationOverflow(); } NGMarginStrut margin_strut = layout_result.EndMarginStrut(); @@ -2095,7 +2062,13 @@ NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( (previous_inflow_position.self_collapsing_child_had_clearance && is_self_collapsing); - return {logical_block_offset, margin_strut, + LayoutUnit annotation_space = layout_result.BlockEndAnnotationSpace(); + if (layout_result.AnnotationOverflow() > LayoutUnit()) { + DCHECK(!annotation_space); + annotation_space = -layout_result.AnnotationOverflow(); + } + + return {logical_block_offset, margin_strut, annotation_space, self_or_sibling_self_collapsing_child_had_clearance}; } @@ -2123,21 +2096,8 @@ LayoutUnit NGBlockLayoutAlgorithm::FragmentainerSpaceAvailable() const { *container_builder_.BfcBlockOffset(); } -bool NGBlockLayoutAlgorithm::IsFragmentainerOutOfSpace( - LayoutUnit block_offset) const { - if (did_break_before_child_) - return true; - if (!ConstraintSpace().HasKnownFragmentainerBlockSize()) - return false; - if (!container_builder_.BfcBlockOffset().has_value()) - return false; - return block_offset >= FragmentainerSpaceAvailable(); -} - -void NGBlockLayoutAlgorithm::SetFragmentainerOutOfSpace( +void NGBlockLayoutAlgorithm::ConsumeRemainingFragmentainerSpace( NGPreviousInflowPosition* previous_inflow_position) { - did_break_before_child_ = true; - if (ConstraintSpace().HasKnownFragmentainerBlockSize()) { // The remaining part of the fragmentainer (the unusable space for child // content, due to the break) should still be occupied by this container. @@ -2148,7 +2108,8 @@ void NGBlockLayoutAlgorithm::SetFragmentainerOutOfSpace( bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() { if (Node().IsInlineFormattingContextRoot() && !early_break_) { - if (container_builder_.DidBreak() || first_overflowing_line_) { + if (container_builder_.HasInflowChildBreakInside() || + first_overflowing_line_) { if (first_overflowing_line_ && first_overflowing_line_ < container_builder_.LineCount()) { int line_number; @@ -2176,31 +2137,22 @@ bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() { } } - if (!ConstraintSpace().HasKnownFragmentainerBlockSize()) + if (container_builder_.IsFragmentainerBoxType()) { + // We're building fragmentainers. Just copy the block-size from the + // constraint space. Calculating the size the regular way would cause some + // problems with overflow. For one, we don't want to produce a break token + // if there's no child content that requires it. + LayoutUnit consumed_block_size = + BreakToken() ? BreakToken()->ConsumedBlockSize() : LayoutUnit(); + LayoutUnit block_size = ConstraintSpace().FragmentainerBlockSize(); + container_builder_.SetFragmentBlockSize(block_size); + container_builder_.SetConsumedBlockSize(consumed_block_size + block_size); return true; + } - LayoutUnit consumed_block_size = - BreakToken() ? BreakToken()->ConsumedBlockSize() : LayoutUnit(); - LayoutUnit space_left = FragmentainerSpaceAvailable(); - LayoutUnit block_size; - if (container_builder_.BoxType() == NGPhysicalFragment::kColumnBox && - ConstraintSpace().HasKnownFragmentainerBlockSize()) { - // We're building column fragments, and we know the column size. Just use - // that. Calculating the size the regular way would cause some problems with - // overflow. For one, we don't want to produce a break token if there's no - // child content that requires it. - block_size = ConstraintSpace().FragmentainerBlockSize(); - } else { - block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, - consumed_block_size + intrinsic_block_size_, - container_builder_.InitialBorderBoxSize().inline_size); - - block_size -= consumed_block_size; - DCHECK_GE(block_size, LayoutUnit()) - << "Adding and subtracting the consumed_block_size shouldn't leave the " - "block_size for this fragment smaller than zero."; - + LayoutUnit space_left = kIndefiniteSize; + if (ConstraintSpace().HasKnownFragmentainerBlockSize()) { + space_left = FragmentainerSpaceAvailable(); if (space_left <= LayoutUnit()) { // The amount of space available may be zero, or even negative, if the // border-start edge of this block starts exactly at, or even after the @@ -2216,8 +2168,8 @@ bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() { } } - FinishFragmentation(ConstraintSpace(), BreakToken(), block_size, - intrinsic_block_size_, space_left, &container_builder_); + FinishFragmentation(Node(), ConstraintSpace(), BreakToken(), BorderPadding(), + space_left, &container_builder_); return true; } @@ -2250,7 +2202,7 @@ NGBreakStatus NGBlockLayoutAlgorithm::BreakBeforeChildIfNeeded( BreakBeforeChild(ConstraintSpace(), child, layout_result, fragmentainer_block_offset, kBreakAppealPerfect, /* is_forced_break */ true, &container_builder_); - SetFragmentainerOutOfSpace(previous_inflow_position); + ConsumeRemainingFragmentainerSpace(previous_inflow_position); return NGBreakStatus::kBrokeBefore; } } @@ -2340,7 +2292,7 @@ NGBreakStatus NGBlockLayoutAlgorithm::BreakBeforeChildIfNeeded( &container_builder_)) return NGBreakStatus::kNeedsEarlierBreak; - SetFragmentainerOutOfSpace(previous_inflow_position); + ConsumeRemainingFragmentainerSpace(previous_inflow_position); return NGBreakStatus::kBrokeBefore; } @@ -2348,8 +2300,9 @@ void NGBlockLayoutAlgorithm::UpdateEarlyBreakBetweenLines() { // We shouldn't be here if we already know where to break. DCHECK(!early_break_); - // If the child already broke, it's a little too late to look for breakpoints. - DCHECK(!container_builder_.DidBreak()); + // If something in this flow already broke, it's a little too late to look for + // breakpoints. + DCHECK(!container_builder_.HasInflowChildBreakInside()); int line_count = container_builder_.LineCount(); if (line_count < 2) @@ -2412,7 +2365,7 @@ NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins( NGConstraintSpaceBuilder builder(ConstraintSpace(), child_style.GetWritingMode(), /* is_new_fc */ false); - builder.SetAvailableSize(child_available_size_); + builder.SetAvailableSize(ChildAvailableSize()); builder.SetPercentageResolutionSize(child_percentage_size_); NGConstraintSpace space = builder.ToConstraintSpace(); @@ -2435,7 +2388,8 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( const LogicalSize child_available_size, bool is_new_fc, const base::Optional<LayoutUnit> child_bfc_block_offset, - bool has_clearance_past_adjoining_floats) { + bool has_clearance_past_adjoining_floats, + LayoutUnit block_start_annotation_space) { const ComputedStyle& style = Style(); const ComputedStyle& child_style = child.Style(); WritingMode child_writing_mode = @@ -2550,6 +2504,7 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( // child establishes a new formatting context or not. builder.SetDiscardingMarginStrut(); } + builder.SetBlockStartAnnotationSpace(block_start_annotation_space); if (ConstraintSpace().HasBlockFragmentation()) { LayoutUnit fragmentainer_offset_delta; @@ -2746,7 +2701,7 @@ bool NGBlockLayoutAlgorithm::PositionOrPropagateListMarker( } list_marker.AddToBox(space, baseline_type, content, - border_scrollbar_padding_, *marker_layout_result, + BorderScrollbarPadding(), *marker_layout_result, *content_baseline, content_offset, &container_builder_); return true; @@ -2797,7 +2752,7 @@ bool NGBlockLayoutAlgorithm::PositionListMarkerWithoutLineBoxes( if (container_builder_.BfcBlockOffset()) { intrinsic_block_size_ = std::max(marker_block_size, intrinsic_block_size_); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); - container_builder_.SetBlockSize( + container_builder_.SetFragmentsTotalBlockSize( std::max(marker_block_size, container_builder_.Size().block_size)); } return true; @@ -2824,17 +2779,19 @@ void NGBlockLayoutAlgorithm::LayoutRubyText( NGConstraintSpaceBuilder builder( ConstraintSpace(), ruby_text_child->Style().GetWritingMode(), true); - builder.SetAvailableSize(child_available_size_); + builder.SetAvailableSize(ChildAvailableSize()); scoped_refptr<const NGLayoutResult> result = To<NGBlockNode>(*ruby_text_child) .Layout(builder.ToConstraintSpace(), break_token.get()); - LayoutUnit ruby_text_top; + LayoutUnit ruby_text_box_top; const NGPhysicalBoxFragment& ruby_text_fragment = To<NGPhysicalBoxFragment>(result->PhysicalFragment()); - if (Style().IsFlippedLinesWritingMode() == - (Style().GetRubyPosition() == RubyPosition::kAfter)) { + RubyPosition block_start_position = Style().IsFlippedLinesWritingMode() + ? RubyPosition::kAfter + : RubyPosition::kBefore; + if (Style().GetRubyPosition() == block_start_position) { LayoutUnit last_line_ruby_text_bottom = LastLineTextLogicalBottom( ruby_text_fragment, result->IntrinsicBlockSize()); @@ -2850,30 +2807,53 @@ void NGBlockLayoutAlgorithm::LayoutRubyText( } } } - ruby_text_top = first_line_top - last_line_ruby_text_bottom; + ruby_text_box_top = first_line_top - last_line_ruby_text_bottom; + const LayoutUnit ruby_text_top = + ruby_text_box_top + + FirstLineTextLogicalTop(ruby_text_fragment, LayoutUnit()); + if (ruby_text_top < LayoutUnit()) + container_builder_.SetAnnotationOverflow(ruby_text_top); } else { LayoutUnit first_line_ruby_text_top = FirstLineTextLogicalTop(ruby_text_fragment, LayoutUnit()); // Find a fragment for RubyBase, and get the bottom of text in it. LayoutUnit last_line_bottom; + LayoutUnit base_logical_bottom; for (const auto& child : container_builder_.Children()) { if (const auto* layout_object = child.fragment->GetLayoutObject()) { if (layout_object->IsRubyBase()) { - last_line_bottom = LastLineTextLogicalBottom( - To<NGPhysicalBoxFragment>(*child.fragment), + LayoutUnit base_block_size = child.fragment->Size() .ConvertToLogical(Style().GetWritingMode()) - .block_size); + .block_size; + last_line_bottom = LastLineTextLogicalBottom( + To<NGPhysicalBoxFragment>(*child.fragment), base_block_size); last_line_bottom += child.offset.block_offset; + base_logical_bottom = child.offset.block_offset + base_block_size; break; } } } - ruby_text_top = last_line_bottom - first_line_ruby_text_top; + ruby_text_box_top = last_line_bottom - first_line_ruby_text_top; + LayoutUnit ruby_text_height = + ruby_text_fragment.Size() + .ConvertToLogical(Style().GetWritingMode()) + .block_size; + ruby_text_height = + LastLineTextLogicalBottom(ruby_text_fragment, ruby_text_height); + LayoutUnit logical_bottom_overflow = + ruby_text_box_top + ruby_text_height - base_logical_bottom; + if (logical_bottom_overflow > LayoutUnit()) + container_builder_.SetAnnotationOverflow(logical_bottom_overflow); } container_builder_.AddResult(*result, - LogicalOffset(LayoutUnit(), ruby_text_top)); + LogicalOffset(LayoutUnit(), ruby_text_box_top)); + // RubyText provides baseline if RubyBase didn't. + // This behavior doesn't make much sense, but it's compatible with the legacy + // layout. + if (!container_builder_.Baseline()) + PropagateBaselineFromChild(ruby_text_fragment, ruby_text_box_top); } } // namespace blink 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 b7599118a1a..55a5c8352cc 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 @@ -32,6 +32,9 @@ class NGFragment; struct NGPreviousInflowPosition { LayoutUnit logical_block_offset; NGMarginStrut margin_strut; + // > 0: Block-end annotation space of the previous line + // < 0: Block-end annotation overflow of the previous line + LayoutUnit block_end_annotation_space; bool self_collapsing_child_had_clearance; }; @@ -113,7 +116,8 @@ class CORE_EXPORT NGBlockLayoutAlgorithm const LogicalSize child_available_size, bool is_new_fc, const base::Optional<LayoutUnit> bfc_block_offset = base::nullopt, - bool has_clearance_past_adjoining_floats = false); + bool has_clearance_past_adjoining_floats = false, + LayoutUnit block_start_annotation_space = LayoutUnit()); // @return Estimated BFC block offset for the "to be layout" child. NGInflowChildData ComputeChildData(const NGPreviousInflowPosition&, @@ -215,14 +219,11 @@ class CORE_EXPORT NGBlockLayoutAlgorithm // for the node being laid out by this algorithm. LayoutUnit FragmentainerSpaceAvailable() const; - // Return true if the node being laid out by this fragmentainer has used all - // the available space in the current fragmentainer. - // |block_offset| is the border-edge relative block offset we want to check - // whether fits within the fragmentainer or not. - bool IsFragmentainerOutOfSpace(LayoutUnit block_offset) const; - - // Signal that we've reached the end of the fragmentainer. - void SetFragmentainerOutOfSpace(NGPreviousInflowPosition*); + // Consume all remaining fragmentainer space. This happens when we decide to + // break before a child. + // + // https://www.w3.org/TR/css-break-3/#box-splitting + void ConsumeRemainingFragmentainerSpace(NGPreviousInflowPosition*); // Final adjustments before fragment creation. We need to prevent the fragment // from crossing fragmentainer boundaries, and rather create a break token if @@ -342,15 +343,6 @@ class CORE_EXPORT NGBlockLayoutAlgorithm // |ruby_text_child|. This is called only if IsRubyText() returns true. void LayoutRubyText(NGLayoutInputNode* ruby_text_child); - // Border + padding sum, resolved from the node's computed style. - const NGBoxStrut border_padding_; - - // Border + scrollbar + padding sum for the fragment to be generated (most - // importantly, for non-first fragments, leading block border + scrollbar + - // padding is zero). - NGBoxStrut border_scrollbar_padding_; - - LogicalSize child_available_size_; LogicalSize child_percentage_size_; LogicalSize replaced_child_percentage_size_; @@ -386,14 +378,6 @@ class CORE_EXPORT NGBlockLayoutAlgorithm // A or B breakpoint (between block-level siblings or line box siblings). bool has_processed_first_child_ = false; - // Set once we've inserted a break before a float. We need to know this, so - // that we don't attempt to lay out any more floats in the current - // fragmentainer. Floats aren't allowed have an earlier block-start offset - // than earlier floats. - bool broke_before_float_ = false; - - bool did_break_before_child_ = false; - NGExclusionSpace exclusion_space_; // If set, this is the number of lines until a clamp. A value of 1 indicates 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 6b75c41df3b..1af502775f3 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 @@ -45,7 +45,8 @@ class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { NGBlockLayoutAlgorithm algorithm({node, fragment_geometry, space}); MinMaxSizesInput input( - /* percentage_resolution_block_size */ (LayoutUnit())); + /* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent); return algorithm.ComputeMinMaxSizes(input).sizes; } 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 80d066499e2..2d1a57926ad 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 @@ -196,6 +196,10 @@ void UpdateLegacyMultiColumnFlowThread( LayoutMultiColumnSet* column_set = ToLayoutMultiColumnSetOrNull(flow_thread->FirstMultiColumnBox()); for (const auto& child : fragment.Children()) { + // TODO(almaher): Remove check for out of flow. + if (child->IsOutOfFlowPositioned()) + continue; + if (child->GetLayoutObject() && child->GetLayoutObject()->IsColumnSpanAll()) { // Column spanners are not part of the fragmentation context. We'll use @@ -258,11 +262,16 @@ void UpdateLegacyMultiColumnFlowThread( flow_thread->ClearNeedsLayout(); } -NGConstraintSpace CreateConstraintSpaceForMinMax(const NGBlockNode& node) { +NGConstraintSpace CreateConstraintSpaceForMinMax( + const NGBlockNode& node, + const MinMaxSizesInput& input) { NGConstraintSpaceBuilder builder(node.Style().GetWritingMode(), node.Style().GetWritingMode(), node.CreatesNewFormattingContext()); builder.SetTextDirection(node.Style().Direction()); + builder.SetAvailableSize(LogicalSize()); + builder.SetPercentageResolutionSize( + {LayoutUnit(), input.percentage_resolution_block_size}); return builder.ToConstraintSpace(); } @@ -351,7 +360,7 @@ bool CanUseCachedIntrinsicInlineSizes(const MinMaxSizesInput& input, scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( const NGConstraintSpace& constraint_space, const NGBlockBreakToken* break_token, - const NGEarlyBreak* early_break) { + const NGEarlyBreak* early_break) const { // Use the old layout code and synthesize a fragment. if (!CanUseNewLayout()) return RunLegacyLayout(constraint_space); @@ -508,7 +517,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( } scoped_refptr<const NGLayoutResult> NGBlockNode::SimplifiedLayout( - const NGPhysicalFragment& previous_fragment) { + const NGPhysicalFragment& previous_fragment) const { scoped_refptr<const NGLayoutResult> previous_result = box_->GetCachedLayoutResult(); DCHECK(previous_result); @@ -587,7 +596,7 @@ NGBlockNode::CachedLayoutResultForOutOfFlowPositioned( return cached_layout_result; } -void NGBlockNode::PrepareForLayout() { +void NGBlockNode::PrepareForLayout() const { auto* block = DynamicTo<LayoutBlock>(box_); if (block && block->HasOverflowClip()) { DCHECK(block->GetScrollableArea()); @@ -605,7 +614,7 @@ void NGBlockNode::FinishLayout( LayoutBlockFlow* block_flow, const NGConstraintSpace& constraint_space, const NGBlockBreakToken* break_token, - scoped_refptr<const NGLayoutResult> layout_result) { + scoped_refptr<const NGLayoutResult> layout_result) const { // If we abort layout and don't clear the cached layout-result, we can end // up in a state where the layout-object tree doesn't match fragment tree // referenced by this layout-result. @@ -676,7 +685,7 @@ void NGBlockNode::FinishLayout( MinMaxSizesResult NGBlockNode::ComputeMinMaxSizes( WritingMode container_writing_mode, const MinMaxSizesInput& input, - const NGConstraintSpace* constraint_space) { + const NGConstraintSpace* constraint_space) const { // TODO(layoutng) Can UpdateMarkerTextIfNeeded call be moved // somewhere else? List items need up-to-date markers before layout. if (IsListItem()) @@ -687,6 +696,10 @@ MinMaxSizesResult NGBlockNode::ComputeMinMaxSizes( // If we're orthogonal, run layout to compute the sizes. if (is_orthogonal_flow_root) { + // If we have an aspect ratio, we may be able to avoid laying out the + // child as an optimization, if performance testing shows this to be + // important. + MinMaxSizes sizes; // Some other areas of the code can query the intrinsic-sizes while outside // of the layout phase. @@ -706,6 +719,24 @@ MinMaxSizesResult NGBlockNode::ComputeMinMaxSizes( return {sizes, /* depends_on_percentage_block_size */ false}; } + // Synthesize a zero space if not provided. + auto zero_constraint_space = CreateConstraintSpaceForMinMax(*this, input); + if (!constraint_space) + constraint_space = &zero_constraint_space; + + if (Style().AspectRatio() && input.type == MinMaxSizesType::kContent) { + NGFragmentGeometry fragment_geometry = + CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this); + NGBoxStrut border_padding = + fragment_geometry.border + fragment_geometry.padding; + LayoutUnit size_from_ar = ComputeInlineSizeFromAspectRatio( + *constraint_space, Style(), border_padding); + if (size_from_ar != kIndefiniteSize) { + return {{size_from_ar, size_from_ar}, + Style().LogicalHeight().IsPercentOrCalc()}; + } + } + bool can_use_cached_intrinsic_inline_sizes = CanUseCachedIntrinsicInlineSizes(input, *this); @@ -723,11 +754,6 @@ MinMaxSizesResult NGBlockNode::ComputeMinMaxSizes( return {sizes, depends_on_percentage_block_size}; } - // Synthesize a zero space if not provided. - auto zero_constraint_space = CreateConstraintSpaceForMinMax(*this); - if (!constraint_space) - constraint_space = &zero_constraint_space; - NGFragmentGeometry fragment_geometry = CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this); @@ -918,23 +944,42 @@ String NGBlockNode::ToString() const { void NGBlockNode::CopyFragmentDataToLayoutBox( const NGConstraintSpace& constraint_space, const NGLayoutResult& layout_result, - const NGBlockBreakToken* previous_break_token) { + const NGBlockBreakToken* previous_break_token) const { const auto& physical_fragment = To<NGPhysicalBoxFragment>(layout_result.PhysicalFragment()); NGBoxFragment fragment(constraint_space.GetWritingMode(), constraint_space.Direction(), physical_fragment); 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 - // layout object. Logical width will only be set at the first fragment and is - // expected to remain the same throughout all subsequent fragments, since - // legacy layout doesn't support non-uniform fragmentainer widths. - LayoutUnit intrinsic_content_logical_height; + NGBoxStrut borders = fragment.Borders(); + NGBoxStrut scrollbars = ComputeScrollbars(constraint_space, *this); + NGBoxStrut padding = fragment.Padding(); + NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding; + bool is_last_fragment = !physical_fragment.BreakToken(); + + // For each fragment we process, we'll accumulate the logical height. We reset + // it at the first fragment, and accumulate at each method call for fragments + // belonging to the same layout object. Logical width will only be set at the + // first fragment and is expected to remain the same throughout all subsequent + // fragments, since legacy layout doesn't support non-uniform fragmentainer + // widths. if (LIKELY(physical_fragment.IsFirstForNode())) { box_->SetSize(LayoutSize(physical_fragment.Size().width, physical_fragment.Size().height)); + // If this is a fragment from a node that didn't break into multiple + // fragments, write back the intrinsic size. We skip this if the node has + // fragmented, since intrinsic block-size is rather meaningless in that + // case, because the block-size may have been affected by something on the + // outside (i.e. the fragmentainer). + // + // If we had a fixed block size, our children will have sized themselves + // relative to the fixed size, which would make our intrinsic size incorrect + // (too big). So skip the write-back in that case, too. + if (LIKELY(is_last_fragment && !constraint_space.IsFixedBlockSize())) { + box_->SetIntrinsicContentLogicalHeight( + layout_result.IntrinsicBlockSize() - + border_scrollbar_padding.BlockSum()); + } } else { DCHECK_EQ(box_->LogicalWidth(), fragment_logical_size.inline_size) << "Variable fragment inline size not supported"; @@ -942,24 +987,6 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( if (previous_break_token) logical_height += previous_break_token->ConsumedBlockSize(); box_->SetLogicalHeight(logical_height); - intrinsic_content_logical_height = box_->IntrinsicContentLogicalHeight(); - } - - intrinsic_content_logical_height += layout_result.IntrinsicBlockSize(); - - NGBoxStrut borders = fragment.Borders(); - NGBoxStrut scrollbars = ComputeScrollbars(constraint_space, *this); - NGBoxStrut padding = fragment.Padding(); - NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding; - bool is_last_fragment = !physical_fragment.BreakToken(); - - if (LIKELY(is_last_fragment)) - intrinsic_content_logical_height -= border_scrollbar_padding.BlockSum(); - if (!constraint_space.IsFixedBlockSize()) { - // If we had a fixed block size, our children will have sized themselves - // relative to the fixed size, which would make our intrinsic size - // incorrect (too big). - box_->SetIntrinsicContentLogicalHeight(intrinsic_content_logical_height); } // TODO(mstensho): This should always be done by the parent algorithm, since @@ -1041,7 +1068,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( void NGBlockNode::PlaceChildrenInLayoutBox( const NGPhysicalBoxFragment& physical_fragment, - const NGBlockBreakToken* previous_break_token) { + const NGBlockBreakToken* previous_break_token) const { LayoutBox* rendered_legend = nullptr; for (const auto& child_fragment : physical_fragment.Children()) { // Skip any line-boxes we have as children, this is handled within @@ -1078,12 +1105,14 @@ void NGBlockNode::PlaceChildrenInLayoutBox( } void NGBlockNode::PlaceChildrenInFlowThread( - const NGPhysicalBoxFragment& physical_fragment) { + const NGPhysicalBoxFragment& physical_fragment) const { const NGBlockBreakToken* previous_break_token = nullptr; for (const auto& child : physical_fragment.Children()) { const LayoutObject* child_object = child->GetLayoutObject(); if (child_object && child_object != box_) { - DCHECK(child_object->IsColumnSpanAll()); + // TODO(almaher): Remove check for out of flow. + DCHECK(child_object->IsColumnSpanAll() || + child_object->IsOutOfFlowPositioned()); CopyChildFragmentPosition(To<NGPhysicalBoxFragment>(*child), child.offset, physical_fragment); continue; @@ -1102,7 +1131,7 @@ void NGBlockNode::CopyChildFragmentPosition( const NGPhysicalBoxFragment& child_fragment, PhysicalOffset offset, const NGPhysicalBoxFragment& container_fragment, - const NGBlockBreakToken* previous_container_break_token) { + const NGBlockBreakToken* previous_container_break_token) const { LayoutBox* layout_box = ToLayoutBox(child_fragment.GetMutableLayoutObject()); if (!layout_box) return; @@ -1140,7 +1169,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( const NGPhysicalContainerFragment& container, LayoutUnit initial_container_width, bool initial_container_is_flipped, - PhysicalOffset offset) { + PhysicalOffset offset) const { DCHECK(!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); for (const auto& child : container.Children()) { if (child->IsContainer()) { @@ -1185,7 +1214,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( void NGBlockNode::CopyFragmentItemsToLayoutBox( const NGPhysicalBoxFragment& container, - const NGFragmentItems& items) { + const NGFragmentItems& items) const { DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); bool initial_container_is_flipped = Style().IsFlippedBlocksWritingMode(); @@ -1321,7 +1350,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline( } scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( - const NGConstraintSpace& constraint_space) { + const NGConstraintSpace& constraint_space) const { // 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 // cooperate on e.g. margin collapsing. @@ -1371,7 +1400,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( // TODO(kojii): Implement use_first_line_style. NGBoxFragmentBuilder builder(*this, box_->Style(), &constraint_space, - writing_mode, box_->StyleRef().Direction()); + {writing_mode, box_->StyleRef().Direction()}); builder.SetIsNewFormattingContext( constraint_space.IsNewFormattingContext()); builder.SetInitialFragmentGeometry(fragment_geometry); @@ -1452,7 +1481,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunSimplifiedLayout( void NGBlockNode::CopyBaselinesFromLegacyLayout( const NGConstraintSpace& constraint_space, - NGBoxFragmentBuilder* builder) { + NGBoxFragmentBuilder* builder) const { // As the calls to query baselines from legacy layout are potentially // expensive we only ask for them if needed. // TODO(layout-dev): Once we have flexbox, and editing switched over to @@ -1479,7 +1508,7 @@ void NGBlockNode::CopyBaselinesFromLegacyLayout( } LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout( - const NGConstraintSpace& constraint_space) { + const NGConstraintSpace& constraint_space) const { LineDirectionMode line_direction = box_->IsHorizontalWritingMode() ? LineDirectionMode::kHorizontalLine : LineDirectionMode::kVerticalLine; @@ -1512,7 +1541,7 @@ LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout( // in the parents writing mode. void NGBlockNode::UpdateShapeOutsideInfoIfNeeded( const NGLayoutResult& layout_result, - LayoutUnit percentage_resolution_inline_size) { + LayoutUnit percentage_resolution_inline_size) const { if (!box_->IsFloating() || !box_->GetShapeOutsideInfo()) return; @@ -1550,7 +1579,7 @@ void NGBlockNode::StoreMargins(const NGPhysicalBoxStrut& physical_margins) { void NGBlockNode::AddColumnResult( scoped_refptr<const NGLayoutResult> result, - const NGBlockBreakToken* incoming_break_token) { + const NGBlockBreakToken* incoming_break_token) const { wtf_size_t index = FragmentIndex(incoming_break_token); GetFlowThread(To<LayoutBlockFlow>(box_))->AddLayoutResult(result, index); } 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 9befdc8f5e0..87891e8467f 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 @@ -37,7 +37,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { scoped_refptr<const NGLayoutResult> Layout( const NGConstraintSpace& constraint_space, const NGBlockBreakToken* break_token = nullptr, - const NGEarlyBreak* = nullptr); + const NGEarlyBreak* = nullptr) const; // This method is just for use within the |NGSimplifiedLayoutAlgorithm|. // @@ -45,7 +45,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // space used to generate the |NGLayoutResult|. // Otherwise it will simply return the previous layout result generated. scoped_refptr<const NGLayoutResult> SimplifiedLayout( - const NGPhysicalFragment& previous_fragment); + const NGPhysicalFragment& previous_fragment) const; // This method is just for use within the |NGOutOfFlowLayoutPart|. // @@ -82,9 +82,10 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // The constraint space is also used to perform layout when this block's // writing mode is orthogonal to its parent's, in which case the constraint // space is not optional. - MinMaxSizesResult ComputeMinMaxSizes(WritingMode container_writing_mode, - const MinMaxSizesInput&, - const NGConstraintSpace* = nullptr); + MinMaxSizesResult ComputeMinMaxSizes( + WritingMode container_writing_mode, + const MinMaxSizesInput&, + const NGConstraintSpace* = nullptr) const; MinMaxSizes ComputeMinMaxSizesFromLegacy(const MinMaxSizesInput&) const; @@ -152,7 +153,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // LayoutObject-less, but we still need to keep the fragments generated // somewhere. void AddColumnResult(scoped_refptr<const NGLayoutResult>, - const NGBlockBreakToken* incoming_break_token); + const NGBlockBreakToken* incoming_break_token) const; static bool CanUseNewLayout(const LayoutBox&); bool CanUseNewLayout() const; @@ -160,11 +161,12 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { String ToString() const; private: - void PrepareForLayout(); + void PrepareForLayout() const; // 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> RunLegacyLayout( + const NGConstraintSpace&) const; scoped_refptr<const NGLayoutResult> RunSimplifiedLayout( const NGLayoutAlgorithmParams&, @@ -175,37 +177,39 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { void FinishLayout(LayoutBlockFlow*, const NGConstraintSpace&, const NGBlockBreakToken*, - scoped_refptr<const NGLayoutResult>); + scoped_refptr<const NGLayoutResult>) const; // After we run the layout algorithm, this function copies back the geometry // data to the layout box. void CopyFragmentDataToLayoutBox( const NGConstraintSpace&, const NGLayoutResult&, - const NGBlockBreakToken* previous_break_token); + const NGBlockBreakToken* previous_break_token) const; void CopyFragmentItemsToLayoutBox(const NGPhysicalBoxFragment& container, - const NGFragmentItems& items); + const NGFragmentItems& items) const; void CopyFragmentDataToLayoutBoxForInlineChildren( const NGPhysicalContainerFragment& container, LayoutUnit initial_container_width, bool initial_container_is_flipped, - PhysicalOffset offset = {}); - void PlaceChildrenInLayoutBox(const NGPhysicalBoxFragment&, - const NGBlockBreakToken* previous_break_token); - void PlaceChildrenInFlowThread(const NGPhysicalBoxFragment&); + PhysicalOffset offset = {}) const; + void PlaceChildrenInLayoutBox( + const NGPhysicalBoxFragment&, + const NGBlockBreakToken* previous_break_token) const; + void PlaceChildrenInFlowThread(const NGPhysicalBoxFragment&) const; void CopyChildFragmentPosition( const NGPhysicalBoxFragment& child_fragment, PhysicalOffset, const NGPhysicalBoxFragment& container_fragment, - const NGBlockBreakToken* previous_container_break_token = nullptr); + const NGBlockBreakToken* previous_container_break_token = nullptr) const; void CopyBaselinesFromLegacyLayout(const NGConstraintSpace&, - NGBoxFragmentBuilder*); - LayoutUnit AtomicInlineBaselineFromLegacyLayout(const NGConstraintSpace&); + NGBoxFragmentBuilder*) const; + LayoutUnit AtomicInlineBaselineFromLegacyLayout( + const NGConstraintSpace&) const; void UpdateShapeOutsideInfoIfNeeded( const NGLayoutResult&, - LayoutUnit percentage_resolution_inline_size); + LayoutUnit percentage_resolution_inline_size) const; }; template <> diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc index 3ad5edcf391..7cf812f28b1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc @@ -178,7 +178,8 @@ TEST_F(NGBlockNodeForTest, MinAndMaxContent) { box.ComputeMinMaxSizes( WritingMode::kHorizontalTb, MinMaxSizesInput( - /* percentage_resolution_block_size */ LayoutUnit())) + /* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent)) .sizes; EXPECT_EQ(LayoutUnit(kWidth), sizes.min_size); EXPECT_EQ(LayoutUnit(kWidth), sizes.max_size); 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 8a0c0a6ebf2..532fef71209 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 @@ -159,7 +159,10 @@ void NGBoxFragmentBuilder::AddBreakBeforeChild( } DCHECK(has_block_fragmentation_); - SetDidBreak(); + + if (!has_inflow_child_break_inside_) + has_inflow_child_break_inside_ = !child.IsFloatingOrOutOfFlowPositioned(); + if (auto* child_inline_node = DynamicTo<NGInlineNode>(child)) { if (inline_break_tokens_.IsEmpty()) { // In some cases we may want to break before the first line, as a last @@ -197,6 +200,7 @@ void NGBoxFragmentBuilder::AddBreakToken( scoped_refptr<const NGBreakToken> token) { DCHECK(token.get()); child_break_tokens_.push_back(std::move(token)); + has_inflow_child_break_inside_ = true; } void NGBoxFragmentBuilder::AddOutOfFlowLegacyCandidate( @@ -245,9 +249,20 @@ void NGBoxFragmentBuilder::PropagateBreak( const NGLayoutResult& child_layout_result) { if (LIKELY(!has_block_fragmentation_)) return; - if (!did_break_) { - const auto* token = child_layout_result.PhysicalFragment().BreakToken(); - did_break_ = token && !token->IsFinished(); + if (!has_inflow_child_break_inside_) { + // Figure out if this child break is in the same flow as this parent. If + // it's an out-of-flow positioned box, it's not. If it's in a parallel flow, + // it's also not. + const auto& child_fragment = + To<NGPhysicalBoxFragment>(child_layout_result.PhysicalFragment()); + if (!child_fragment.IsFloatingOrOutOfFlowPositioned()) { + if (const auto* token = child_fragment.BreakToken()) { + if (!token->IsFinished() && + (!token->IsBlockType() || + !To<NGBlockBreakToken>(token)->IsAtBlockEnd())) + has_inflow_child_break_inside_ = true; + } + } } if (child_layout_result.HasForcedBreak()) { SetHasForcedBreak(); @@ -280,10 +295,10 @@ scoped_refptr<const NGLayoutResult> NGBoxFragmentBuilder::ToBoxFragment( child_break_tokens_.push_back(std::move(token)); } } - if (did_break_) { + if (DidBreakSelf() || HasChildBreakInside()) { break_token_ = NGBlockBreakToken::Create( node_, consumed_block_size_, sequence_number_, child_break_tokens_, - break_appeal_, has_seen_all_children_); + break_appeal_, has_seen_all_children_, is_at_block_end_); } } @@ -351,7 +366,7 @@ void NGBoxFragmentBuilder::ComputeInlineContainerGeometryFromFragmentTree( // This function has detailed knowledge of inline fragment tree structure, // and will break if this changes. DCHECK_GE(InlineSize(), LayoutUnit()); - DCHECK_GE(BlockSize(), LayoutUnit()); + DCHECK_GE(FragmentBlockSize(), LayoutUnit()); #if DCHECK_IS_ON() // Make sure all entries are continuation root. for (const auto& entry : *inline_containing_block_map) @@ -407,7 +422,7 @@ void NGBoxFragmentBuilder::ComputeInlineContainerGeometry( // This function requires that we have the final size of the fragment set // upon the builder. DCHECK_GE(InlineSize(), LayoutUnit()); - DCHECK_GE(BlockSize(), LayoutUnit()); + DCHECK_GE(FragmentBlockSize(), LayoutUnit()); #if DCHECK_IS_ON() // Make sure all entries are a continuation root. @@ -420,9 +435,10 @@ void NGBoxFragmentBuilder::ComputeInlineContainerGeometry( if (items_builder_) { // To access the items correctly we need to convert them to the physical // coordinate space. + DCHECK_EQ(items_builder_->GetWritingMode(), GetWritingMode()); + DCHECK_EQ(items_builder_->Direction(), Direction()); GatherInlineContainerFragmentsFromItems( - items_builder_->Items(GetWritingMode(), Direction(), - ToPhysicalSize(Size(), GetWritingMode())), + items_builder_->Items(ToPhysicalSize(Size(), GetWritingMode())), PhysicalOffset(), inline_containing_block_map, &containing_linebox_map); return; } @@ -463,13 +479,15 @@ void NGBoxFragmentBuilder::SetLastBaselineToBlockEndMarginEdgeIfNeeded() { // When overflow is present (within an atomic-inline baseline context) we // should always use the block-end margin edge as the baseline. NGBoxStrut margins = ComputeMarginsForSelf(*ConstraintSpace(), Style()); - SetLastBaseline(BlockSize() + margins.block_end); + SetLastBaseline(FragmentBlockSize() + margins.block_end); } #if DCHECK_IS_ON() void NGBoxFragmentBuilder::CheckNoBlockFragmentation() const { - DCHECK(!did_break_); + DCHECK(!HasChildBreakInside()); + DCHECK(!HasInflowChildBreakInside()); + DCHECK(!DidBreakSelf()); DCHECK(!has_forced_break_); DCHECK_EQ(consumed_block_size_, LayoutUnit()); DCHECK_EQ(minimal_space_shortage_, LayoutUnit::Max()); 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 67cb8f120c3..26b0856df32 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 @@ -10,9 +10,12 @@ #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_mathml_paint_info.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.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" #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/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h" @@ -29,31 +32,25 @@ class CORE_EXPORT NGBoxFragmentBuilder final NGBoxFragmentBuilder(NGLayoutInputNode node, scoped_refptr<const ComputedStyle> style, const NGConstraintSpace* space, - WritingMode writing_mode, - TextDirection direction) + WritingDirectionMode writing_direction) : NGContainerFragmentBuilder(node, std::move(style), space, - writing_mode, - direction), + writing_direction), box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), - is_inline_formatting_context_(node.IsInline()), - did_break_(false) {} + is_inline_formatting_context_(node.IsInline()) {} // Build a fragment for LayoutObject without NGLayoutInputNode. LayoutInline // has NGInlineItem but does not have corresponding NGLayoutInputNode. NGBoxFragmentBuilder(LayoutObject* layout_object, scoped_refptr<const ComputedStyle> style, - WritingMode writing_mode, - TextDirection direction) + WritingDirectionMode writing_direction) : NGContainerFragmentBuilder(/* node */ nullptr, std::move(style), /* space */ nullptr, - writing_mode, - direction), + writing_direction), box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), - is_inline_formatting_context_(true), - did_break_(false) { + is_inline_formatting_context_(true) { layout_object_ = layout_object; } @@ -62,6 +59,32 @@ class CORE_EXPORT NGBoxFragmentBuilder final initial_fragment_geometry_ = &initial_fragment_geometry; size_ = initial_fragment_geometry_->border_box_size; is_initial_block_size_indefinite_ = size_.block_size == kIndefiniteSize; + + border_padding_ = + initial_fragment_geometry.border + initial_fragment_geometry.padding; + border_scrollbar_padding_ = + border_padding_ + initial_fragment_geometry.scrollbar; + if (space_) { + child_available_size_ = CalculateChildAvailableSize( + *space_, To<NGBlockNode>(node_), size_, border_scrollbar_padding_); + } + } + + void AdjustBorderScrollbarPaddingForFragmentation( + const NGBlockBreakToken* break_token) { + if (LIKELY(!break_token)) + return; + if (break_token->IsBreakBefore()) + return; + border_scrollbar_padding_.block_start = LayoutUnit(); + } + + void AdjustBorderScrollbarPaddingForTableCell() { + if (!space_->IsTableCell()) + return; + + border_scrollbar_padding_ += + ComputeIntrinsicPadding(*space_, *style_, Scrollbar()); } const NGFragmentGeometry& InitialFragmentGeometry() const { @@ -69,6 +92,52 @@ class CORE_EXPORT NGBoxFragmentBuilder final return *initial_fragment_geometry_; } + // Use the block-size setters/getters further down instead of the inherited + // ones. + LayoutUnit BlockSize() const = delete; + void SetBlockSize(LayoutUnit block_size) = delete; + + // Set the total border-box block-size of all the fragments to be generated + // from this node (as if we stitched them together). Layout algorithms are + // expected to pass this value, and at the end of layout (if block + // fragmentation is needed), the fragmentation machinery will be invoked to + // adjust the block-size to the correct size, ensuring that we break at the + // best location. + void SetFragmentsTotalBlockSize(LayoutUnit block_size) { +#if DCHECK_IS_ON() + // Note that we just store the block-size in a shared field. We have a flag + // for debugging, to assert that we know what we're doing when attempting to + // access the data. + block_size_is_for_all_fragments_ = true; +#endif + size_.block_size = block_size; + } + LayoutUnit FragmentsTotalBlockSize() const { +#if DCHECK_IS_ON() + if (has_block_fragmentation_) + DCHECK(block_size_is_for_all_fragments_); +#endif + return size_.block_size; + } + + // Set the final block-size of this fragment. + void SetFragmentBlockSize(LayoutUnit block_size) { +#if DCHECK_IS_ON() + // Note that we just store the block-size in a shared field. We have a flag + // for debugging, to assert that we know what we're doing when attempting to + // access the data. + block_size_is_for_all_fragments_ = false; +#endif + size_.block_size = block_size; + } + LayoutUnit FragmentBlockSize() const { +#if DCHECK_IS_ON() + if (has_block_fragmentation_) + DCHECK(!block_size_is_for_all_fragments_); +#endif + return size_.block_size; + } + void SetOverflowBlockSize(LayoutUnit overflow_block_size) { overflow_block_size_ = overflow_block_size; } @@ -92,6 +161,22 @@ class CORE_EXPORT NGBoxFragmentBuilder final DCHECK(initial_fragment_geometry_); return initial_fragment_geometry_->border_box_size; } + const NGBoxStrut& BorderPadding() const { + DCHECK(initial_fragment_geometry_); + return border_padding_; + } + const NGBoxStrut& BorderScrollbarPadding() const { + DCHECK(initial_fragment_geometry_); + return border_scrollbar_padding_; + } + // The child available-size is subtly different from the content-box size of + // an element. For an anonymous-block the child available-size is equal to + // its non-anonymous parent (similar to percentages). + const LogicalSize& ChildAvailableSize() const { + DCHECK(initial_fragment_geometry_); + DCHECK(space_); + return child_available_size_; + } // Add a break token for a child that doesn't yet have any fragments, because // its first fragment is to be produced in the next fragmentainer. This will @@ -108,6 +193,8 @@ class CORE_EXPORT NGBoxFragmentBuilder final // descendants, propagating fragmentainer breaks, and more. void AddResult(const NGLayoutResult&, const LogicalOffset); + // Manually add a break token to the builder. Note that we're assuming that + // this break token is for content in the same flow as this parent. void AddBreakToken(scoped_refptr<const NGBreakToken>); void AddOutOfFlowLegacyCandidate(NGBlockNode, @@ -126,10 +213,31 @@ class CORE_EXPORT NGBoxFragmentBuilder final sequence_number_ = sequence_number; } - // Specify that we broke. - // - // This will result in a fragment which has an unfinished break token. - void SetDidBreak() { did_break_ = true; } + // Return true if we broke inside this node on our own initiative (typically + // not because of a child break, but rather due to the size of this node). + bool DidBreakSelf() const { return did_break_self_; } + void SetDidBreakSelf() { did_break_self_ = true; } + + // Return true if we need to break before or inside any child, doesn't matter + // if it's in-flow or not. As long as there are only breaks in parallel flows, + // we may continue layout, but when we're done, we'll need to create a break + // token for this fragment nevertheless, so that we re-enter, descend and + // resume at the broken children in the next fragmentainer. + bool HasChildBreakInside() const { + if (!child_break_tokens_.IsEmpty()) + return true; + // Inline nodes produce a "finished" trailing break token even if we don't + // need to block-fragment. + return !inline_break_tokens_.IsEmpty() && + !inline_break_tokens_.back()->IsFinished(); + } + + // Return true if we need to break before or inside any in-flow child that + // doesn't establish a parallel flow. When this happens, we want to finish our + // fragment, create a break token, and resume in the next fragmentainer. + bool HasInflowChildBreakInside() const { + return has_inflow_child_break_inside_; + } // Report space shortage, i.e. how much more space would have been sufficient // to prevent some piece of content from breaking. This information may be @@ -198,6 +306,9 @@ class CORE_EXPORT NGBoxFragmentBuilder final // children have been fully laid out, or have break tokens. No more children // left to discover. void SetHasSeenAllChildren() { has_seen_all_children_ = true; } + bool HasSeenAllChildren() { return has_seen_all_children_; } + + void SetIsAtBlockEnd() { is_at_block_end_ = true; } void SetColumnSpanner(NGBlockNode spanner) { column_spanner_ = spanner; } bool FoundColumnSpanner() const { return !!column_spanner_; } @@ -246,6 +357,9 @@ class CORE_EXPORT NGBoxFragmentBuilder final void SetBoxType(NGPhysicalFragment::NGBoxType box_type) { box_type_ = box_type; } + bool IsFragmentainerBoxType() const { + return BoxType() == NGPhysicalFragment::kColumnBox; + } void SetIsFieldsetContainer() { is_fieldset_container_ = true; } void SetIsLegacyLayoutRoot() { is_legacy_layout_root_ = true; } @@ -254,8 +368,22 @@ class CORE_EXPORT NGBoxFragmentBuilder final } void SetIsMathMLFraction() { is_math_fraction_ = true; } - - bool DidBreak() const { return did_break_; } + void SetMathMLPaintInfo( + UChar operator_character, + scoped_refptr<const ShapeResultView> operator_shape_result_view, + LayoutUnit operator_inline_size, + LayoutUnit operator_ascent, + LayoutUnit operator_descent) { + if (!mathml_paint_info_) + mathml_paint_info_ = std::make_unique<NGMathMLPaintInfo>(); + + mathml_paint_info_->operator_shape_result_view = + std::move(operator_shape_result_view); + + mathml_paint_info_->operator_inline_size = operator_inline_size; + mathml_paint_info_->operator_ascent = operator_ascent; + mathml_paint_info_->operator_descent = operator_descent; + } void SetBorderEdges(NGBorderEdges border_edges) { border_edges_ = border_edges; @@ -329,12 +457,15 @@ class CORE_EXPORT NGBoxFragmentBuilder final void SetHasForcedBreak() { has_forced_break_ = true; - minimal_space_shortage_ = LayoutUnit(); + minimal_space_shortage_ = LayoutUnit::Max(); } scoped_refptr<const NGLayoutResult> ToBoxFragment(WritingMode); const NGFragmentGeometry* initial_fragment_geometry_ = nullptr; + NGBoxStrut border_padding_; + NGBoxStrut border_scrollbar_padding_; + LogicalSize child_available_size_; LayoutUnit overflow_block_size_ = kIndefiniteSize; LayoutUnit intrinsic_block_size_; @@ -347,12 +478,14 @@ class CORE_EXPORT NGBoxFragmentBuilder final bool is_initial_block_size_indefinite_ = false; bool is_inline_formatting_context_; bool is_first_for_node_ = true; - bool did_break_; + bool did_break_self_ = false; + bool has_inflow_child_break_inside_ = false; bool has_forced_break_ = false; bool is_new_fc_ = false; bool subtree_modified_margin_strut_ = false; bool has_seen_all_children_ = false; bool is_math_fraction_ = false; + bool is_at_block_end_ = false; LayoutUnit consumed_block_size_; unsigned sequence_number_ = 0; @@ -373,6 +506,14 @@ class CORE_EXPORT NGBoxFragmentBuilder final scoped_refptr<SerializedScriptValue> custom_layout_data_; base::Optional<int> lines_until_clamp_; + std::unique_ptr<NGMathMLPaintInfo> mathml_paint_info_; + +#if DCHECK_IS_ON() + // Describes what size_.block_size represents; either the size of a single + // fragment (false), or the size of all fragments for a node (true). + bool block_size_is_for_all_fragments_ = false; +#endif + friend class NGPhysicalBoxFragment; friend class NGLayoutResult; }; 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 29c7bb6009b..5b927db4bfd 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 @@ -78,6 +78,7 @@ class CORE_EXPORT NGBreakToken : public RefCounted<NGBreakToken> { flags_(0), is_break_before_(false), is_forced_break_(false), + is_at_block_end_(false), break_appeal_(kBreakAppealPerfect), has_seen_all_children_(false) { DCHECK_EQ(type, static_cast<NGBreakTokenType>(node.Type())); @@ -104,6 +105,11 @@ class CORE_EXPORT NGBreakToken : public RefCounted<NGBreakToken> { unsigned is_forced_break_ : 1; + // Set when layout is past the block-end border edge. If we break when we're + // in this state, it means that something is overflowing, and thus establishes + // a parallel flow. + unsigned is_at_block_end_ : 1; + // If the break is unforced, this is the appeal of the break. Higher is // better. Violating breaking rules decreases appeal. Forced breaks always // have perfect appeal. 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 2db4791c47a..5950ccb01a1 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 @@ -88,32 +88,30 @@ void PushSpannerBreakTokens( NGColumnLayoutAlgorithm::NGColumnLayoutAlgorithm( const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params), - early_break_(params.early_break), - border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding), - border_scrollbar_padding_(border_padding_ + - params.fragment_geometry.scrollbar) { - AdjustForFragmentation(BreakToken(), &border_scrollbar_padding_); + : NGLayoutAlgorithm(params), early_break_(params.early_break) { container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + container_builder_.AdjustBorderScrollbarPaddingForFragmentation(BreakToken()); } scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { - LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - content_box_size_ = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - - DCHECK_GE(content_box_size_.inline_size, LayoutUnit()); + const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); + // TODO(mstensho): This isn't the content-box size, as + // |BorderScrollbarPadding()| has been adjusted for fragmentation. Verify + // that this is the correct size. + column_block_size_ = + ShrinkLogicalSize(border_box_size, BorderScrollbarPadding()).block_size; + + DCHECK_GE(ChildAvailableSize().inline_size, LayoutUnit()); column_inline_size_ = - ResolveUsedColumnInlineSize(content_box_size_.inline_size, Style()); + ResolveUsedColumnInlineSize(ChildAvailableSize().inline_size, Style()); column_inline_progression_ = column_inline_size_ + - ResolveUsedColumnGap(content_box_size_.inline_size, Style()); + ResolveUsedColumnGap(ChildAvailableSize().inline_size, Style()); used_column_count_ = - ResolveUsedColumnCount(content_box_size_.inline_size, Style()); + ResolveUsedColumnCount(ChildAvailableSize().inline_size, Style()); // If we know the block-size of the fragmentainers in an outer fragmentation // context (if any), our columns may be constrained by that, meaning that we @@ -129,7 +127,7 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { container_builder_.SetIsBlockFragmentationContextRoot(); - intrinsic_block_size_ = border_scrollbar_padding_.block_start; + intrinsic_block_size_ = BorderScrollbarPadding().block_start; NGBreakStatus break_status = LayoutChildren(); if (break_status == NGBreakStatus::kNeedsEarlierBreak) { @@ -138,11 +136,13 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { return RelayoutAndBreakEarlier(); } else if (break_status == NGBreakStatus::kBrokeBefore) { // If we want to break before, make sure that we're actually at the start. - DCHECK(!BreakToken()); + DCHECK(!IsResumingLayout(BreakToken())); return container_builder_.Abort(NGLayoutResult::kOutOfFragmentainerSpace); } + intrinsic_block_size_ += BorderScrollbarPadding().block_end; + // 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. @@ -155,22 +155,23 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { LayoutUnit block_size; if (border_box_size.block_size == kIndefiniteSize) { // Get the block size from the contents if it's auto. - block_size = intrinsic_block_size_ + border_scrollbar_padding_.block_end; + block_size = intrinsic_block_size_; } 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_consumed_block_size; } - if (is_constrained_by_outer_fragmentation_context_) { + container_builder_.SetFragmentsTotalBlockSize(previously_consumed_block_size + + block_size); + container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); + + if (ConstraintSpace().HasBlockFragmentation()) { // In addition to establishing one, we're nested inside another // fragmentation context. FinishFragmentation( - ConstraintSpace(), BreakToken(), block_size, intrinsic_block_size_, + Node(), ConstraintSpace(), BreakToken(), BorderPadding(), FragmentainerSpaceAtBfcStart(ConstraintSpace()), &container_builder_); - } else { - container_builder_.SetBlockSize(block_size); - container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); } NGOutOfFlowLayoutPart( @@ -211,7 +212,7 @@ MinMaxSizesResult NGColumnLayoutAlgorithm::ComputeMinMaxSizes( // TODO(mstensho): Need to include spanners. - result.sizes += border_scrollbar_padding_.InlineSum(); + result.sizes += BorderScrollbarPadding().InlineSum(); return result; } @@ -295,7 +296,12 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutChildren() { if (!result) { // Not enough outer fragmentainer space to produce any columns at all. - container_builder_.SetDidBreak(); + + // TODO(mstensho): Explicitly marking that we broke shouldn't be necessary + // here, ideally. But the fragmentation machinery needs this hint in some + // cases. There's probably a break token missing. + container_builder_.SetDidBreakSelf(); + if (intrinsic_block_size_) { // We have preceding initial border/padding, or a column spanner // (possibly preceded by other spanners or even column content). So we @@ -370,6 +376,9 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutChildren() { // resuming. container_builder_.SetHasSeenAllChildren(); + // TODO(mstensho): Truncate the child margin if it overflows the + // fragmentainer, by using AdjustedMarginAfterFinalChildFragment(). + intrinsic_block_size_ += margin_strut.Sum(); } @@ -379,7 +388,7 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutChildren() { scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( const NGBlockBreakToken* next_column_token, NGMarginStrut* margin_strut) { - LogicalSize column_size(column_inline_size_, content_box_size_.block_size); + LogicalSize column_size(column_inline_size_, column_block_size_); // If block-size is non-auto, subtract the space for content we've consumed in // previous fragments. This is necessary when we're nested inside another @@ -462,7 +471,7 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( // preceding columns in this row and there are also no preceding rows. bool is_first_fragmentainer = !column_break_token && !BreakToken(); - LayoutUnit column_inline_offset(border_scrollbar_padding_.inline_start); + LayoutUnit column_inline_offset(BorderScrollbarPadding().inline_start); int actual_column_count = 0; int forced_break_count = 0; @@ -526,7 +535,6 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( if (zero_outer_space_left) return nullptr; - container_builder_.SetDidBreak(); container_builder_.SetBreakAppeal(kBreakAppealPerfect); break; } @@ -535,52 +543,68 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( } while (column_break_token); // TODO(mstensho): Nested column balancing. - if (container_builder_.DidBreak()) + if (container_builder_.DidBreakSelf()) break; - if (!balance_columns && result->ColumnSpanner()) { - // We always have to balance columns preceding a spanner, so if we didn't - // do that initially, switch over to column balancing mode now, and lay - // out again. - balance_columns = true; - new_columns.clear(); - column_size.block_size = - CalculateBalancedColumnBlockSize(column_size, next_column_token); - continue; + if (!balance_columns) { + if (result->ColumnSpanner()) { + // We always have to balance columns preceding a spanner, so if we + // didn't do that initially, switch over to column balancing mode now, + // and lay out again. + balance_columns = true; + new_columns.clear(); + column_size.block_size = + CalculateBalancedColumnBlockSize(column_size, next_column_token); + continue; + } + + // Balancing not enabled. We're done. + 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. + // We're balancing columns. Check if the column block-size that we laid out + // with was satisfactory. If not, stretch and retry, if possible. // - // We can only stretch the columns if we have at least one column that could - // take more content, and we also need to know the stretch amount (minimal - // space shortage). We need at least one soft break opportunity to do - // this. If forced breaks cause too many breaks, there's no stretch amount - // that could prevent the actual column count from overflowing. + // If we overflowed (actual column count larger than what we have room for), + // see if we're able to stretch them. We can only stretch the columns if we + // have at least one column that could take more content. // + // If we didn't exceed used column-count, we're done. + if (actual_column_count <= used_column_count_) + break; + + // We're in a situation where we'd like to stretch the columns, but then we + // need to know the stretch amount (minimal space shortage). + if (minimal_space_shortage == LayoutUnit::Max()) + break; + + // We also need at least one soft break opportunity. If forced breaks cause + // too many breaks, there's no stretch amount that could prevent the columns + // from overflowing. + if (actual_column_count <= forced_break_count + 1) + break; + // TODO(mstensho): Handle this situation also when we're inside another // balanced multicol container, rather than bailing (which we do now, to // avoid infinite loops). If we exhaust the inner column-count in such // cases, that piece of information may have to be propagated to the outer // multicol, and instead stretch there (not here). We have no such mechanism // in place yet. - if (balance_columns && actual_column_count > used_column_count_ && - actual_column_count > forced_break_count + 1 && - minimal_space_shortage != LayoutUnit::Max() && - !ConstraintSpace().IsInsideBalancedColumns()) { - LayoutUnit new_column_block_size = StretchColumnBlockSize( - minimal_space_shortage, column_size.block_size); - - DCHECK_GE(new_column_block_size, column_size.block_size); - if (new_column_block_size > column_size.block_size) { - // Remove column fragments and re-attempt layout with taller columns. - new_columns.clear(); - column_size.block_size = new_column_block_size; - continue; - } - } - break; + if (ConstraintSpace().IsInsideBalancedColumns()) + break; + + LayoutUnit new_column_block_size = + StretchColumnBlockSize(minimal_space_shortage, column_size.block_size); + + // Give up if we cannot get taller columns. The multicol container may have + // a specified block-size preventing taller columns, for instance. + DCHECK_GE(new_column_block_size, column_size.block_size); + if (new_column_block_size <= column_size.block_size) + break; + + // Remove column fragments and re-attempt layout with taller columns. + new_columns.clear(); + column_size.block_size = new_column_block_size; } while (true); bool is_empty = false; @@ -631,7 +655,7 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutSpanner( *spanner_break_token = nullptr; const ComputedStyle& spanner_style = spanner_node.Style(); NGBoxStrut margins = ComputeMarginsFor( - spanner_style, content_box_size_.inline_size, + spanner_style, ChildAvailableSize().inline_size, ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction()); if (break_token) { @@ -693,11 +717,11 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutSpanner( NGFragment fragment(ConstraintSpace().GetWritingMode(), result->PhysicalFragment()); - ResolveInlineMargins(spanner_style, Style(), content_box_size_.inline_size, + ResolveInlineMargins(spanner_style, Style(), ChildAvailableSize().inline_size, fragment.InlineSize(), &margins); LogicalOffset offset( - border_scrollbar_padding_.inline_start + margins.inline_start, + BorderScrollbarPadding().inline_start + margins.inline_start, block_offset); container_builder_.AddResult(*result, offset); @@ -815,7 +839,7 @@ LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize( // Then distribute as many implicit breaks into the content runs as we need. int used_column_count = - ResolveUsedColumnCount(content_box_size_.inline_size, Style()); + ResolveUsedColumnCount(ChildAvailableSize().inline_size, Style()); for (int columns_found = content_runs.size(); columns_found < used_column_count; columns_found++) { // The tallest content run (with all assumed implicit breaks added so far @@ -869,15 +893,15 @@ LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize( // 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(); + LayoutUnit extra = BorderScrollbarPadding().BlockSum(); size += extra; const ComputedStyle& style = Style(); LayoutUnit max = ResolveMaxBlockLength( - ConstraintSpace(), style, border_padding_, style.LogicalMaxHeight(), + ConstraintSpace(), style, BorderPadding(), style.LogicalMaxHeight(), LengthResolvePhase::kLayout); LayoutUnit extent = ResolveMainBlockLength( - ConstraintSpace(), style, border_padding_, style.LogicalHeight(), size, + ConstraintSpace(), style, BorderPadding(), style.LogicalHeight(), size, LengthResolvePhase::kLayout); if (extent != kIndefiniteSize) { // A specified height/width will just constrain the maximum length. @@ -987,8 +1011,8 @@ NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForSpanner( LayoutUnit block_offset) const { NGConstraintSpaceBuilder space_builder( ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true); - space_builder.SetAvailableSize(content_box_size_); - space_builder.SetPercentageResolutionSize(content_box_size_); + space_builder.SetAvailableSize(ChildAvailableSize()); + space_builder.SetPercentageResolutionSize(ChildAvailableSize()); if (ConstraintSpace().HasBlockFragmentation()) { SetupSpaceBuilderForFragmentation(ConstraintSpace(), spanner, block_offset, 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 70735070448..44f402fdb9c 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 @@ -66,7 +66,7 @@ class CORE_EXPORT NGColumnLayoutAlgorithm LayoutUnit ConstrainColumnBlockSize(LayoutUnit size) const; LayoutUnit CurrentContentBlockOffset() const { - return intrinsic_block_size_ - border_scrollbar_padding_.block_start; + return intrinsic_block_size_ - BorderScrollbarPadding().block_start; } // Finalize layout after breaking before column contents. @@ -97,18 +97,10 @@ class CORE_EXPORT NGColumnLayoutAlgorithm // When set, this will specify where to break before or inside. const NGEarlyBreak* early_break_ = nullptr; - // Border + padding sum, resolved from the node's computed style. - const NGBoxStrut border_padding_; - - // Border + scrollbar + padding sum for the fragment to be generated (most - // importantly, for non-first fragments, leading block border + scrollbar + - // padding is zero). - NGBoxStrut border_scrollbar_padding_; - - LogicalSize content_box_size_; int used_column_count_; LayoutUnit column_inline_size_; LayoutUnit column_inline_progression_; + LayoutUnit column_block_size_; LayoutUnit intrinsic_block_size_; bool is_constrained_by_outer_fragmentation_context_ = false; 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 89e5c4de416..cbb3aa027fb 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 @@ -2053,7 +2053,9 @@ TEST_F(NGColumnLayoutAlgorithmTest, UnsatisfiableOrphansAndWidows) { EXPECT_EQ(expectation, dump); } -TEST_F(NGColumnLayoutAlgorithmTest, WidowsAndAbspos) { +// TODO(1079031): Re-enable once layout for fragmented positioned elements is +// complete. +TEST_F(NGColumnLayoutAlgorithmTest, DISABLED_WidowsAndAbspos) { SetBodyInnerHTML(R"HTML( <style> #parent { @@ -2719,7 +2721,8 @@ TEST_F(NGColumnLayoutAlgorithmTest, MinMax) { NGColumnLayoutAlgorithm algorithm({node, fragment_geometry, space}); base::Optional<MinMaxSizes> sizes; MinMaxSizesInput zero_input( - /* percentage_resolution_block_size */ (LayoutUnit())); + /* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent); // Both column-count and column-width set. style->SetColumnCount(3); @@ -4327,7 +4330,9 @@ TEST_F(NGColumnLayoutAlgorithmTest, NestedWithTallSpanner) { EXPECT_EQ(expectation, dump); } -TEST_F(NGColumnLayoutAlgorithmTest, AbsposFitsInOneColumn) { +// TODO(1079031): Re-enable once layout for fragmented positioned elements is +// complete. +TEST_F(NGColumnLayoutAlgorithmTest, DISABLED_AbsposFitsInOneColumn) { SetBodyInnerHTML(R"HTML( <div id="container"> <div style="columns:3; width:320px; height:100px; column-gap:10px; column-fill:auto;"> 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 6b49934f15d..069997ac3e0 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 @@ -460,6 +460,16 @@ class CORE_EXPORT NGConstraintSpace final { return HasRareData() && rare_data_->is_restricted_block_size_table_cell; } + // The amount of available space for block-start side annotation. + // For the first box, this is the padding-block-start value of the container. + // Otherwise, this comes from NGLayoutResult::BlockEndAnnotationSpace(). + // If the value is negative, it's block-end annotation overflow of the + // previous box. + LayoutUnit BlockStartAnnotationSpace() const { + return HasRareData() ? rare_data_->BlockStartAnnotationSpace() + : LayoutUnit(); + } + NGMarginStrut MarginStrut() const { return HasRareData() ? rare_data_->MarginStrut() : NGMarginStrut(); } @@ -676,6 +686,7 @@ class CORE_EXPORT NGConstraintSpace final { : percentage_resolution_size(other.percentage_resolution_size), replaced_percentage_resolution_block_size( other.replaced_percentage_resolution_block_size), + block_start_annotation_space(other.block_start_annotation_space), bfc_offset(other.bfc_offset), fragmentainer_block_size(other.fragmentainer_block_size), fragmentainer_offset_at_bfc(other.fragmentainer_offset_at_bfc), @@ -784,6 +795,14 @@ class CORE_EXPORT NGConstraintSpace final { return stretch_data_.IsInitialForMaySkipLayout(); } + LayoutUnit BlockStartAnnotationSpace() const { + return block_start_annotation_space; + } + + void SetBlockStartAnnotationSpace(LayoutUnit space) { + block_start_annotation_space = space; + } + NGMarginStrut MarginStrut() const { return data_union_type == kBlockData ? block_data_.margin_strut : NGMarginStrut(); @@ -903,6 +922,7 @@ class CORE_EXPORT NGConstraintSpace final { LogicalSize percentage_resolution_size; LayoutUnit replaced_percentage_resolution_block_size; + LayoutUnit block_start_annotation_space; NGBfcOffset bfc_offset; LayoutUnit fragmentainer_block_size = kIndefiniteSize; 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 30655178c30..5da0ad3a734 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 @@ -215,6 +215,11 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { space_.bitfields_.cache_slot = static_cast<unsigned>(slot); } + void SetBlockStartAnnotationSpace(LayoutUnit space) { + if (space) + space_.EnsureRareData()->SetBlockStartAnnotationSpace(space); + } + void SetMarginStrut(const NGMarginStrut& margin_strut) { #if DCHECK_IS_ON() DCHECK(!is_margin_strut_set_); 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 2be126bca70..02912fb434a 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,6 +7,7 @@ #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_physical_box_fragment.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" @@ -75,11 +76,40 @@ void NGContainerFragmentBuilder::PropagateChildData( IsInlineContainerForNode(descendant.node, inline_container)) new_inline_container = inline_container; + // |oof_positioned_candidates_| should not have duplicated entries. + DCHECK(std::none_of( + oof_positioned_candidates_.begin(), oof_positioned_candidates_.end(), + [&descendant](const NGLogicalOutOfFlowPositionedNode& node) { + return node.node == descendant.node; + })); oof_positioned_candidates_.emplace_back(descendant.node, static_position, new_inline_container); } } + if (const NGPhysicalBoxFragment* fragment = + DynamicTo<NGPhysicalBoxFragment>(&child)) { + if (fragment->HasOutOfFlowPositionedFragmentainerDescendants()) { + const auto& out_of_flow_fragmentainer_descendants = + fragment->OutOfFlowPositionedFragmentainerDescendants(); + + for (const auto& descendant : out_of_flow_fragmentainer_descendants) { + const NGPhysicalContainerFragment* containing_block_fragment = + descendant.containing_block_fragment.get(); + if (!containing_block_fragment) + containing_block_fragment = fragment; + + NGLogicalStaticPosition static_position = + descendant.static_position.ConvertToLogical( + GetWritingMode(), Direction(), PhysicalSize()); + oof_positioned_fragmentainer_descendants_.emplace_back( + descendant.node, static_position, descendant.inline_container, + /* needs_block_offset_adjustment */ false, + containing_block_fragment); + } + } + } + // 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() || @@ -128,10 +158,9 @@ void NGContainerFragmentBuilder::PropagateChildData( // Compute |has_floating_descendants_for_paint_| to optimize tree traversal // in paint. if (!has_floating_descendants_for_paint_) { - // TODO(layout-dev): The |NGPhysicalFragment::IsAtomicInline| check should - // be checking for any children which paint all phases atomically. if (child.IsFloating() || child.IsLegacyLayoutRoot() || - (child.HasFloatingDescendantsForPaint() && !child.IsAtomicInline())) + (child.HasFloatingDescendantsForPaint() && + !child.IsPaintedAtomically())) has_floating_descendants_for_paint_ = true; } @@ -219,6 +248,11 @@ void NGContainerFragmentBuilder::AddOutOfFlowInlineChildCandidate( NGLogicalStaticPosition::kBlockStart); } +void NGContainerFragmentBuilder::AddOutOfFlowFragmentainerDescendant( + const NGLogicalOutOfFlowPositionedNode& descendant) { + oof_positioned_fragmentainer_descendants_.push_back(descendant); +} + void NGContainerFragmentBuilder::AddOutOfFlowDescendant( const NGLogicalOutOfFlowPositionedNode& descendant) { oof_positioned_descendants_.push_back(descendant); @@ -251,6 +285,13 @@ void NGContainerFragmentBuilder::SwapOutOfFlowPositionedCandidates( has_oof_candidate_that_needs_block_offset_adjustment_ = false; } +void NGContainerFragmentBuilder::SwapOutOfFlowFragmentainerDescendants( + Vector<NGLogicalOutOfFlowPositionedNode>* descendants) { + DCHECK(descendants->IsEmpty()); + DCHECK(!has_oof_candidate_that_needs_block_offset_adjustment_); + std::swap(oof_positioned_fragmentainer_descendants_, *descendants); +} + void NGContainerFragmentBuilder:: MoveOutOfFlowDescendantCandidatesToDescendants() { DCHECK(oof_positioned_descendants_.IsEmpty()); 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 4edb57dcf10..1b007aafac9 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 @@ -126,16 +126,26 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { const LogicalOffset& child_offset, TextDirection inline_container_direction); + void AddOutOfFlowFragmentainerDescendant( + const NGLogicalOutOfFlowPositionedNode& descendant); + void AddOutOfFlowDescendant( const NGLogicalOutOfFlowPositionedNode& descendant); void SwapOutOfFlowPositionedCandidates( Vector<NGLogicalOutOfFlowPositionedNode>* candidates); + void SwapOutOfFlowFragmentainerDescendants( + Vector<NGLogicalOutOfFlowPositionedNode>* descendants); + bool HasOutOfFlowPositionedCandidates() const { return !oof_positioned_candidates_.IsEmpty(); } + bool HasOutOfFlowFragmentainerDescendants() const { + return !oof_positioned_fragmentainer_descendants_.IsEmpty(); + } + // This method should only be used within the inline layout algorithm. It is // used to convert all OOF-positioned candidates to descendants. // @@ -173,6 +183,20 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { is_fragmentation_context_root_ = true; } + bool IsBlockFragmentationContextRoot() const { + return is_fragmentation_context_root_; + } + + // See NGLayoutResult::AnnotationOverflow(). + void SetAnnotationOverflow(LayoutUnit overflow) { + annotation_overflow_ = overflow; + } + + // See NGLayoutRsult::BlockEndAnnotatioSpace(). + void SetBlockEndAnnotationSpace(LayoutUnit space) { + block_end_annotation_space_ = space; + } + const NGConstraintSpace* ConstraintSpace() const { return space_; } #if DCHECK_IS_ON() @@ -187,9 +211,8 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { NGContainerFragmentBuilder(NGLayoutInputNode node, scoped_refptr<const ComputedStyle> style, const NGConstraintSpace* space, - WritingMode writing_mode, - TextDirection direction) - : NGFragmentBuilder(std::move(style), writing_mode, direction), + WritingDirectionMode writing_direction) + : NGFragmentBuilder(std::move(style), writing_direction), node_(node), space_(space) { layout_object_ = node.GetLayoutBox(); @@ -211,6 +234,8 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { NGExclusionSpace exclusion_space_; Vector<NGLogicalOutOfFlowPositionedNode> oof_positioned_candidates_; + Vector<NGLogicalOutOfFlowPositionedNode> + oof_positioned_fragmentainer_descendants_; Vector<NGLogicalOutOfFlowPositionedNode> oof_positioned_descendants_; NGUnpositionedListMarker unpositioned_list_marker_; @@ -225,6 +250,11 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { scoped_refptr<const NGEarlyBreak> early_break_; NGBreakAppeal break_appeal_ = kBreakAppealLastResort; + // See NGLayoutResult::AnnotationOverflow(). + LayoutUnit annotation_overflow_; + // See NGLayoutResult::BlockEndAnotationSpace(). + LayoutUnit block_end_annotation_space_; + NGAdjoiningObjectTypes adjoining_object_types_ = kAdjoiningNone; bool has_adjoining_object_descendants_ = false; 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 2e117e138f2..17ddb935ff0 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 @@ -23,10 +23,9 @@ NGFieldsetLayoutAlgorithm::NGFieldsetLayoutAlgorithm( const NGLayoutAlgorithmParams& params) : NGLayoutAlgorithm(params), writing_mode_(ConstraintSpace().GetWritingMode()), - border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding), consumed_block_size_(BreakToken() ? BreakToken()->ConsumedBlockSize() : LayoutUnit()) { + DCHECK(params.fragment_geometry.scrollbar.IsEmpty()); container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); @@ -38,21 +37,22 @@ NGFieldsetLayoutAlgorithm::NGFieldsetLayoutAlgorithm( // Leading border and padding should only apply to the first fragment. We // don't adjust the value of border_padding_ itself so that it can be used // when calculating the block size of the last fragment. - adjusted_border_padding_ = border_padding_; + adjusted_border_padding_ = BorderPadding(); AdjustForFragmentation(BreakToken(), &adjusted_border_padding_); } scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // Layout of a fieldset container consists of two parts: Create a child // fragment for the rendered legend (if any), and create a child fragment for - // the fieldset contents anonymous box (if any). Fieldset scrollbars and - // padding will not be applied to the fieldset container itself, but rather to - // the fieldset contents anonymous child box. The reason for this is that the - // rendered legend shouldn't be part of the scrollport; the legend is - // essentially a part of the block-start border, and should not scroll along - // 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. + // the fieldset contents anonymous box (if any). + // Fieldset scrollbars and padding will not be applied to the fieldset + // container itself, but rather to the fieldset contents anonymous child box. + // The reason for this is that the rendered legend shouldn't be part of the + // scrollport; the legend is essentially a part of the block-start border, + // and should not scroll along 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. // Calculate the amount of the border block-start that was consumed in // previous fragments. @@ -72,7 +72,7 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // Recompute the block-axis size now that we know our content size. border_box_size_.block_size = - ComputeBlockSizeForFragment(ConstraintSpace(), Style(), border_padding_, + ComputeBlockSizeForFragment(ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size_ + consumed_block_size_, border_box_size_.inline_size); @@ -93,16 +93,16 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // TODO(almaher): end border and padding may overflow the parent // fragmentainer, and we should avoid that. - LayoutUnit block_size = border_box_size_.block_size - consumed_block_size_; + LayoutUnit all_fragments_block_size = border_box_size_.block_size; + container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); + container_builder_.SetFragmentsTotalBlockSize(all_fragments_block_size); container_builder_.SetIsFieldsetContainer(); - if (ConstraintSpace().HasKnownFragmentainerBlockSize()) { + + if (ConstraintSpace().HasBlockFragmentation()) { FinishFragmentation( - ConstraintSpace(), BreakToken(), block_size, intrinsic_block_size_, + Node(), ConstraintSpace(), BreakToken(), BorderPadding(), FragmentainerSpaceAtBfcStart(ConstraintSpace()), &container_builder_); - } else { - container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); - container_builder_.SetBlockSize(block_size); } NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_, @@ -158,7 +158,7 @@ NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutChildren() { NGBoxStrut borders_with_legend = borders_; borders_with_legend.block_start = intrinsic_block_size_; LogicalSize adjusted_padding_box_size = - ShrinkAvailableSize(border_box_size_, borders_with_legend); + ShrinkLogicalSize(border_box_size_, borders_with_legend); if (adjusted_padding_box_size.block_size != kIndefiniteSize) { // If intrinsic_block_size_ does not include the border block-start that was @@ -226,10 +226,8 @@ NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutLegend( // 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. - LogicalSize content_box_size = - ShrinkAvailableSize(border_box_size_, adjusted_border_padding_); - LogicalSize percentage_size = - CalculateChildPercentageSize(ConstraintSpace(), Node(), content_box_size); + LogicalSize percentage_size = CalculateChildPercentageSize( + ConstraintSpace(), Node(), ChildAvailableSize()); NGBoxStrut legend_margins = ComputeMarginsFor( legend.Style(), percentage_size.inline_size, ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction()); @@ -243,7 +241,7 @@ NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutLegend( LayoutUnit block_offset = legend_margins.block_start; do { auto legend_space = CreateConstraintSpaceForLegend( - legend, content_box_size, percentage_size, block_offset); + legend, ChildAvailableSize(), percentage_size, block_offset); result = legend.Layout(legend_space, legend_break_token.get()); // TODO(layout-dev): Handle abortions caused by block fragmentation. @@ -277,8 +275,16 @@ NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutLegend( } LayoutUnit legend_margin_box_block_size = - NGFragment(writing_mode_, physical_fragment).BlockSize() + - legend_margins.BlockSum(); + legend_margins.block_start + + NGFragment(writing_mode_, physical_fragment).BlockSize(); + + LayoutUnit block_end_margin = legend_margins.block_end; + if (ConstraintSpace().HasKnownFragmentainerBlockSize()) { + block_end_margin = AdjustedMarginAfterFinalChildFragment( + ConstraintSpace(), legend_margin_box_block_size, block_end_margin); + } + legend_margin_box_block_size += block_end_margin; + LayoutUnit space_left = borders_.block_start - legend_margin_box_block_size; if (space_left > LayoutUnit()) { @@ -303,6 +309,8 @@ NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutLegend( // the size of the legend instead of the border. intrinsic_block_size_ = legend_margin_box_block_size; + is_legend_past_border_ = true; + // Don't adjust the block-start offset of the fragment border if it broke. if (BreakToken() || (ConstraintSpace().HasKnownFragmentainerBlockSize() && legend_margin_box_block_size > @@ -345,11 +353,12 @@ NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutFieldsetContent( NGBreakStatus break_status = NGBreakStatus::kContinue; if (ConstraintSpace().HasBlockFragmentation()) { + bool has_container_separation = is_legend_past_border_; // TODO(almaher): The legend should be treated as out-of-flow. break_status = BreakBeforeChildIfNeeded( ConstraintSpace(), fieldset_content, *result.get(), ConstraintSpace().FragmentainerOffsetAtBfc() + intrinsic_block_size_, - /*has_container_separation*/ has_legend, &container_builder_); + has_container_separation, &container_builder_); EBreakBetween break_after = JoinFragmentainerBreakValues( result->FinalBreakAfter(), fieldset_content.Style().BreakAfter()); container_builder_.SetPreviousBreakAfter(break_after); 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 a6a6d9996a0..57fc9dfefa7 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 @@ -52,7 +52,6 @@ class CORE_EXPORT NGFieldsetLayoutAlgorithm const WritingMode writing_mode_; - const NGBoxStrut border_padding_; NGBoxStrut borders_; NGBoxStrut padding_; @@ -76,6 +75,13 @@ class CORE_EXPORT NGFieldsetLayoutAlgorithm // If true, this indicates that the legend broke during the current layout // pass. bool legend_broke_ = false; + + // If true, the legend is taller than the block-start border, so that it + // sticks below it, allowing for a class C breakpoint [1] before any fieldset + // content. + // + // [1] https://www.w3.org/TR/css-break-3/#possible-breaks + bool is_legend_past_border_ = false; }; } // 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 8f88b0d0ee2..1ced64ad3c0 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 @@ -45,7 +45,8 @@ class NGFieldsetLayoutAlgorithmTest NGFieldsetLayoutAlgorithm algorithm({node, fragment_geometry, space}); MinMaxSizesInput input( - /* percentage_resolution_block_size */ (LayoutUnit())); + /* percentage_resolution_block_size */ LayoutUnit(), + MinMaxSizesType::kContent); return algorithm.ComputeMinMaxSizes(input).sizes; } @@ -1905,6 +1906,60 @@ TEST_F(NGFieldsetLayoutAlgorithmTest, SmallerLegendLargeBorderFragmentation) { } // Tests that a fieldset with a large border and a small legend fragment +// correctly. In this case, since the legend doesn't stick below the block-start +// border, there's no class C breakpoint before the fieldset contents. +// Therefore, prefer breaking before the fieldset to breaking before the child +// DIV. +TEST_F(NGFieldsetLayoutAlgorithmTest, SmallerLegendLargeBorderFragmentation2) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { margin:0; border:30px solid; padding:0px; width:100px; } + #legend { padding:0; width:10px; height:5px; } + </style> + <div id="container" style="width:300px;"> + <div style="width:33px; height:70px;"></div> + <fieldset id="fieldset"> + <legend id="legend"></legend> + <div style="width:44px; height:30px; break-inside:avoid;"></div> + </fieldset> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:300x100 + offset:0,0 size:33x70 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + node, space, fragment->BreakToken()); + EXPECT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:300x90 + offset:0,0 size:160x90 + offset:30,12.5 size:10x5 + offset:30,30 size:100x30 + offset:0,0 size:44x30 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a large border and a small legend fragment // correctly. In this case, the legend block offset is not adjusted because the // legend breaks after attempting to adjust the offset. TEST_F(NGFieldsetLayoutAlgorithmTest, SmallerLegendLargeBorderWithBreak) { 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 f772d4d8bbe..46cced99c7d 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 @@ -196,9 +196,9 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, NGExclusionSpace* exclusion_space) { DCHECK(unpositioned_float); const NGConstraintSpace& parent_space = unpositioned_float->parent_space; + NGBlockNode node = unpositioned_float->node; bool is_same_writing_mode = - unpositioned_float->node.Style().GetWritingMode() == - parent_space.GetWritingMode(); + node.Style().GetWritingMode() == parent_space.GetWritingMode(); bool is_fragmentable = is_same_writing_mode && parent_space.HasBlockFragmentation(); @@ -206,6 +206,7 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, scoped_refptr<const NGLayoutResult> layout_result; NGBoxStrut fragment_margins; NGLayoutOpportunity opportunity; + bool need_break_before = false; if (!is_fragmentable) { // We may be able to re-use the fragment from when we calculated the @@ -223,8 +224,7 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, float_fragment.InlineSize()); } else { fragment_margins = ComputeMarginsFor( - unpositioned_float->node.Style(), - unpositioned_float->percentage_size.inline_size, + node.Style(), unpositioned_float->percentage_size.inline_size, parent_space.GetWritingMode(), parent_space.Direction()); AdjustForFragmentation(unpositioned_float->token.get(), &fragment_margins); @@ -250,8 +250,7 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, NGConstraintSpace space = CreateConstraintSpaceForFloat( *unpositioned_float, fragmentainer_delta); - layout_result = unpositioned_float->node.Layout( - space, unpositioned_float->token.get()); + layout_result = node.Layout(space, unpositioned_float->token.get()); // If we knew the right block-offset up front, we're done. if (!optimistically_placed) @@ -282,9 +281,42 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, break; } while (true); - if (const NGBreakToken* break_token = - layout_result->PhysicalFragment().BreakToken()) - fragment_margins.block_end = LayoutUnit(); + LayoutUnit fragmentainer_margin_edge_block_offset = + parent_space.FragmentainerOffsetAtBfc() + + opportunity.rect.start_offset.block_offset; + + // Note that we don't check if we're at a valid class A, B or C breakpoint + // (we only check that we're not at the start of the fragmentainer (in which + // case breaking typically wouldn't eliminate the unappealing break inside + // the float)). While no other browsers do this either, we should consider + // doing this in the future. But for now, don't let the float affect the + // appeal of breaking inside this container. + // + // If we're past the fragmentainer start, we can consider breaking before + // this float. Otherwise we cannot, or there'd be no content + // progression. The common fragmentation machinery assumes that margins can + // collapse with fragmentainer boundaries, but this isn't the case for + // floats. We don't allow float margins to collapse with anything, nor be + // split into multiple fragmentainers. Hence this additional check. Note + // that we might want to reconsider this behavior, since browsers disagree + // (what we do now is relatively similar to legacy Blink, though). Should we + // split a margin in cases where it helps prevent fragmentainer overflow? + // Should we always split them if they occur at fragmentainer boundaries? Or + // even allow them to collapse with the fragmentainer boundary? Exact + // behavior is currently unspecified. + if (fragmentainer_margin_edge_block_offset > LayoutUnit()) { + LayoutUnit fragmentainer_block_offset = + fragmentainer_margin_edge_block_offset + fragment_margins.block_start; + if (!MovePastBreakpoint(parent_space, node, *layout_result, + fragmentainer_block_offset, kBreakAppealPerfect, + /* builder */ nullptr)) { + need_break_before = true; + } else if (layout_result->PhysicalFragment().BreakToken()) { + // We need to resume in the next fragmentainer, which means that + // there'll be no block-end margin here. + fragment_margins.block_end = LayoutUnit(); + } + } } NGFragment float_fragment(parent_space.GetWritingMode(), @@ -300,13 +332,25 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, } // Add the float as an exclusion. - scoped_refptr<const NGExclusion> exclusion = - CreateExclusion(float_fragment, float_margin_bfc_offset, fragment_margins, - *unpositioned_float, - unpositioned_float->IsLineRight(parent_space.Direction()) - ? EFloat::kRight - : EFloat::kLeft); - exclusion_space->Add(std::move(exclusion)); + if (need_break_before) { + // Create a special exclusion past everything. This will prevent us from + // adding any more floats in this formatting context to the current + // fragmentainer, and also make clearance behave correctly (e.g. an in-flow + // block with clear:left after a float:left that got pushed to the next + // fragmentainer means that the in-flow block also needs to be pushed, while + // if the in-flow block has clear:right, it may still be allowed in the + // current fragmentainer). + NGBfcOffset past_everything(LayoutUnit(), LayoutUnit::Max()); + scoped_refptr<const NGExclusion> exclusion = + NGExclusion::Create(NGBfcRect(past_everything, past_everything), + node.Style().Floating(parent_space.Direction())); + exclusion_space->Add(std::move(exclusion)); + } else { + scoped_refptr<const NGExclusion> exclusion = CreateExclusion( + float_fragment, float_margin_bfc_offset, fragment_margins, + *unpositioned_float, node.Style().Floating(parent_space.Direction())); + exclusion_space->Add(std::move(exclusion)); + } // Adjust the float's bfc_offset to its border-box (instead of margin-box). NGBfcOffset float_bfc_offset( @@ -314,7 +358,8 @@ NGPositionedFloat PositionFloat(NGUnpositionedFloat* unpositioned_float, fragment_margins.LineLeft(parent_space.Direction()), float_margin_bfc_offset.block_offset + fragment_margins.block_start); - return NGPositionedFloat(std::move(layout_result), float_bfc_offset); + return NGPositionedFloat(std::move(layout_result), float_bfc_offset, + need_break_before); } } // namespace blink 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 a1ceb920f02..221149e728d 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 @@ -11,8 +11,7 @@ #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" -#include "third_party/blink/renderer/platform/text/text_direction.h" -#include "third_party/blink/renderer/platform/text/writing_mode.h" +#include "third_party/blink/renderer/platform/text/writing_direction_mode.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" namespace blink { @@ -37,8 +36,13 @@ class CORE_EXPORT NGFragmentBuilder { style_variant_ = style_variant; } - WritingMode GetWritingMode() const { return writing_mode_; } - TextDirection Direction() const { return direction_; } + WritingDirectionMode GetWritingDirection() const { + return writing_direction_; + } + WritingMode GetWritingMode() const { + return writing_direction_.GetWritingMode(); + } + TextDirection Direction() const { return writing_direction_.Direction(); } LayoutUnit InlineSize() const { return size_.inline_size; } LayoutUnit BlockSize() const { return size_.block_size; } @@ -51,30 +55,26 @@ class CORE_EXPORT NGFragmentBuilder { protected: NGFragmentBuilder(scoped_refptr<const ComputedStyle> style, - WritingMode writing_mode, - TextDirection direction) + WritingDirectionMode writing_direction) : style_(std::move(style)), - writing_mode_(writing_mode), - direction_(direction), + writing_direction_(writing_direction), style_variant_(NGStyleVariant::kStandard) { DCHECK(style_); } - NGFragmentBuilder(WritingMode writing_mode, TextDirection direction) - : writing_mode_(writing_mode), direction_(direction) {} + explicit NGFragmentBuilder(WritingDirectionMode writing_direction) + : writing_direction_(writing_direction) {} NGFragmentBuilder(const NGPhysicalFragment& fragment) : style_(&fragment.Style()), - writing_mode_(style_->GetWritingMode()), - direction_(style_->Direction()), + writing_direction_(style_->GetWritingDirection()), style_variant_(fragment.StyleVariant()), - size_(fragment.Size().ConvertToLogical(writing_mode_)), + size_(fragment.Size().ConvertToLogical(GetWritingMode())), layout_object_(fragment.GetMutableLayoutObject()), is_hidden_for_paint_(fragment.IsHiddenForPaint()) {} protected: scoped_refptr<const ComputedStyle> style_; - WritingMode writing_mode_; - TextDirection direction_; + WritingDirectionMode writing_direction_; NGStyleVariant style_variant_; LogicalSize size_; LayoutObject* layout_object_ = nullptr; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc index 6275cba58ee..c3d6fe3245f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc @@ -97,7 +97,7 @@ void NGFragmentChildIterator::UpdateSelfFromFragment( current_.link_.fragment->GetLayoutObject()); current_.break_token_for_fragmentainer_only_ = false; } else if (is_fragmentation_context_root_ && previous_fragment) { - if (previous_fragment->IsColumnBox()) { + if (previous_fragment->IsFragmentainerBox()) { // The outgoing break token from one fragmentainer is the incoming break // token to the next one. This is also true when there are column spanners // between two columns (fragmentainers); the outgoing break token from the @@ -112,8 +112,10 @@ void NGFragmentChildIterator::UpdateSelfFromFragment( // rendered legend. We'll leave |current_block_break_token_| alone here, // as it will be used as in incoming break token when we get to the next // column. + // TODO(almaher): Remove check for out of flow. DCHECK( previous_fragment->IsRenderedLegend() || + previous_fragment->IsOutOfFlowPositioned() || NGBlockNode(ToLayoutBox(previous_fragment->GetMutableLayoutObject())) .IsColumnSpanAll()); @@ -129,28 +131,7 @@ void NGFragmentChildIterator::UpdateSelfFromFragment( bool NGFragmentChildIterator::AdvanceWithCursor() { DCHECK(current_.cursor_); - const NGFragmentItem* item = current_.cursor_->CurrentItem(); - if (item->HasChildren()) { - // If we're advancing past a non-atomic inline, we also need to advance past - // any break tokens for fragments in there. - for (wtf_size_t remaining = item->DescendantsCount(); remaining; - remaining--) { - if (item->IsFloating()) { - SkipToBlockBreakToken(); - if (child_break_token_idx_ < child_break_tokens_.size()) { - DCHECK_EQ(child_break_tokens_[child_break_token_idx_] - ->InputNode() - .GetLayoutBox(), - item->GetLayoutObject()); - child_break_token_idx_++; - } - } - current_.cursor_->MoveToNext(); - item = current_.cursor_->CurrentItem(); - } - } else { - current_.cursor_->MoveToNext(); - } + current_.cursor_->MoveToNextSkippingChildren(); UpdateSelfFromCursor(); if (current_.cursor_->CurrentItem()) return true; @@ -175,25 +156,6 @@ void NGFragmentChildIterator::UpdateSelfFromCursor() { return; } current_.link_ = {item->BoxFragment(), item->OffsetInContainerBlock()}; - if (!current_.link_.fragment || !current_.link_.fragment->IsFloating()) { - DCHECK(!current_.link_.fragment || - current_.link_.fragment->GetLayoutObject()->IsInline()); - return; - } - if (!parent_break_token_) - return; - // Floats may fragment, in which case there's a designated break token for - // them. - SkipToBlockBreakToken(); - if (child_break_token_idx_ >= child_break_tokens_.size()) { - current_.block_break_token_ = nullptr; - return; - } - current_.block_break_token_ = - To<NGBlockBreakToken>(child_break_tokens_[child_break_token_idx_]); - DCHECK(!current_.link_.fragment->GetLayoutObject() || - current_.block_break_token_->InputNode().GetLayoutBox() == - current_.link_.fragment->GetLayoutObject()); } void NGFragmentChildIterator::SkipToBoxFragment() { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h index c4927f0acad..5f295d91867 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h @@ -89,7 +89,7 @@ class CORE_EXPORT NGFragmentChildIterator { // be used by a subsequent fragmentainer. Other fragment types (such as // column spanners) need to ignore it. if (break_token_for_fragmentainer_only_ && - !link_.fragment->IsColumnBox()) + !link_.fragment->IsFragmentainerBox()) return nullptr; } return block_break_token_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc index a0ddc40690f..db9a91cc8b3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc @@ -140,7 +140,7 @@ TEST_F(NGFragmentationTest, MultipleFragmentsNestedMulticol) { SetBodyInnerHTML(R"HTML( <div id="container"> <div id="outer_multicol" style="columns:3; column-fill:auto; height:100px; width:620px; column-gap:10px;"> - <div id="inner_multicol" style="columns:2;"> + <div id="inner_multicol" style="columns:2; column-fill:auto;"> <div id="child1" style="width:11px; height:350px;"></div> <div id="child2" style="width:22px; height:350px;"></div> </div> @@ -193,5 +193,54 @@ TEST_F(NGFragmentationTest, MultipleFragmentsNestedMulticol) { EXPECT_EQ(child2->GetPhysicalFragment(3)->Size(), PhysicalSize(22, 100)); } +TEST_F(NGFragmentationTest, HasSeenAllChildrenIfc) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div style="columns:3; column-fill:auto; height:50px; line-height:20px; orphans:1; widows:1;"> + <div id="ifc" style="height:300px;"> + <br><br> + <br><br> + <br><br> + <br> + </div> + </div> + </div> + )HTML"); + + RunBlockLayoutAlgorithm(GetElementById("container")); + + const LayoutBox* ifc = ToLayoutBox(GetLayoutObjectByElementId("ifc")); + ASSERT_EQ(ifc->PhysicalFragmentCount(), 6u); + const NGPhysicalBoxFragment* fragment = ifc->GetPhysicalFragment(0); + const NGBlockBreakToken* break_token = + DynamicTo<NGBlockBreakToken>(fragment->BreakToken()); + ASSERT_TRUE(break_token); + EXPECT_FALSE(break_token->HasSeenAllChildren()); + + fragment = ifc->GetPhysicalFragment(1); + break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken()); + ASSERT_TRUE(break_token); + EXPECT_FALSE(break_token->HasSeenAllChildren()); + + fragment = ifc->GetPhysicalFragment(2); + break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken()); + ASSERT_TRUE(break_token); + EXPECT_FALSE(break_token->HasSeenAllChildren()); + + fragment = ifc->GetPhysicalFragment(3); + break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken()); + ASSERT_TRUE(break_token); + EXPECT_TRUE(break_token->HasSeenAllChildren()); + + fragment = ifc->GetPhysicalFragment(4); + break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken()); + ASSERT_TRUE(break_token); + EXPECT_TRUE(break_token->HasSeenAllChildren()); + + fragment = ifc->GetPhysicalFragment(5); + break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken()); + EXPECT_FALSE(break_token); +} + } // anonymous namespace } // namespace blink 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 abc1aa6fc79..92343c933f1 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 @@ -10,6 +10,7 @@ #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" +#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/style/computed_style.h" @@ -209,30 +210,101 @@ void SetupFragmentBuilderForFragmentation( builder->SetSequenceNumber(sequence_number); } -void FinishFragmentation(const NGConstraintSpace& space, +bool IsNodeFullyGrown(NGBlockNode node, + const NGConstraintSpace& space, + LayoutUnit current_total_block_size, + const NGBoxStrut& border_padding, + LayoutUnit inline_size) { + // Pass an "infinite" intrinsic size to see how the block-size is + // constrained. If it doesn't affect the block size, it means that the node + // cannot grow any further. + LayoutUnit max_block_size = ComputeBlockSizeForFragment( + space, node.Style(), border_padding, LayoutUnit::Max(), inline_size); + DCHECK_GE(max_block_size, current_total_block_size); + return max_block_size == current_total_block_size; +} + +void FinishFragmentation(NGBlockNode node, + const NGConstraintSpace& space, const NGBlockBreakToken* previous_break_token, - LayoutUnit block_size, - LayoutUnit intrinsic_block_size, + const NGBoxStrut& border_padding, LayoutUnit space_left, NGBoxFragmentBuilder* builder) { LayoutUnit previously_consumed_block_size; if (previous_break_token && !previous_break_token->IsBreakBefore()) previously_consumed_block_size = previous_break_token->ConsumedBlockSize(); - if (builder->DidBreak()) { - // One of our children broke. Even if we fit within the remaining space, we - // need to prepare a break token. - builder->SetConsumedBlockSize(std::min(space_left, block_size) + - previously_consumed_block_size); - builder->SetBlockSize(std::min(space_left, block_size)); - builder->SetIntrinsicBlockSize(space_left); + LayoutUnit fragments_total_block_size = builder->FragmentsTotalBlockSize(); + LayoutUnit wanted_block_size = + fragments_total_block_size - previously_consumed_block_size; + DCHECK_GE(wanted_block_size, LayoutUnit()); + + LayoutUnit final_block_size = wanted_block_size; + if (space_left != kIndefiniteSize) + final_block_size = std::min(final_block_size, space_left); + builder->SetConsumedBlockSize(previously_consumed_block_size + + final_block_size); + builder->SetFragmentBlockSize(final_block_size); + + if (space_left == kIndefiniteSize) { + // We don't know how space is available (initial column balancing pass), so + // we won't break. + builder->SetIsAtBlockEnd(); return; } - if (block_size > space_left) { - // Need a break inside this block. - builder->SetConsumedBlockSize(space_left + previously_consumed_block_size); - builder->SetDidBreak(); + if (builder->HasChildBreakInside()) { + // We broke before or inside one of our children. Even if we fit within the + // remaining space, and even if the child involved in the break were to be + // in a parallel flow, we still need to prepare a break token for this node, + // so that we can resume layout of its broken or unstarted children in the + // next fragmentainer. + // + // If we're at the end of the node, we need to mark the outgoing break token + // as such. This is a way for the parent algorithm to determine whether we + // need to insert a break there, or whether we may continue with any sibling + // content. If we are allowed to continue, while there's still child content + // left to be laid out, said content ends up in a parallel flow. + // https://www.w3.org/TR/css-break-3/#parallel-flows + // + // TODO(mstensho): The spec actually says that we enter a parallel flow once + // we're past the block-end *content edge*, but here we're checking against + // the *border edge* instead. Does it matter? + if (previous_break_token && previous_break_token->IsAtBlockEnd()) { + builder->SetIsAtBlockEnd(); + // We entered layout already at the end of the block (but with overflowing + // children). So we should take up no more space on our own. + DCHECK_EQ(wanted_block_size, LayoutUnit()); + } else if (wanted_block_size <= space_left) { + // We have room for the calculated block-size in the current + // fragmentainer, but we need to figure out whether this node is going to + // produce more non-zero block-size fragments or not. + // + // If the block-size is constrained / fixed (in which case + // IsNodeFullyGrown() will return true now), we know that we're at the + // end. If block-size is unconstrained (or at least allowed to grow a bit + // more), we're only at the end if no in-flow content inside broke. + if (!builder->HasInflowChildBreakInside() || + IsNodeFullyGrown(node, space, fragments_total_block_size, + border_padding, + builder->InitialBorderBoxSize().inline_size)) + builder->SetIsAtBlockEnd(); + + // If we're going to break just because of floats or out-of-flow child + // breaks, no break appeal will have been recorded so far, since we only + // update the appeal at same-flow breakpoints, and since we start off by + // assuming the lowest appeal, upgrade it now. There's nothing here that + // makes breaking inside less appealing than perfect. + if (!builder->HasInflowChildBreakInside()) + builder->SetBreakAppeal(kBreakAppealPerfect); + } + return; + } + + if (wanted_block_size > space_left) { + // No child inside broke, but we need a break inside this block anyway, due + // to its size. + builder->SetDidBreakSelf(); NGBreakAppeal break_appeal = kBreakAppealPerfect; if (!previously_consumed_block_size) { // This is the first fragment generated for the node. Avoid breaking @@ -246,18 +318,14 @@ void FinishFragmentation(const NGConstraintSpace& space, break_appeal = kBreakAppealLastResort; } builder->SetBreakAppeal(break_appeal); - builder->SetBlockSize(space_left); - builder->SetIntrinsicBlockSize(space_left); if (space.BlockFragmentationType() == kFragmentColumn && !space.IsInitialColumnBalancingPass()) - builder->PropagateSpaceShortage(block_size - space_left); + builder->PropagateSpaceShortage(wanted_block_size - space_left); return; } // The end of the block fits in the current fragmentainer. - builder->SetConsumedBlockSize(previously_consumed_block_size + block_size); - builder->SetBlockSize(block_size); - builder->SetIntrinsicBlockSize(intrinsic_block_size); + builder->SetIsAtBlockEnd(); } NGBreakStatus BreakBeforeChildIfNeeded(const NGConstraintSpace& space, @@ -386,7 +454,7 @@ bool MovePastBreakpoint(const NGConstraintSpace& space, NGFragment fragment(space.GetWritingMode(), physical_fragment); if (!space.HasKnownFragmentainerBlockSize()) { - if (space.IsInitialColumnBalancingPass()) { + if (space.IsInitialColumnBalancingPass() && builder) { if (child.IsMonolithic() || (child.IsBlock() && IsAvoidBreakValue(space, child.Style().BreakInside()))) { @@ -408,13 +476,13 @@ bool MovePastBreakpoint(const NGConstraintSpace& space, // If we haven't used any space at all in the fragmentainer yet, we cannot // break before this child, or there'd be no progress. We'd risk creating an // infinite number of fragmentainers without putting any content into them. - bool refuse_break = space_left >= space.FragmentainerBlockSize(); + bool refuse_break_before = space_left >= space.FragmentainerBlockSize(); // If the child starts past the end of the fragmentainer (probably due to a // block-start margin), we must break before it. bool must_break_before = space_left < LayoutUnit(); if (must_break_before) { - DCHECK(!refuse_break); + DCHECK(!refuse_break_before); return false; } @@ -426,15 +494,20 @@ bool MovePastBreakpoint(const NGConstraintSpace& space, // Allow breaking inside if it has the same appeal or higher than breaking // before or breaking earlier. Also, if breaking before is impossible, break // inside regardless of appeal. - if (refuse_break || (appeal_inside >= appeal_before && - (!builder->HasEarlyBreak() || - appeal_inside >= builder->BreakAppeal()))) { - builder->SetBreakAppeal(appeal_inside); + bool want_break_inside = refuse_break_before; + if (!want_break_inside && appeal_inside >= appeal_before) { + if (!builder || !builder->HasEarlyBreak() || + appeal_inside >= builder->BreakAppeal()) + want_break_inside = true; + } + if (want_break_inside) { + if (builder) + builder->SetBreakAppeal(appeal_inside); return true; } } else { bool need_break; - if (refuse_break) { + if (refuse_break_before) { need_break = false; } else if (child.IsMonolithic()) { // If the monolithic piece of content (e.g. a line, or block-level @@ -451,7 +524,7 @@ bool MovePastBreakpoint(const NGConstraintSpace& space, } if (!need_break) { - if (child.IsBlock()) { + if (child.IsBlock() && builder) { // If this doesn't happen, though, we're tentatively not going to break // before or inside this child, but we'll check the appeal of breaking // there anyway. It may be the best breakpoint we'll ever find. (Note 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 b12b66333ff..23c52f2d447 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 @@ -70,8 +70,11 @@ NGBreakAppeal CalculateBreakAppealInside(const NGConstraintSpace& space, // start of the current block formatting context. Note that if the start of the // current block formatting context is in a previous fragmentainer, the size of // the current fragmentainer is returned instead. +// In the case of initial column balancing, the size is unknown, in which case +// kIndefiniteSize is returned. inline LayoutUnit FragmentainerSpaceAtBfcStart(const NGConstraintSpace& space) { - DCHECK(space.HasKnownFragmentainerBlockSize()); + if (!space.HasKnownFragmentainerBlockSize()) + return kIndefiniteSize; return space.FragmentainerBlockSize() - space.FragmentainerOffsetAtBfc(); } @@ -111,11 +114,27 @@ inline void SetupFragmentBuilderForFragmentation(const NGConstraintSpace&, const NGInlineBreakToken*, NGLineBoxFragmentBuilder*) {} -// Write fragmentation information to the fragment builder after layout. -void FinishFragmentation(const NGConstraintSpace&, +// Return true if the node is fully grown at its current size. +// |current_total_block_size| is the total block-size of the node, as if all +// fragments were stitched together. +bool IsNodeFullyGrown(NGBlockNode, + const NGConstraintSpace&, + LayoutUnit current_total_block_size, + const NGBoxStrut& border_padding, + LayoutUnit inline_size); + +// Update and write fragmentation information to the fragment builder after +// layout. This will update the block-size stored in the builder. When +// calculating the block-size, a layout algorithm will include the accumulated +// block-size of all fragments generated for this node - as if they were all +// stitched together as one tall fragment. This is the most convenient thing to +// do, since any block-size specified in CSS applies to the entire box, +// regardless of fragmentation. This function will update the block-size to the +// actual fragment size, by examining possible breakpoints, if necessary. +void FinishFragmentation(NGBlockNode node, + const NGConstraintSpace&, const NGBlockBreakToken* previous_break_token, - LayoutUnit block_size, - LayoutUnit intrinsic_block_size, + const NGBoxStrut& border_padding, LayoutUnit space_left, NGBoxFragmentBuilder*); @@ -225,6 +244,19 @@ bool AttemptSoftBreak(const NGConstraintSpace&, NGBreakAppeal appeal_before, NGBoxFragmentBuilder*); +// Return the adjusted child margin to be applied at the end of a fragment. +// Margins should collapse with the fragmentainer boundary. |bfc_block_offset| +// is the BFC offset where the margin should be applied (i.e. after the +// block-end border edge of the last child fragment). +inline LayoutUnit AdjustedMarginAfterFinalChildFragment( + const NGConstraintSpace& space, + LayoutUnit bfc_block_offset, + LayoutUnit block_end_margin) { + LayoutUnit space_left = + FragmentainerSpaceAtBfcStart(space) - bfc_block_offset; + return std::min(block_end_margin, space_left.ClampNegativeToZero()); +} + } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENTATION_UTILS_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.cc deleted file mode 100644 index ee14539bc1c..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.cc +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 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_grid_layout_algorithm.h" - -namespace blink { - -NGGridLayoutAlgorithm::NGGridLayoutAlgorithm( - const NGLayoutAlgorithmParams& params) - : NGLayoutAlgorithm(params) { - DCHECK(params.space.IsNewFormattingContext()); - DCHECK(!params.break_token); - container_builder_.SetIsNewFormattingContext(true); - container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); -} - -scoped_refptr<const NGLayoutResult> NGGridLayoutAlgorithm::Layout() { - return container_builder_.ToBoxFragment(); -} - -MinMaxSizesResult NGGridLayoutAlgorithm::ComputeMinMaxSizes( - const MinMaxSizesInput& input) const { - return {MinMaxSizes(), /* depends_on_percentage_block_size */ true}; -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc index e5ed5b152e8..a3b4b610629 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_ink_overflow.h" #include "third_party/blink/renderer/core/layout/geometry/logical_rect.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h" #include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -58,7 +59,8 @@ void NGInkOverflow::ComputeTextInkOverflow( } PhysicalRect local_ink_overflow = - LogicalRect(ink_overflow).ConvertToPhysical(writing_mode, size); + WritingModeConverter({writing_mode, TextDirection::kLtr}, size) + .ToPhysical(LogicalRect(ink_overflow)); // Uniting the frame rect ensures that non-ink spaces such side bearings, or // even space characters, are included in the visual rect for decorations. 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 90c7bd7b3f0..2eaf870c75f 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 @@ -77,8 +77,7 @@ class CORE_EXPORT NGLayoutAlgorithm : public NGLayoutAlgorithmOperations { container_builder_(node, style, &space, - space.GetWritingMode(), - direction) { + {space.GetWritingMode(), direction}) { if (UNLIKELY(space.HasBlockFragmentation())) { DCHECK(space.IsAnonymous() || !node.IsMonolithic()); SetupFragmentBuilderForFragmentation(space, BreakToken(), @@ -113,6 +112,16 @@ class CORE_EXPORT NGLayoutAlgorithm : public NGLayoutAlgorithmOperations { const NGBreakTokenType* BreakToken() const { return break_token_.get(); } + const NGBoxStrut& BorderPadding() const { + return container_builder_.BorderPadding(); + } + const NGBoxStrut& BorderScrollbarPadding() const { + return container_builder_.BorderScrollbarPadding(); + } + const LogicalSize& ChildAvailableSize() const { + return container_builder_.ChildAvailableSize(); + } + NGInputNodeType node_; // The break token from which we are currently resuming layout. 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 fe3212c1b9c..380dc1b5c3a 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 @@ -63,7 +63,7 @@ void AppendNodeToString(NGLayoutInputNode node, MinMaxSizesResult NGLayoutInputNode::ComputeMinMaxSizes( WritingMode writing_mode, const MinMaxSizesInput& input, - const NGConstraintSpace* space) { + const NGConstraintSpace* space) const { if (auto* inline_node = DynamicTo<NGInlineNode>(this)) return inline_node->ComputeMinMaxSizes(writing_mode, input, space); return To<NGBlockNode>(*this).ComputeMinMaxSizes(writing_mode, input, space); 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 59c09f148d1..26ce67d1fc4 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 @@ -27,6 +27,11 @@ class NGPaintFragment; struct MinMaxSizes; struct PhysicalSize; +// min/max-content take the CSS aspect-ratio property into account. +// In some cases that's undesirable; this enum lets you choose not +// to do that using |kIntrinsic|. +enum class MinMaxSizesType { kContent, kIntrinsic }; + // The input to the min/max inline size calculation algorithm for child nodes. // Child nodes within the same formatting context need to know which floats are // beside them. @@ -41,11 +46,15 @@ struct MinMaxSizesInput { // // As we don't perform any tree walking, we need to pass the percentage // resolution block-size for min/max down the min/max size calculation. - explicit MinMaxSizesInput(LayoutUnit percentage_resolution_block_size) - : percentage_resolution_block_size(percentage_resolution_block_size) {} + MinMaxSizesInput(LayoutUnit percentage_resolution_block_size, + MinMaxSizesType type) + : percentage_resolution_block_size(percentage_resolution_block_size), + type(type) {} LayoutUnit float_left_inline_size; LayoutUnit float_right_inline_size; LayoutUnit percentage_resolution_block_size; + + MinMaxSizesType type; }; // The output of the min/max inline size calculation algorithm. Contains the @@ -92,6 +101,9 @@ class CORE_EXPORT NGLayoutInputNode { bool IsOutOfFlowPositioned() const { return IsBlock() && box_->IsOutOfFlowPositioned(); } + bool IsFloatingOrOutOfFlowPositioned() const { + return IsFloating() || IsOutOfFlowPositioned(); + } bool IsReplaced() const { return box_->IsLayoutReplaced(); } bool IsAbsoluteContainer() const { return box_->CanContainAbsolutePositionObjects(); @@ -120,7 +132,7 @@ class CORE_EXPORT NGLayoutInputNode { return IsBlock() && box_->IsLayoutNGFieldset(); } bool IsRubyRun() const { return IsBlock() && box_->IsRubyRun(); } - bool IsRubyText() const { return IsBlock() && box_->IsRubyText(); } + bool IsRubyText() const { return box_->IsRubyText(); } // Return true if this is the legend child of a fieldset that gets special // treatment (i.e. placed over the block-start border). @@ -129,6 +141,8 @@ class CORE_EXPORT NGLayoutInputNode { } bool IsTable() const { return IsBlock() && box_->IsTable(); } + bool IsTableCaption() const { return IsBlock() && box_->IsTableCaption(); } + bool IsMathRoot() const { return box_->IsMathMLRoot(); } bool IsAnonymousBlock() const { return box_->IsAnonymousBlock(); } @@ -166,9 +180,10 @@ class CORE_EXPORT NGLayoutInputNode { } // Returns the border-box min/max content sizes for the node. - MinMaxSizesResult ComputeMinMaxSizes(WritingMode, - const MinMaxSizesInput&, - const NGConstraintSpace* = nullptr); + MinMaxSizesResult ComputeMinMaxSizes( + WritingMode, + const MinMaxSizesInput&, + const NGConstraintSpace* = nullptr) const; // Returns intrinsic sizing information for replaced elements. // ComputeReplacedSize can use it to compute actual replaced size. 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 6f3e2393746..337bfb89d45 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 @@ -49,24 +49,6 @@ NGLayoutResult::NGLayoutResult( bitfields_.subtree_modified_margin_strut = builder->subtree_modified_margin_strut_; intrinsic_block_size_ = builder->intrinsic_block_size_; - // We don't support fragment caching when block-fragmenting, so mark the - // result as non-reusable. - if (builder->has_block_fragmentation_) - EnsureRareData()->is_single_use = true; - if (builder->minimal_space_shortage_ != LayoutUnit::Max()) { -#if DCHECK_IS_ON() - DCHECK(!HasRareData() || !rare_data_->has_tallest_unbreakable_block_size); -#endif - EnsureRareData()->minimal_space_shortage = builder->minimal_space_shortage_; - } - if (builder->tallest_unbreakable_block_size_ >= LayoutUnit()) { - auto* rare_data = EnsureRareData(); - rare_data->tallest_unbreakable_block_size = - builder->tallest_unbreakable_block_size_; -#if DCHECK_IS_ON() - rare_data->has_tallest_unbreakable_block_size = true; -#endif - } if (builder->overflow_block_size_ != kIndefiniteSize && builder->overflow_block_size_ != intrinsic_block_size_) { EnsureRareData()->overflow_block_size = builder->overflow_block_size_; @@ -75,15 +57,45 @@ NGLayoutResult::NGLayoutResult( EnsureRareData()->custom_layout_data = std::move(builder->custom_layout_data_); } - if (builder->column_spanner_) - EnsureRareData()->column_spanner = builder->column_spanner_; if (builder->lines_until_clamp_) EnsureRareData()->lines_until_clamp = *builder->lines_until_clamp_; - bitfields_.initial_break_before = - static_cast<unsigned>(builder->initial_break_before_); - bitfields_.final_break_after = - static_cast<unsigned>(builder->previous_break_after_); - bitfields_.has_forced_break = builder->has_forced_break_; + if (builder->annotation_overflow_) + EnsureRareData()->annotation_overflow = builder->annotation_overflow_; + if (builder->block_end_annotation_space_) { + EnsureRareData()->block_end_annotation_space = + builder->block_end_annotation_space_; + } + + if (builder->has_block_fragmentation_) { + RareData* rare_data = EnsureRareData(); + + // We don't support fragment caching when block-fragmenting, so mark the + // result as non-reusable. + rare_data->is_single_use = true; + + if (builder->tallest_unbreakable_block_size_ >= LayoutUnit()) { + rare_data->tallest_unbreakable_block_size = + builder->tallest_unbreakable_block_size_; +#if DCHECK_IS_ON() + rare_data->has_tallest_unbreakable_block_size = true; +#endif + } + if (builder->minimal_space_shortage_ != LayoutUnit::Max()) { +#if DCHECK_IS_ON() + DCHECK(!rare_data->has_tallest_unbreakable_block_size); +#endif + rare_data->minimal_space_shortage = builder->minimal_space_shortage_; + } + + if (builder->column_spanner_) + rare_data->column_spanner = builder->column_spanner_; + + bitfields_.initial_break_before = + static_cast<unsigned>(builder->initial_break_before_); + bitfields_.final_break_after = + static_cast<unsigned>(builder->previous_break_after_); + bitfields_.has_forced_break = builder->has_forced_break_; + } } NGLayoutResult::NGLayoutResult( @@ -174,6 +186,12 @@ NGLayoutResult::NGLayoutResult( if (builder->end_margin_strut_ != NGMarginStrut()) EnsureRareData()->end_margin_strut = builder->end_margin_strut_; + if (builder->annotation_overflow_ > LayoutUnit()) + EnsureRareData()->annotation_overflow = builder->annotation_overflow_; + if (builder->block_end_annotation_space_) { + EnsureRareData()->block_end_annotation_space = + builder->block_end_annotation_space_; + } if (builder->unpositioned_list_marker_) { EnsureRareData()->unpositioned_list_marker = builder->unpositioned_list_marker_; @@ -294,4 +312,12 @@ void NGLayoutResult::CheckSameForSimplifiedLayout( } #endif +#if DCHECK_IS_ON() +void NGLayoutResult::AssertSoleBoxFragment() const { + DCHECK(physical_fragment_->IsBox()); + DCHECK(To<NGPhysicalBoxFragment>(PhysicalFragment()).IsFirstForNode()); + DCHECK(!physical_fragment_->BreakToken()); +} +#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 5d15b09f808..4674ca56060 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 @@ -68,6 +68,24 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { return HasRareData() ? rare_data_->lines_until_clamp : 0; } + // How much an annotation box overflow from this box. + // This is for LayoutNGRubyRun and line boxes. + // 0 : No overflow + // -N : Overflowing by N px at block-start side + // This happens only for LayoutRubyRun. + // N : Overflowing by N px at block-end side + LayoutUnit AnnotationOverflow() const { + return HasRareData() ? rare_data_->annotation_overflow : LayoutUnit(); + } + + // The amount of available space for block-start side annotations of the + // next box. + // This never be negative. + LayoutUnit BlockEndAnnotationSpace() const { + return HasRareData() ? rare_data_->block_end_annotation_space + : LayoutUnit(); + } + LogicalOffset OutOfFlowPositionedOffset() const { DCHECK(bitfields_.has_oof_positioned_offset); return HasRareData() ? rare_data_->oof_positioned_offset @@ -145,8 +163,18 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { return HasRareData() ? rare_data_->end_margin_strut : NGMarginStrut(); } + // Get the intrinsic block-size of the fragment (i.e. the block-size the + // fragment would get if no block-size constraints were applied). This is not + // supported (and should not be needed [1]) if the node got split into + // multiple fragments. + // + // [1] If a node gets block-fragmented, it means that it has possibly been + // constrained and/or stretched by something extrinsic (i.e. the + // fragmentainer), so the value returned here wouldn't be useful. const LayoutUnit IntrinsicBlockSize() const { - DCHECK(physical_fragment_->IsBox()); +#if DCHECK_IS_ON() + AssertSoleBoxFragment(); +#endif return intrinsic_block_size_; } @@ -373,6 +401,8 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { NGExclusionSpace exclusion_space; scoped_refptr<SerializedScriptValue> custom_layout_data; LayoutUnit overflow_block_size = kIndefiniteSize; + LayoutUnit annotation_overflow; + LayoutUnit block_end_annotation_space; #if DCHECK_IS_ON() bool has_tallest_unbreakable_block_size = false; #endif @@ -383,6 +413,10 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { bool HasRareData() const { return bitfields_.has_rare_data; } RareData* EnsureRareData(); +#if DCHECK_IS_ON() + void AssertSoleBoxFragment() const; +#endif + struct Bitfields { DISALLOW_NEW(); 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 db86d7af448..1a629ac1261 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 @@ -212,14 +212,17 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( if (old_space.IsFixedBlockSize()) return NGLayoutCacheStatus::kNeedsLayout; - // 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. + // The intrinsic size of flex-boxes can depend on the %-block-size. This + // occurs when: + // - A column flex-box has "max-height: 100%" (or similar) on itself. + // - A row flex-box has "height: 100%" (or similar) and children which + // stretch to this size. // // Due to this we can't use cached |NGLayoutResult::IntrinsicBlockSize| // value, as the following |block_size| calculation would be incorrect. - if (style.ResolvedIsColumnFlexDirection() && - layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { + // TODO(dgrogan): We can hit the cache here for row flexboxes when they + // don't have stretchy children. + if (layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { if (new_space.PercentageResolutionBlockSize() != old_space.PercentageResolutionBlockSize()) return NGLayoutCacheStatus::kNeedsLayout; 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 25629c7019c..261ed63c908 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 @@ -72,7 +72,7 @@ bool BlockLengthUnresolvable( LengthResolvePhase phase, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max) { if (length.IsAuto() || length.IsMinContent() || length.IsMaxContent() || - length.IsFitContent() || length.IsNone()) + length.IsMinIntrinsic() || length.IsFitContent() || length.IsNone()) return true; if (length.IsPercentOrCalc()) { if (phase == LengthResolvePhase::kIntrinsic) @@ -127,11 +127,12 @@ LayoutUnit ResolveInlineLengthInternal( } case Length::kMinContent: case Length::kMaxContent: + case Length::kMinIntrinsic: case Length::kFitContent: { DCHECK(min_max_sizes.has_value()); LayoutUnit available_size = constraint_space.AvailableSize().inline_size; LayoutUnit value; - if (length.IsMinContent()) { + if (length.IsMinContent() || length.IsMinIntrinsic()) { value = min_max_sizes->min_size; } else if (length.IsMaxContent() || available_size == LayoutUnit::Max()) { // If the available space is infinite, fit-content resolves to @@ -200,6 +201,7 @@ LayoutUnit ResolveBlockLengthInternal( case Length::kAuto: case Length::kMinContent: case Length::kMaxContent: + case Length::kMinIntrinsic: case Length::kFitContent: #if DCHECK_IS_ON() // Due to how content_size is calculated, it should always include border @@ -270,7 +272,7 @@ MinMaxSizesResult ComputeMinAndMaxContentContributionInternal( : style.Height(); if (inline_size.IsAuto() || inline_size.IsPercentOrCalc() || inline_size.IsFillAvailable() || inline_size.IsFitContent()) { - result = min_max_sizes_func(); + result = min_max_sizes_func(MinMaxSizesType::kContent); } else { if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { MinMaxSizes sizes; @@ -279,7 +281,10 @@ MinMaxSizesResult ComputeMinAndMaxContentContributionInternal( result = {sizes, /* depends_on_percentage_block_size */ false}; } else { auto IntrinsicBlockSizeFunc = [&]() -> LayoutUnit { - return min_max_sizes_func().sizes.max_size; + return min_max_sizes_func(inline_size.IsMinIntrinsic() + ? MinMaxSizesType::kIntrinsic + : MinMaxSizesType::kContent) + .sizes.max_size; }; MinMaxSizes sizes; sizes = ResolveMainBlockLength(space, style, border_padding, inline_size, @@ -326,7 +331,7 @@ MinMaxSizes ComputeMinAndMaxContentContributionForTest( WritingMode parent_writing_mode, const ComputedStyle& style, const MinMaxSizes& min_max_sizes) { - auto MinMaxSizesFunc = [&]() -> MinMaxSizesResult { + auto MinMaxSizesFunc = [&](MinMaxSizesType) -> MinMaxSizesResult { return {min_max_sizes, false}; }; return ComputeMinAndMaxContentContributionInternal(parent_writing_mode, style, @@ -374,17 +379,19 @@ MinMaxSizesResult ComputeMinAndMaxContentContribution( } } - auto MinMaxSizesFunc = [&]() -> MinMaxSizesResult { + auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult { + MinMaxSizesInput input_copy(input); + input_copy.type = type; // We need to set up a constraint space with correct fallback available // inline-size in case of orthogonal children. NGConstraintSpace indefinite_constraint_space; const NGConstraintSpace* child_constraint_space = nullptr; if (!IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { - indefinite_constraint_space = - CreateIndefiniteConstraintSpaceForChild(parent_style, child); + indefinite_constraint_space = CreateIndefiniteConstraintSpaceForChild( + parent_style, input_copy, child); child_constraint_space = &indefinite_constraint_space; } - return child.ComputeMinMaxSizes(parent_writing_mode, input, + return child.ComputeMinMaxSizes(parent_writing_mode, input_copy, child_constraint_space); }; @@ -418,13 +425,12 @@ LayoutUnit ComputeInlineSizeForFragment( const ComputedStyle& style = node.Style(); Length logical_width = style.LogicalWidth(); - auto MinMaxSizesFunc = [&]() -> MinMaxSizesResult { + auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult { if (override_min_max_sizes_for_test) return {*override_min_max_sizes_for_test, false}; - return node.ComputeMinMaxSizes( - space.GetWritingMode(), - MinMaxSizesInput(space.PercentageResolutionBlockSize()), &space); + MinMaxSizesInput input(space.PercentageResolutionBlockSize(), type); + return node.ComputeMinMaxSizes(space.GetWritingMode(), input, &space); }; Length min_length = style.LogicalMinWidth(); @@ -439,8 +445,9 @@ LayoutUnit ComputeInlineSizeForFragment( // if we need to apply the implied minimum size: // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum if (style.OverflowInlineDirection() == EOverflow::kVisible && - min_length.IsAuto()) - min_length = Length::MinContent(); + min_length.IsAuto()) { + min_length = Length::MinIntrinsic(); + } } else { if (logical_width.IsAuto() && space.IsShrinkToFit()) logical_width = Length::FitContent(); @@ -498,8 +505,7 @@ LayoutUnit ComputeBlockSizeForFragmentInternal( LengthResolvePhase::kLayout, opt_percentage_resolution_block_size_for_min_max); if (UNLIKELY((extent == kIndefiniteSize || logical_height.IsAuto()) && - style.LogicalAspectRatio() && inline_size && - !style.LogicalWidth().IsAuto())) { + style.LogicalAspectRatio() && inline_size)) { extent = BlockSizeFromAspectRatio(border_padding, *style.LogicalAspectRatio(), style.BoxSizing(), *inline_size); @@ -782,9 +788,9 @@ LayoutUnit ResolveUsedColumnInlineSize(LayoutUnit available_size, LayoutUnit ResolveUsedColumnGap(LayoutUnit available_size, const ComputedStyle& style) { - if (style.ColumnGap().IsNormal()) - return LayoutUnit(style.GetFontDescription().ComputedPixelSize()); - return ValueForLength(style.ColumnGap().GetLength(), available_size); + if (const base::Optional<Length>& column_gap = style.ColumnGap()) + return ValueForLength(*column_gap, available_size); + return LayoutUnit(style.GetFontDescription().ComputedPixelSize()); } NGPhysicalBoxStrut ComputePhysicalMargins( @@ -916,10 +922,9 @@ NGBoxStrut ComputePadding(const NGConstraintSpace& constraint_space, return padding; } -NGBoxStrut ComputeScrollbars(const NGConstraintSpace& constraint_space, - const NGLayoutInputNode node) { +NGBoxStrut ComputeScrollbarsForNonAnonymous(const NGBlockNode& node) { const ComputedStyle& style = node.Style(); - if (constraint_space.IsAnonymous() || style.IsOverflowVisible()) + if (style.IsOverflowVisible()) return NGBoxStrut(); NGPhysicalBoxStrut sizes; const LayoutBox* layout_box = node.GetLayoutBox(); @@ -1103,19 +1108,33 @@ NGFragmentGeometry CalculateInitialMinMaxFragmentGeometry( return {/* border_box_size */ LogicalSize(), border, scrollbar, padding}; } -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()); - +LogicalSize ShrinkLogicalSize(LogicalSize size, const NGBoxStrut& insets) { + if (size.inline_size != kIndefiniteSize) { + size.inline_size = + (size.inline_size - insets.InlineSum()).ClampNegativeToZero(); + } if (size.block_size != kIndefiniteSize) { - size.block_size -= inset.BlockSum(); - size.block_size = std::max(size.block_size, LayoutUnit()); + size.block_size = + (size.block_size - insets.BlockSum()).ClampNegativeToZero(); } return size; } +LogicalSize CalculateChildAvailableSize( + const NGConstraintSpace& space, + const NGBlockNode& node, + const LogicalSize border_box_size, + const NGBoxStrut& border_scrollbar_padding) { + LogicalSize child_available_size = + ShrinkLogicalSize(border_box_size, border_scrollbar_padding); + + if (space.IsAnonymous() || node.IsAnonymousBlock()) + child_available_size.block_size = space.AvailableSize().block_size; + + return child_available_size; +} + namespace { // Implements the common part of the child percentage size calculation. Deals 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 ef8e775331d..d2a15b80bfd 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 @@ -91,8 +91,12 @@ inline LayoutUnit ResolveMinInlineLength( return border_padding.InlineSum(); base::Optional<MinMaxSizes> min_max_sizes; - if (length.IsIntrinsic()) - min_max_sizes = min_max_sizes_func().sizes; + if (length.IsIntrinsic()) { + min_max_sizes = + min_max_sizes_func(length.IsMinIntrinsic() ? MinMaxSizesType::kIntrinsic + : MinMaxSizesType::kContent) + .sizes; + } return ResolveInlineLengthInternal(constraint_space, style, border_padding, min_max_sizes, length); @@ -126,8 +130,12 @@ inline LayoutUnit ResolveMaxInlineLength( return LayoutUnit::Max(); base::Optional<MinMaxSizes> min_max_sizes; - if (length.IsIntrinsic()) - min_max_sizes = min_max_sizes_func().sizes; + if (length.IsIntrinsic()) { + min_max_sizes = + min_max_sizes_func(length.IsMinIntrinsic() ? MinMaxSizesType::kIntrinsic + : MinMaxSizesType::kContent) + .sizes; + } return ResolveInlineLengthInternal(constraint_space, style, border_padding, min_max_sizes, length); @@ -157,8 +165,12 @@ inline LayoutUnit ResolveMainInlineLength( const MinMaxSizesFunc& min_max_sizes_func, const Length& length) { base::Optional<MinMaxSizes> min_max_sizes; - if (length.IsIntrinsic()) - min_max_sizes = min_max_sizes_func().sizes; + if (length.IsIntrinsic()) { + min_max_sizes = + min_max_sizes_func(length.IsMinIntrinsic() ? MinMaxSizesType::kIntrinsic + : MinMaxSizesType::kContent) + .sizes; + } return ResolveInlineLengthInternal(constraint_space, style, border_padding, min_max_sizes, length); @@ -451,8 +463,15 @@ inline NGLineBoxStrut ComputeLinePadding( style.IsFlippedLinesWritingMode()); } -CORE_EXPORT NGBoxStrut ComputeScrollbars(const NGConstraintSpace&, - const NGLayoutInputNode); +CORE_EXPORT NGBoxStrut ComputeScrollbarsForNonAnonymous(const NGBlockNode&); + +inline NGBoxStrut ComputeScrollbars(const NGConstraintSpace& space, + const NGBlockNode& node) { + if (space.IsAnonymous()) + return NGBoxStrut(); + + return ComputeScrollbarsForNonAnonymous(node); +} // 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, @@ -500,10 +519,15 @@ 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. -LogicalSize ShrinkAvailableSize(LogicalSize size, const NGBoxStrut& inset); +// Shrinks the logical |size| by |insets|. +LogicalSize ShrinkLogicalSize(LogicalSize size, const NGBoxStrut& insets); + +// Calculates the available size that children of the node should use. +LogicalSize CalculateChildAvailableSize( + const NGConstraintSpace&, + const NGBlockNode& node, + const LogicalSize border_box_size, + const NGBoxStrut& border_scrollbar_padding); // Calculates the percentage resolution size that children of the node should // use. 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 4020a8fd3b7..74a8fcb12ec 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 @@ -19,6 +19,7 @@ #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_positioned_node.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" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" @@ -113,7 +114,7 @@ NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( default_containing_block_.direction = container_style.Direction(); default_containing_block_.content_size_for_absolute = - ShrinkAvailableSize(container_builder_->Size(), border_scrollbar); + ShrinkLogicalSize(container_builder_->Size(), border_scrollbar); default_containing_block_.content_size_for_fixed = initial_containing_block_fixed_size ? *initial_containing_block_fixed_size @@ -124,6 +125,15 @@ NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( } void NGOutOfFlowLayoutPart::Run(const LayoutBox* only_layout) { + if (container_builder_->IsBlockFragmentationContextRoot()) { + Vector<NGLogicalOutOfFlowPositionedNode> fragmentainer_descendants; + container_builder_->SwapOutOfFlowFragmentainerDescendants( + &fragmentainer_descendants); + + if (!fragmentainer_descendants.IsEmpty()) + LayoutFragmentainerDescendants(&fragmentainer_descendants); + } + Vector<NGLogicalOutOfFlowPositionedNode> candidates; const LayoutObject* current_container = container_builder_->GetLayoutObject(); // If the container is display-locked, then we skip the layout of descendants, @@ -267,14 +277,53 @@ bool NGOutOfFlowLayoutPart::SweepLegacyCandidates( return true; } +// Retrieve the stored ContainingBlockInfo needed for placing positioned nodes. +// When fragmenting, the ContainingBlockInfo is not stored ahead of time and +// must be generated on demand. The reason being that during fragmentation, we +// wait to place positioned nodes until they've reached the fragmentation +// context root. In such cases, we cannot use |default_containing_block_| since +// the fragmentation root is not the containing block of the positioned nodes. +// Rather, we must generate their ContainingBlockInfo based on the provided +// |containing_block_fragment|. const NGOutOfFlowLayoutPart::ContainingBlockInfo& NGOutOfFlowLayoutPart::GetContainingBlockInfo( - const NGLogicalOutOfFlowPositionedNode& candidate) const { + const NGLogicalOutOfFlowPositionedNode& candidate, + const NGPhysicalContainerFragment* containing_block_fragment) { if (candidate.inline_container) { const auto it = containing_blocks_map_.find(candidate.inline_container); DCHECK(it != containing_blocks_map_.end()); return it->value; } + if (containing_block_fragment) { + DCHECK(container_builder_->IsBlockFragmentationContextRoot()); + + const LayoutObject* containing_block = + containing_block_fragment->GetLayoutObject(); + DCHECK(containing_block); + auto it = containing_blocks_map_.find(containing_block); + if (it != containing_blocks_map_.end()) + return it->value; + + const ComputedStyle& style = containing_block->StyleRef(); + LogicalSize size = containing_block_fragment->Size().ConvertToLogical( + style.GetWritingMode()); + const NGPhysicalBoxFragment* fragment = + To<NGPhysicalBoxFragment>(containing_block_fragment); + + // TODO(1079031): This should eventually include scrollbar and border. + NGBoxStrut border = fragment->Borders().ConvertToLogical( + style.GetWritingMode(), style.Direction()); + LogicalSize content_size = ShrinkLogicalSize(size, border); + LogicalOffset container_offset = + LogicalOffset(border.inline_start, border.block_start); + + ContainingBlockInfo containing_block_info{style.Direction(), content_size, + content_size, container_offset}; + + return containing_blocks_map_ + .insert(containing_block, containing_block_info) + .stored_value->value; + } return default_containing_block_; } @@ -433,6 +482,10 @@ void NGOutOfFlowLayoutPart::LayoutCandidates( candidate.static_position); if (IsContainingBlockForCandidate(candidate) && (!only_layout || layout_box == only_layout)) { + if (container_space_.HasBlockFragmentation()) { + container_builder_->AddOutOfFlowFragmentainerDescendant(candidate); + continue; + } scoped_refptr<const NGLayoutResult> result = LayoutCandidate(candidate, only_layout); container_builder_->AddChild(result->PhysicalFragment(), @@ -521,7 +574,8 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutCandidate( do { scoped_refptr<const NGLayoutResult> layout_result = Layout(node, candidate_constraint_space, candidate_static_position, - container_content_size, container_info, only_layout); + container_content_size, container_info, + default_containing_block_.direction, only_layout); if (!freeze_scrollbars.has_value()) { // Since out-of-flow positioning sets up a constraint space with fixed @@ -545,14 +599,85 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutCandidate( } while (true); } +void NGOutOfFlowLayoutPart::LayoutFragmentainerDescendants( + Vector<NGLogicalOutOfFlowPositionedNode>* descendants) { + while (descendants->size() > 0) { + for (auto& descendant : *descendants) { + scoped_refptr<const NGLayoutResult> result = + LayoutFragmentainerDescendant(descendant); + + // TODO(almaher): Add children to the correct fragmentainer. + container_builder_->AddChild(result->PhysicalFragment(), + result->OutOfFlowPositionedOffset(), + descendant.inline_container); + } + // Sweep any descendants that might have been added. + // This happens when an absolute container has a fixed child. + descendants->Shrink(0); + container_builder_->SwapOutOfFlowFragmentainerDescendants(descendants); + } +} + +scoped_refptr<const NGLayoutResult> +NGOutOfFlowLayoutPart::LayoutFragmentainerDescendant( + const NGLogicalOutOfFlowPositionedNode& descendant) { + // TODO(almaher): Properly implement the layout algorithm for fragmented + // positioned elements. + NGBlockNode node = descendant.node; + const NGPhysicalContainerFragment* containing_block_fragment = + descendant.containing_block_fragment.get(); + + DCHECK(containing_block_fragment && + containing_block_fragment->GetLayoutObject() == + node.GetLayoutBox()->ContainingBlock()); + + const ContainingBlockInfo& container_info = + GetContainingBlockInfo(descendant, containing_block_fragment); + const TextDirection default_direction = + containing_block_fragment->Style().Direction(); + const ComputedStyle& descendant_style = node.Style(); + const WritingMode descendant_writing_mode = descendant_style.GetWritingMode(); + const TextDirection descendant_direction = descendant_style.Direction(); + + LogicalSize container_content_size = + container_info.ContentSize(descendant_style.GetPosition()); + PhysicalSize container_physical_content_size = + ToPhysicalSize(container_content_size, writing_mode_); + + // Adjust the |static_position| (which is currently relative to the default + // container's border-box). ng_absolute_utils expects the static position to + // be relative to the container's padding-box. + NGLogicalStaticPosition static_position = descendant.static_position; + static_position.offset -= container_info.container_offset; + + NGLogicalStaticPosition descendant_static_position = + static_position + .ConvertToPhysical(writing_mode_, default_direction, + container_physical_content_size) + .ConvertToLogical(descendant_writing_mode, descendant_direction, + container_physical_content_size); + + // Need a constraint space to resolve offsets. + NGConstraintSpaceBuilder builder(writing_mode_, descendant_writing_mode, + /* is_new_fc */ true); + builder.SetTextDirection(descendant_direction); + builder.SetAvailableSize(container_content_size); + builder.SetPercentageResolutionSize(container_content_size); + NGConstraintSpace descendant_constraint_space = builder.ToConstraintSpace(); + + return Layout(node, descendant_constraint_space, descendant_static_position, + container_content_size, container_info, default_direction, + nullptr); +} + scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( NGBlockNode node, const NGConstraintSpace& candidate_constraint_space, const NGLogicalStaticPosition& candidate_static_position, LogicalSize container_content_size, const ContainingBlockInfo& container_info, + const TextDirection default_direction, const LayoutBox* only_layout) { - const TextDirection default_direction = default_containing_block_.direction; const ComputedStyle& candidate_style = node.Style(); const WritingMode candidate_writing_mode = candidate_style.GetWritingMode(); const TextDirection candidate_direction = candidate_style.Direction(); @@ -588,7 +713,7 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( if (AbsoluteNeedsChildInlineSize(candidate_style) || NeedMinMaxSize(candidate_style) || should_be_considered_as_replaced) { - MinMaxSizesInput input(kIndefiniteSize); + MinMaxSizesInput input(kIndefiniteSize, MinMaxSizesType::kContent); if (is_replaced) { input.percentage_resolution_block_size = container_content_size_in_candidate_writing_mode.block_size; @@ -610,19 +735,19 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( base::Optional<LogicalSize> replaced_size; base::Optional<LogicalSize> replaced_aspect_ratio; - bool is_replaced_with_only_aspect_ratio = false; + bool has_aspect_ratio_without_intrinsic_size = false; if (is_replaced) { ComputeReplacedSize(node, candidate_constraint_space, min_max_sizes, &replaced_size, &replaced_aspect_ratio); - is_replaced_with_only_aspect_ratio = !replaced_size && - replaced_aspect_ratio && - !replaced_aspect_ratio->IsEmpty(); + has_aspect_ratio_without_intrinsic_size = !replaced_size && + replaced_aspect_ratio && + !replaced_aspect_ratio->IsEmpty(); // If we only have aspect ratio, and no replaced size, intrinsic size // defaults to 300x150. min_max_sizes gets computed from the intrinsic size. // We reset the min_max_sizes because spec says that OOF-positioned size // should not be constrained by intrinsic size in this case. // https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width - if (is_replaced_with_only_aspect_ratio) + if (has_aspect_ratio_without_intrinsic_size) min_max_sizes = MinMaxSizes{LayoutUnit(), LayoutUnit::NearlyMax()}; } else if (should_be_considered_as_replaced) { replaced_size = @@ -641,10 +766,10 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( if (!is_replaced && should_be_considered_as_replaced) replaced_size.reset(); - // Replaced elements with only aspect ratio compute their block size from + // Elements with only aspect ratio compute their block size from // inline size and aspect ratio. // https://www.w3.org/TR/css-sizing-3/#intrinsic-sizes - if (is_replaced_with_only_aspect_ratio) { + if (has_aspect_ratio_without_intrinsic_size) { replaced_size = LogicalSize( node_dimensions.size.inline_size, (replaced_aspect_ratio->block_size * 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 777309f62ef..92bbec5bb1f 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 @@ -23,6 +23,7 @@ class NGBlockNode; class NGBoxFragmentBuilder; class NGConstraintSpace; class NGLayoutResult; +class NGPhysicalContainerFragment; struct NGLogicalOutOfFlowPositionedNode; // Helper class for positioning of out-of-flow blocks. @@ -96,7 +97,8 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { bool SweepLegacyCandidates(HashSet<const LayoutObject*>* placed_objects); const ContainingBlockInfo& GetContainingBlockInfo( - const NGLogicalOutOfFlowPositionedNode&) const; + const NGLogicalOutOfFlowPositionedNode&, + const NGPhysicalContainerFragment* = nullptr); void ComputeInlineContainingBlocks( const Vector<NGLogicalOutOfFlowPositionedNode>&); @@ -109,11 +111,18 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { const NGLogicalOutOfFlowPositionedNode&, const LayoutBox* only_layout); + void LayoutFragmentainerDescendants( + Vector<NGLogicalOutOfFlowPositionedNode>* descendants); + + scoped_refptr<const NGLayoutResult> LayoutFragmentainerDescendant( + const NGLogicalOutOfFlowPositionedNode&); + scoped_refptr<const NGLayoutResult> Layout(NGBlockNode, const NGConstraintSpace&, const NGLogicalStaticPosition&, LogicalSize container_content_size, const ContainingBlockInfo&, + const TextDirection, const LayoutBox* only_layout); bool IsContainingBlockForCandidate(const NGLogicalOutOfFlowPositionedNode&); 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 c950e7f86e4..f1ec239d16c 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 @@ -5,14 +5,44 @@ #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" namespace blink { namespace { -using NGOutOfFlowLayoutPartTest = NGLayoutTest; +class NGOutOfFlowLayoutPartTest + : public NGBaseLayoutAlgorithmTest, + private ScopedLayoutNGBlockFragmentationForTest { + protected: + NGOutOfFlowLayoutPartTest() : ScopedLayoutNGBlockFragmentationForTest(true) {} + + scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( + Element* element) { + NGBlockNode container(ToLayoutBox(element->GetLayoutObject())); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); + return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); + } + + String DumpFragmentTree(Element* element) { + auto fragment = RunBlockLayoutAlgorithm(element); + return DumpFragmentTree(fragment.get()); + } + + String DumpFragmentTree(const blink::NGPhysicalBoxFragment* fragment) { + NGPhysicalFragment::DumpFlags flags = + NGPhysicalFragment::DumpHeaderText | NGPhysicalFragment::DumpSubtree | + NGPhysicalFragment::DumpIndentation | NGPhysicalFragment::DumpOffset | + NGPhysicalFragment::DumpSize; + + return fragment->DumpFragmentTree(flags); + } +}; // Fixed blocks inside absolute blocks trigger otherwise unused while loop // inside NGOutOfFlowLayoutPart::Run. @@ -73,5 +103,55 @@ TEST_F(NGOutOfFlowLayoutPartTest, FixedInsideAbs) { EXPECT_EQ(fixed_2->OffsetTop(), LayoutUnit(9)); } +// Tests that positioned nodes fragment correctly. +// TODO(almaher): Reenable once the layout algorithm for fragmented positioned +// items is in a more stable state. +TEST_F(NGOutOfFlowLayoutPartTest, DISABLED_PositionedFragmentation) { + SetBodyInnerHTML( + R"HTML( + <style> + #multicol { + column-count: 2; height: 40px; column-fill:auto; + } + .rel { + position: relative; + } + .abs { + position: absolute; + } + </style> + <div id="container"> + <div id="multicol"> + <div style="width:100px; height:50px;"></div> + <div class="rel"> + <div class="abs" style="width:5px; top: 10px; height:5px;"> + </div> + <div class="rel"> + <div class="abs" style="width:10px; top: 20px; height:10px;"> + </div> + </div> + </div> + </div> + </div> + )HTML"); + String dump = DumpFragmentTree(GetElementById("container")); + + // TODO(almaher): Positioned nodes are not currently placed in the correct + // fragment. + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x40 + offset:0,0 size:1000x40 + offset:0,0 size:499.5x40 + offset:0,0 size:100x40 + offset:500.5,0 size:499.5x40 + offset:0,0 size:100x10 + offset:0,10 size:499.5x0 + offset:0,0 size:499.5x0 + offset:0,20 size:10x10 + offset:0,10 size:5x5 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + } // namespace } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h index 3f9a5127140..6b332b1b4ee 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h @@ -19,8 +19,15 @@ namespace blink { // as a positioned-node reaches its containing block, it gets placed, and // doesn't bubble further up the tree. // +// However, when fragmentation comes into play, we no longer place a +// positioned-node as soon as it reaches its containing block. Instead, we +// continue to bubble the positioned node up until it reaches the +// fragmentation context root. There, it will get placed and properly +// fragmented. +// // This needs its static position [1] to be placed correctly in its containing -// block. +// block. And in the case of fragmentation, this also needs the containing block +// fragment to be placed correctly within the fragmentation context root. // // This is struct is allowed to be stored/persisted. // @@ -30,14 +37,18 @@ struct CORE_EXPORT NGPhysicalOutOfFlowPositionedNode { NGPhysicalStaticPosition static_position; // Continuation root of the optional inline container. const LayoutInline* inline_container; + scoped_refptr<const NGPhysicalContainerFragment> containing_block_fragment; NGPhysicalOutOfFlowPositionedNode( NGBlockNode node, NGPhysicalStaticPosition static_position, - const LayoutInline* inline_container = nullptr) + const LayoutInline* inline_container = nullptr, + scoped_refptr<const NGPhysicalContainerFragment> + containing_block_fragment = nullptr) : node(node), static_position(static_position), - inline_container(inline_container) { + inline_container(inline_container), + containing_block_fragment(std::move(containing_block_fragment)) { DCHECK(!inline_container || inline_container == inline_container->ContinuationRoot()); } @@ -55,16 +66,20 @@ struct NGLogicalOutOfFlowPositionedNode { // Continuation root of the optional inline container. const LayoutInline* inline_container; bool needs_block_offset_adjustment; + scoped_refptr<const NGPhysicalContainerFragment> containing_block_fragment; NGLogicalOutOfFlowPositionedNode( NGBlockNode node, NGLogicalStaticPosition static_position, const LayoutInline* inline_container = nullptr, - bool needs_block_offset_adjustment = false) + bool needs_block_offset_adjustment = false, + scoped_refptr<const NGPhysicalContainerFragment> + containing_block_fragment = nullptr) : node(node), static_position(static_position), inline_container(inline_container), - needs_block_offset_adjustment(needs_block_offset_adjustment) { + needs_block_offset_adjustment(needs_block_offset_adjustment), + containing_block_fragment(std::move(containing_block_fragment)) { DCHECK(!inline_container || inline_container == inline_container->ContinuationRoot()); } 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 27aa6125ce8..3752e5dc4f9 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 @@ -17,28 +17,21 @@ namespace blink { 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) { + : NGLayoutAlgorithm(params) { container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); } scoped_refptr<const NGLayoutResult> NGPageLayoutAlgorithm::Layout() { - LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - LogicalSize content_box_size = - ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); - LogicalSize page_size = content_box_size; + LogicalSize page_size = ChildAvailableSize(); NGConstraintSpace child_space = CreateConstraintSpaceForPages(page_size); WritingMode writing_mode = ConstraintSpace().GetWritingMode(); scoped_refptr<const NGBlockBreakToken> break_token = BreakToken(); LayoutUnit intrinsic_block_size; - LogicalOffset page_offset(border_scrollbar_padding_.StartOffset()); + LogicalOffset page_offset = BorderScrollbarPadding().StartOffset(); // TODO(mstensho): Handle auto block size. LogicalOffset page_progression(LayoutUnit(), page_size.block_size); @@ -64,11 +57,11 @@ scoped_refptr<const NGLayoutResult> NGPageLayoutAlgorithm::Layout() { 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(), border_padding_, intrinsic_block_size, - border_box_size.inline_size); - container_builder_.SetBlockSize(border_box_size.block_size); + // Compute the block-axis size now that we know our content size. + LayoutUnit block_size = ComputeBlockSizeForFragment( + ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size, + container_builder_.InitialBorderBoxSize().inline_size); + container_builder_.SetFragmentsTotalBlockSize(block_size); NGOutOfFlowLayoutPart( Node(), ConstraintSpace(), 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 4b9ebf55713..d8613b602c2 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 @@ -30,9 +30,6 @@ class CORE_EXPORT NGPageLayoutAlgorithm private: NGConstraintSpace CreateConstraintSpaceForPages( 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 fd78b87d9c6..bd3601db795 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 @@ -52,15 +52,20 @@ scoped_refptr<const NGPhysicalBoxFragment> NGPhysicalBoxFragment::Create( const NGPhysicalBoxStrut padding = builder->initial_fragment_geometry_->padding.ConvertToPhysical( builder->GetWritingMode(), builder->Direction()); + auto& mathml_paint_info = builder->mathml_paint_info_; size_t byte_size = sizeof(NGPhysicalBoxFragment) + sizeof(NGLink) * builder->children_.size() + (borders.IsZero() ? 0 : sizeof(borders)) + - (padding.IsZero() ? 0 : sizeof(padding)); + (padding.IsZero() ? 0 : sizeof(padding)) + + (mathml_paint_info ? sizeof(NGMathMLPaintInfo*) : 0); if (const NGFragmentItemsBuilder* items_builder = builder->ItemsBuilder()) { // Omit |NGFragmentItems| if there were no items; e.g., display-lock. if (items_builder->Size()) byte_size += NGFragmentItems::ByteSizeFor(items_builder->Size()); } + if (builder->HasOutOfFlowFragmentainerDescendants()) + byte_size += sizeof(NGPhysicalOutOfFlowPositionedNode); + // 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. @@ -68,8 +73,9 @@ scoped_refptr<const NGPhysicalBoxFragment> NGPhysicalBoxFragment::Create( // we pass the buffer as a constructor argument. void* data = ::WTF::Partitions::FastMalloc( byte_size, ::WTF::GetStringWithTypeName<NGPhysicalBoxFragment>()); - new (data) NGPhysicalBoxFragment(PassKey(), builder, borders, padding, - block_or_line_writing_mode); + new (data) + NGPhysicalBoxFragment(PassKey(), builder, borders, padding, + mathml_paint_info, block_or_line_writing_mode); return base::AdoptRef(static_cast<NGPhysicalBoxFragment*>(data)); } @@ -78,6 +84,7 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( NGBoxFragmentBuilder* builder, const NGPhysicalBoxStrut& borders, const NGPhysicalBoxStrut& padding, + std::unique_ptr<NGMathMLPaintInfo>& mathml_paint_info, WritingMode block_or_line_writing_mode) : NGPhysicalContainerFragment(builder, block_or_line_writing_mode, @@ -94,8 +101,9 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( has_fragment_items_ = true; NGFragmentItems* items = const_cast<NGFragmentItems*>(ComputeItemsAddress()); - items_builder->ToFragmentItems(block_or_line_writing_mode, - builder->Direction(), Size(), items); + DCHECK_EQ(items_builder->GetWritingMode(), block_or_line_writing_mode); + DCHECK_EQ(items_builder->Direction(), builder->Direction()); + items_builder->ToFragmentItems(Size(), items); } } @@ -105,6 +113,13 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( has_padding_ = !padding.IsZero(); if (has_padding_) *const_cast<NGPhysicalBoxStrut*>(ComputePaddingAddress()) = padding; + ink_overflow_computed_or_mathml_paint_info_ = !!mathml_paint_info; + if (ink_overflow_computed_or_mathml_paint_info_) { + memset(ComputeMathMLPaintInfoAddress(), 0, sizeof(NGMathMLPaintInfo)); + new (static_cast<void*>(ComputeMathMLPaintInfoAddress())) + NGMathMLPaintInfo(*mathml_paint_info); + } + is_first_for_node_ = builder->is_first_for_node_; is_fieldset_container_ = builder->is_fieldset_container_; is_legacy_layout_root_ = builder->is_legacy_layout_root_; @@ -130,6 +145,28 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( last_baseline_ = LayoutUnit::Min(); } + PhysicalSize size = Size(); + has_oof_positioned_fragmentainer_descendants_ = false; + if (!builder->oof_positioned_fragmentainer_descendants_.IsEmpty()) { + has_oof_positioned_fragmentainer_descendants_ = true; + Vector<NGPhysicalOutOfFlowPositionedNode>* + oof_positioned_fragmentainer_descendants = + const_cast<Vector<NGPhysicalOutOfFlowPositionedNode>*>( + ComputeOutOfFlowPositionedFragmentainerDescendantsAddress()); + new (oof_positioned_fragmentainer_descendants) + Vector<NGPhysicalOutOfFlowPositionedNode>(); + oof_positioned_fragmentainer_descendants->ReserveCapacity( + builder->oof_positioned_fragmentainer_descendants_.size()); + for (const auto& descendant : + builder->oof_positioned_fragmentainer_descendants_) { + oof_positioned_fragmentainer_descendants->emplace_back( + descendant.node, + descendant.static_position.ConvertToPhysical( + builder->Style().GetWritingMode(), builder->Direction(), size), + descendant.inline_container, descendant.containing_block_fragment); + } + } + #if DCHECK_IS_ON() CheckIntegrity(); #endif @@ -139,7 +176,7 @@ scoped_refptr<const NGLayoutResult> NGPhysicalBoxFragment::CloneAsHiddenForPaint() const { const ComputedStyle& style = Style(); NGBoxFragmentBuilder builder(GetMutableLayoutObject(), &style, - style.GetWritingMode(), style.Direction()); + style.GetWritingDirection()); builder.SetBoxType(BoxType()); NGFragmentGeometry initial_fragment_geometry{ Size().ConvertToLogical(style.GetWritingMode())}; @@ -327,7 +364,7 @@ PhysicalRect NGPhysicalBoxFragment::ScrollableOverflowFromChildren() const { } // Traverse child fragments. - const bool children_inline = IsInlineFormattingContext(); + const bool add_inline_children = !items && IsInlineFormattingContext(); // Only add overflow for fragments NG has not reflected into Legacy. // These fragments are: // - inline fragments, @@ -337,7 +374,7 @@ PhysicalRect NGPhysicalBoxFragment::ScrollableOverflowFromChildren() const { for (const auto& child : Children()) { if (child->IsFloatingOrOutOfFlowPositioned()) { context.AddFloatingOrOutOfFlowPositionedChild(*child, child.Offset()); - } else if (children_inline && child->IsLineBox()) { + } else if (add_inline_children && child->IsLineBox()) { context.AddLineBoxChild(To<NGPhysicalLineBoxFragment>(*child), child.Offset()); } 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 8b034f8a7db..d3ecedd282b 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 @@ -8,6 +8,7 @@ #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/inline/ng_fragment_items.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_mathml_paint_info.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" #include "third_party/blink/renderer/platform/graphics/scroll_types.h" #include "third_party/blink/renderer/platform/wtf/casting.h" @@ -29,6 +30,7 @@ class CORE_EXPORT NGPhysicalBoxFragment final NGBoxFragmentBuilder* builder, const NGPhysicalBoxStrut& borders, const NGPhysicalBoxStrut& padding, + std::unique_ptr<NGMathMLPaintInfo>& mathml_paint_info, WritingMode block_or_line_writing_mode); scoped_refptr<const NGLayoutResult> CloneAsHiddenForPaint() const; @@ -36,6 +38,8 @@ class CORE_EXPORT NGPhysicalBoxFragment final ~NGPhysicalBoxFragment() { if (has_fragment_items_) ComputeItemsAddress()->~NGFragmentItems(); + if (ink_overflow_computed_or_mathml_paint_info_) + ComputeMathMLPaintInfoAddress()->~NGMathMLPaintInfo(); for (const NGLink& child : Children()) child.fragment->Release(); } @@ -70,6 +74,20 @@ class CORE_EXPORT NGPhysicalBoxFragment final return *ComputePaddingAddress(); } + bool HasOutOfFlowPositionedFragmentainerDescendants() const { + return has_oof_positioned_fragmentainer_descendants_; + } + + base::span<NGPhysicalOutOfFlowPositionedNode> + OutOfFlowPositionedFragmentainerDescendants() const { + if (!HasOutOfFlowPositionedFragmentainerDescendants()) + return base::span<NGPhysicalOutOfFlowPositionedNode>(); + Vector<NGPhysicalOutOfFlowPositionedNode>* descendants = + const_cast<Vector<NGPhysicalOutOfFlowPositionedNode>*>( + ComputeOutOfFlowPositionedFragmentainerDescendantsAddress()); + return {descendants->data(), descendants->size()}; + } + NGPixelSnappedPhysicalBoxStrut PixelSnappedPadding() const { if (!has_padding_) return NGPixelSnappedPhysicalBoxStrut(); @@ -152,15 +170,23 @@ class CORE_EXPORT NGPhysicalBoxFragment final bool check_same_block_size) const; #endif + bool HasExtraMathMLPainting() const { + return IsMathMLFraction() || ink_overflow_computed_or_mathml_paint_info_; + } + private: const NGFragmentItems* ComputeItemsAddress() const { - DCHECK(has_fragment_items_ || has_borders_ || has_padding_); + DCHECK(has_fragment_items_ || has_borders_ || has_padding_ || + ink_overflow_computed_or_mathml_paint_info_ || + has_oof_positioned_fragmentainer_descendants_); const NGLink* children_end = children_ + Children().size(); return reinterpret_cast<const NGFragmentItems*>(children_end); } const NGPhysicalBoxStrut* ComputeBordersAddress() const { - DCHECK(has_borders_ || has_padding_); + DCHECK(has_borders_ || has_padding_ || + ink_overflow_computed_or_mathml_paint_info_ || + has_oof_positioned_fragmentainer_descendants_); const NGFragmentItems* items = ComputeItemsAddress(); if (!has_fragment_items_) return reinterpret_cast<const NGPhysicalBoxStrut*>(items); @@ -169,11 +195,31 @@ class CORE_EXPORT NGPhysicalBoxFragment final } const NGPhysicalBoxStrut* ComputePaddingAddress() const { - DCHECK(has_padding_); + DCHECK(has_padding_ || ink_overflow_computed_or_mathml_paint_info_ || + has_oof_positioned_fragmentainer_descendants_); const NGPhysicalBoxStrut* address = ComputeBordersAddress(); return has_borders_ ? address + 1 : address; } + NGMathMLPaintInfo* ComputeMathMLPaintInfoAddress() const { + DCHECK(ink_overflow_computed_or_mathml_paint_info_ || + has_oof_positioned_fragmentainer_descendants_); + NGPhysicalBoxStrut* address = + const_cast<NGPhysicalBoxStrut*>(ComputePaddingAddress()); + return has_padding_ ? reinterpret_cast<NGMathMLPaintInfo*>(address + 1) + : reinterpret_cast<NGMathMLPaintInfo*>(address); + } + + const Vector<NGPhysicalOutOfFlowPositionedNode>* + ComputeOutOfFlowPositionedFragmentainerDescendantsAddress() const { + DCHECK(has_oof_positioned_fragmentainer_descendants_); + NGMathMLPaintInfo* address = ComputeMathMLPaintInfoAddress(); + address = + ink_overflow_computed_or_mathml_paint_info_ ? address + 1 : address; + return reinterpret_cast<const Vector<NGPhysicalOutOfFlowPositionedNode>*>( + address); + } + #if DCHECK_IS_ON() void CheckIntegrity() const; #endif @@ -181,7 +227,8 @@ class CORE_EXPORT NGPhysicalBoxFragment final LayoutUnit baseline_; LayoutUnit last_baseline_; NGLink children_[]; - // borders and padding come from after |children_| if they are not zero. + // borders, padding, and oof_positioned_fragmentainer_descendants come after + // |children_| if they are not zero. }; template <> 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 929f8b346b1..5ddbaceda94 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 @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.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/ng/inline/ng_inline_cursor.h" @@ -19,11 +20,11 @@ namespace blink { namespace { struct SameSizeAsNGPhysicalContainerFragment : NGPhysicalFragment { + wtf_size_t size; void* break_token; std::unique_ptr<Vector<NGPhysicalOutOfFlowPositionedNode>> oof_positioned_descendants_; void* pointer; - wtf_size_t size; }; static_assert(sizeof(NGPhysicalContainerFragment) == @@ -39,13 +40,13 @@ NGPhysicalContainerFragment::NGPhysicalContainerFragment( NGFragmentType type, unsigned sub_type) : NGPhysicalFragment(builder, type, sub_type), + num_children_(builder->children_.size()), break_token_(std::move(builder->break_token_)), oof_positioned_descendants_( builder->oof_positioned_descendants_.IsEmpty() ? nullptr : new Vector<NGPhysicalOutOfFlowPositionedNode>()), - buffer_(buffer), - num_children_(builder->children_.size()) { + buffer_(buffer) { has_floating_descendants_for_paint_ = builder->has_floating_descendants_for_paint_; has_adjoining_object_descendants_ = @@ -71,11 +72,12 @@ NGPhysicalContainerFragment::NGPhysicalContainerFragment( // 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. + const WritingModeConverter converter( + {block_or_line_writing_mode, builder->Direction()}, size); wtf_size_t i = 0; for (auto& child : builder->children_) { - buffer[i].offset = child.offset.ConvertToPhysical( - block_or_line_writing_mode, builder->Direction(), size, - child.fragment->Size()); + buffer[i].offset = + converter.ToPhysical(child.offset, child.fragment->Size()); // Call the move constructor to move without |AddRef|. Fragments in // |builder| are not used after |this| was constructed. static_assert( @@ -110,6 +112,8 @@ void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren( } if (item.Type() == NGFragmentItem::kBox) { if (const NGPhysicalBoxFragment* child_box = item.BoxFragment()) { + if (const NGPhysicalFragment* post_layout = child_box->PostLayout()) + child_box = To<NGPhysicalBoxFragment>(post_layout); DCHECK(!child_box->IsOutOfFlowPositioned()); AddOutlineRectsForDescendant( {child_box, item.OffsetInContainerBlock()}, outline_rects, 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 f025de6caf1..a155b84e3f5 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 @@ -187,6 +187,7 @@ class CORE_EXPORT NGPhysicalContainerFragment : public NGPhysicalFragment { static bool DependsOnPercentageBlockSize(const NGContainerFragmentBuilder&); + wtf_size_t num_children_; scoped_refptr<const NGBreakToken> break_token_; const std::unique_ptr<Vector<NGPhysicalOutOfFlowPositionedNode>> oof_positioned_descendants_; @@ -194,7 +195,6 @@ class CORE_EXPORT NGPhysicalContainerFragment : public NGPhysicalFragment { // 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 NGLink* buffer_; - wtf_size_t num_children_; }; template <> 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 4d3daa543ce..c5370218ab6 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 @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/dom/document_lifecycle.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h" #include "third_party/blink/renderer/core/layout/layout_block.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" @@ -23,6 +24,9 @@ namespace { struct SameSizeAsNGPhysicalFragment : RefCounted<const NGPhysicalFragment, NGPhysicalFragmentTraits> { + // |flags_for_free_maybe| is used to support an additional increase in size + // needed for DCHECK and 32-bit builds. + unsigned flags_for_free_maybe; void* layout_object; PhysicalSize size; unsigned flags; @@ -213,18 +217,18 @@ void NGPhysicalFragmentTraits::Destruct(const NGPhysicalFragment* fragment) { NGPhysicalFragment::NGPhysicalFragment(NGFragmentBuilder* builder, NGFragmentType type, unsigned sub_type) - : layout_object_(builder->layout_object_), + : has_floating_descendants_for_paint_(false), + layout_object_(builder->layout_object_), size_(ToPhysicalSize(builder->size_, builder->GetWritingMode())), type_(type), sub_type_(sub_type), style_variant_((unsigned)builder->style_variant_), is_hidden_for_paint_(builder->is_hidden_for_paint_), - has_floating_descendants_for_paint_(false), is_fieldset_container_(false), is_legacy_layout_root_(false), is_painted_atomically_(false), has_baseline_(false) { - DCHECK(builder->layout_object_); + CHECK(builder->layout_object_); } NGPhysicalFragment::NGPhysicalFragment(LayoutObject* layout_object, @@ -232,18 +236,18 @@ NGPhysicalFragment::NGPhysicalFragment(LayoutObject* layout_object, PhysicalSize size, NGFragmentType type, unsigned sub_type) - : layout_object_(layout_object), + : has_floating_descendants_for_paint_(false), + layout_object_(layout_object), size_(size), type_(type), sub_type_(sub_type), style_variant_((unsigned)style_variant), is_hidden_for_paint_(false), - has_floating_descendants_for_paint_(false), is_fieldset_container_(false), is_legacy_layout_root_(false), is_painted_atomically_(false), has_baseline_(false) { - DCHECK(layout_object); + CHECK(layout_object); } // Keep the implementation of the destructor here, to avoid dependencies on @@ -295,7 +299,7 @@ bool NGPhysicalFragment::IsPlacedByLayoutNG() const { // to set. if (IsLineBox()) return false; - if (IsColumnBox()) + if (IsFragmentainerBox()) return true; const LayoutBlock* container = layout_object_->ContainingBlock(); if (!container) @@ -315,14 +319,15 @@ const FragmentData* NGPhysicalFragment::GetFragmentData() const { } 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; - } - } + const auto* layout_box = ToLayoutBoxOrNull(GetLayoutObject()); + if (UNLIKELY(!layout_box)) + return nullptr; + + if (layout_box->PhysicalFragmentCount() == 1) { + const NGPhysicalFragment* post_layout = layout_box->GetPhysicalFragment(0); + DCHECK(post_layout); + if (UNLIKELY(post_layout && post_layout != this)) + return post_layout; } return nullptr; } @@ -506,6 +511,18 @@ bool NGPhysicalFragment::ShouldPaintDragCaret() const { return false; } +LogicalRect NGPhysicalFragment::ConvertChildToLogical( + const PhysicalRect& physical_rect) const { + return WritingModeConverter(Style().GetWritingDirection(), Size()) + .ToLogical(physical_rect); +} + +PhysicalRect NGPhysicalFragment::ConvertChildToPhysical( + const LogicalRect& logical_rect) const { + return WritingModeConverter(Style().GetWritingDirection(), Size()) + .ToPhysical(logical_rect); +} + String NGPhysicalFragment::ToString() const { StringBuilder output; output.AppendFormat("Type: '%d' Size: '%s'", Type(), 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 4cebbebf948..d14c4b9eb1e 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 @@ -25,9 +25,9 @@ class FragmentData; class Node; class NGFragmentBuilder; class NGInlineItem; -class PaintLayer; - class NGPhysicalFragment; +class PaintLayer; +struct LogicalRect; struct CORE_EXPORT NGPhysicalFragmentTraits { static void Destruct(const NGPhysicalFragment*); @@ -98,6 +98,7 @@ class CORE_EXPORT NGPhysicalFragment bool IsColumnBox() const { return IsBox() && BoxType() == NGBoxType::kColumnBox; } + bool IsFragmentainerBox() const { return IsColumnBox(); } // An atomic inline is represented as a kFragmentBox, such as inline block and // replaced elements. bool IsAtomicInline() const { @@ -133,7 +134,7 @@ class CORE_EXPORT NGPhysicalFragment // // [1] https://www.w3.org/TR/css-display-3/#box-tree // [2] https://www.w3.org/TR/css-break-3/#fragmentation-container - bool IsCSSBox() const { return !IsLineBox() && !IsColumnBox(); } + bool IsCSSBox() const { return !IsLineBox() && !IsFragmentainerBox(); } bool IsBlockFlow() const; bool IsAnonymousBlock() const { @@ -142,6 +143,7 @@ class CORE_EXPORT NGPhysicalFragment bool IsListMarker() const { return IsCSSBox() && layout_object_->IsLayoutNGOutsideListMarker(); } + bool IsRubyRun() const { return layout_object_->IsRubyRun(); } // Return true if this fragment is a container established by a fieldset // element. Such a fragment contains an optional rendered legend fragment and @@ -336,6 +338,11 @@ class CORE_EXPORT NGPhysicalFragment // be confused with the CSS 'direction' property. TextDirection ResolvedDirection() const; + // Helper functions to convert between |PhysicalRect| and |LogicalRect| of a + // child. + LogicalRect ConvertChildToLogical(const PhysicalRect& physical_rect) const; + PhysicalRect ConvertChildToPhysical(const LogicalRect& logical_rect) const; + // Utility functions for caret painting. Note that carets are painted as part // of the containing block's foreground. bool ShouldPaintCursorCaret() const; @@ -386,14 +393,6 @@ class CORE_EXPORT NGPhysicalFragment const Vector<NGInlineItem>& InlineItemsOfContainingBlock() const; - 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 - const unsigned is_hidden_for_paint_ : 1; - // 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_for_paint_ : 1; @@ -405,8 +404,6 @@ class CORE_EXPORT NGPhysicalFragment // 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; - // base (line box) or resolve (text) direction - unsigned base_or_resolved_direction_ : 1; // TextDirection unsigned has_hanging_ : 1; // The following bitfields are only to be used by NGPhysicalBoxFragment @@ -416,8 +413,19 @@ class CORE_EXPORT NGPhysicalFragment unsigned border_edge_ : 4; // NGBorderEdges::Physical unsigned has_borders_ : 1; unsigned has_padding_ : 1; - unsigned is_math_fraction_ : 1; unsigned is_first_for_node_ : 1; + unsigned has_oof_positioned_fragmentainer_descendants_ : 1; + + 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 + const unsigned is_hidden_for_paint_ : 1; + unsigned is_math_fraction_ : 1; + // base (line box) or resolve (text) direction + unsigned base_or_resolved_direction_ : 1; // TextDirection // The following are only used by NGPhysicalBoxFragment but are initialized // for all types to allow methods using them to be inlined. @@ -429,10 +437,11 @@ class CORE_EXPORT NGPhysicalFragment // The following bitfields are only to be used by NGPhysicalTextFragment // (it's defined here to save memory, since that class has no bitfields). - mutable unsigned ink_overflow_computed_ : 1; + mutable unsigned ink_overflow_computed_or_mathml_paint_info_ : 1; // Note: We've used 32-bit bit field. If you need more bits, please think to - // share bit fields. + // share bit fields, or put them before layout_object_ to fill the gap after + // RefCounted on 64-bit systems. private: friend struct NGPhysicalFragmentTraits; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_positioned_float.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_positioned_float.h index e24bf5b2c18..84473ecc6d8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_positioned_float.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_positioned_float.h @@ -17,8 +17,11 @@ class NGLayoutResult; // Contains the information necessary for copying back data to a FloatingObject. struct CORE_EXPORT NGPositionedFloat { NGPositionedFloat(scoped_refptr<const NGLayoutResult> layout_result, - const NGBfcOffset& bfc_offset) - : layout_result(layout_result), bfc_offset(bfc_offset) {} + const NGBfcOffset& bfc_offset, + bool need_break_before = false) + : layout_result(layout_result), + bfc_offset(bfc_offset), + need_break_before(need_break_before) {} NGPositionedFloat(NGPositionedFloat&&) noexcept = default; NGPositionedFloat(const NGPositionedFloat&) = default; NGPositionedFloat& operator=(NGPositionedFloat&&) = default; @@ -26,6 +29,7 @@ struct CORE_EXPORT NGPositionedFloat { scoped_refptr<const NGLayoutResult> layout_result; NGBfcOffset bfc_offset; + bool need_break_before = false; }; } // namespace blink 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 index 58f9ccbd1c8..400a7226def 100644 --- 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 @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h" +#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.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" @@ -24,8 +25,7 @@ NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm( const NGLayoutResult& result) : NGLayoutAlgorithm(params), previous_result_(result), - writing_mode_(Style().GetWritingMode()), - direction_(Style().Direction()) { + writing_direction_(Style().GetWritingDirection()) { // 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. @@ -112,20 +112,19 @@ NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm( container_builder_.SetOverflowBlockSize(result.OverflowBlockSize()); LayoutUnit new_block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), - container_builder_.Borders() + container_builder_.Padding(), - result.IntrinsicBlockSize(), + ConstraintSpace(), Style(), BorderPadding(), result.IntrinsicBlockSize(), container_builder_.InitialBorderBoxSize().inline_size); // Only block-flow is allowed to change its block-size during "simplified" // layout, all other layout types must remain the same size. if (is_block_flow) { - container_builder_.SetBlockSize(new_block_size); + container_builder_.SetFragmentBlockSize(new_block_size); } else { LayoutUnit old_block_size = - NGFragment(writing_mode_, physical_fragment).BlockSize(); + NGFragment(writing_direction_.GetWritingMode(), physical_fragment) + .BlockSize(); DCHECK_EQ(old_block_size, new_block_size); - container_builder_.SetBlockSize(old_block_size); + container_builder_.SetFragmentBlockSize(old_block_size); } // We need the previous physical container size to calculate the position of @@ -193,8 +192,8 @@ scoped_refptr<const NGLayoutResult> NGSimplifiedLayoutAlgorithm::Layout() { if (const NGFragmentItems* previous_items = previous_fragment.Items()) { auto* items_builder = container_builder_.ItemsBuilder(); DCHECK(items_builder); - items_builder->AddPreviousItems(*previous_items, writing_mode_, - direction_, + DCHECK_EQ(items_builder->GetWritingDirection(), writing_direction_); + items_builder->AddPreviousItems(*previous_items, previous_physical_container_size_); } } @@ -217,7 +216,7 @@ scoped_refptr<const NGLayoutResult> NGSimplifiedLayoutAlgorithm::Layout() { NOINLINE scoped_refptr<const NGLayoutResult> NGSimplifiedLayoutAlgorithm::LayoutWithItemsBuilder() { - NGFragmentItemsBuilder items_builder; + NGFragmentItemsBuilder items_builder(writing_direction_); container_builder_.SetItemsBuilder(&items_builder); scoped_refptr<const NGLayoutResult> result = Layout(); // Ensure stack-allocated |NGFragmentItemsBuilder| is not used anymore. @@ -233,9 +232,10 @@ void NGSimplifiedLayoutAlgorithm::AddChildFragment( DCHECK_EQ(old_fragment->Size(), new_fragment.Size()); // Determine the previous position in the logical coordinate system. - LogicalOffset child_offset = old_fragment.Offset().ConvertToLogical( - writing_mode_, direction_, previous_physical_container_size_, - new_fragment.Size()); + LogicalOffset child_offset = + WritingModeConverter(writing_direction_, + previous_physical_container_size_) + .ToLogical(old_fragment.Offset(), new_fragment.Size()); // Add the new fragment to the builder. container_builder_.AddChild(new_fragment, child_offset); 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 index bebbcf7425c..54963f70e9e 100644 --- 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 @@ -57,8 +57,7 @@ class CORE_EXPORT NGSimplifiedLayoutAlgorithm const NGLayoutResult& previous_result_; NGBoxStrut border_scrollbar_padding_; - const WritingMode writing_mode_; - const TextDirection direction_; + const WritingDirectionMode writing_direction_; PhysicalSize previous_physical_container_size_; }; 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 bcda1289de2..1b4213395d2 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 @@ -25,6 +25,7 @@ bool AdjustToClearance(LayoutUnit clearance_offset, NGBfcOffset* offset) { NGConstraintSpace CreateIndefiniteConstraintSpaceForChild( const ComputedStyle& container_style, + const MinMaxSizesInput& input, NGLayoutInputNode child) { WritingMode parent_writing_mode = container_style.GetWritingMode(); WritingMode child_writing_mode = child.Style().GetWritingMode(); @@ -37,7 +38,8 @@ NGConstraintSpace CreateIndefiniteConstraintSpaceForChild( builder.SetCacheSlot(NGCacheSlot::kMeasure); builder.SetAvailableSize(indefinite_size); - builder.SetPercentageResolutionSize(indefinite_size); + builder.SetPercentageResolutionSize( + {kIndefiniteSize, input.percentage_resolution_block_size}); builder.SetReplacedPercentageResolutionSize(indefinite_size); builder.SetIsShrinkToFit(child.Style().LogicalWidth().IsAuto()); return builder.ToConstraintSpace(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.h index af96da0cb51..916a2898678 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.h @@ -21,12 +21,14 @@ CORE_EXPORT bool AdjustToClearance(LayoutUnit clearance_offset, NGBfcOffset* offset); // Create a child constraint space with no sizing data, except for fallback -// inline sizing for orthongonal flow roots. This will not and can not be used -// for final layout, but is needed in an intermediate measure pass that -// calculates the min/max size contribution from a child that establishes an -// orthogonal flow root. +// inline sizing for orthogonal flow roots and a percentage resolution block +// size based on |input| (for calculating aspect-ratio based sizes). This will +// not and can not be used for final layout, but is needed in an intermediate +// measure pass that calculates the min/max size contribution from a child that +// establishes an orthogonal flow root. NGConstraintSpace CreateIndefiniteConstraintSpaceForChild( const ComputedStyle& container_style, + const MinMaxSizesInput& input, NGLayoutInputNode child); // Calculate and set the available inline fallback size for orthogonal flow 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 b67b5756cc9..d45bd12d0f5 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 @@ -37,10 +37,12 @@ int NGTextDecorationOffset::ComputeUnderlineOffsetForUnder( int offset_int = offset.Floor(); // Gaps are not needed for TextTop because it generally has internal - // leadings. + // leadings. Overline needs to grow upwards, hence subtract thickness. if (position_type == FontVerticalPositionType::TextTop) - return offset_int; - return !IsLineOverSide(position_type) ? offset_int + 1 : offset_int - 1; + return offset_int - floorf(text_decoration_thickness); + return !IsLineOverSide(position_type) + ? offset_int + 1 + : offset_int - 1 - floorf(text_decoration_thickness); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.cc b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.cc index 5fed73b3c50..90f2147df54 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.cc @@ -14,6 +14,19 @@ namespace blink { +namespace { + +inline bool NeedsTableSection(const LayoutObject& object) { + // Return true if 'object' can't exist in an anonymous table without being + // wrapped in a table section box. + EDisplay display = object.StyleRef().Display(); + return display != EDisplay::kTableCaption && + display != EDisplay::kTableColumnGroup && + display != EDisplay::kTableColumn; +} + +} // namespace + LayoutNGTable::LayoutNGTable(Element* element) : LayoutNGMixin<LayoutBlock>(element) {} @@ -33,6 +46,61 @@ void LayoutNGTable::UpdateBlockLayout(bool relayout_children) { UpdateInFlowBlockLayout(); } +void LayoutNGTable::AddChild(LayoutObject* child, LayoutObject* before_child) { + bool wrap_in_anonymous_section = !child->IsTableCaption() && + !child->IsLayoutTableCol() && + !child->IsTableSection(); + + if (!wrap_in_anonymous_section) { + if (before_child && before_child->Parent() != this) + before_child = SplitAnonymousBoxesAroundChild(before_child); + LayoutBox::AddChild(child, before_child); + return; + } + + if (!before_child && LastChild() && LastChild()->IsTableSection() && + LastChild()->IsAnonymous() && !LastChild()->IsBeforeContent()) { + LastChild()->AddChild(child); + return; + } + + if (before_child && !before_child->IsAnonymous() && + before_child->Parent() == this) { + LayoutNGTableSection* section = + DynamicTo<LayoutNGTableSection>(before_child->PreviousSibling()); + if (section && section->IsAnonymous()) { + section->AddChild(child); + return; + } + } + + LayoutObject* last_box = before_child; + while (last_box && last_box->Parent()->IsAnonymous() && + !last_box->IsTableSection() && NeedsTableSection(*last_box)) + last_box = last_box->Parent(); + if (last_box && last_box->IsAnonymous() && last_box->IsTablePart() && + !IsAfterContent(last_box)) { + if (before_child == last_box) + before_child = last_box->SlowFirstChild(); + last_box->AddChild(child, before_child); + return; + } + + if (before_child && !before_child->IsTableSection() && + NeedsTableSection(*before_child)) + before_child = nullptr; + + LayoutBox* section = + LayoutObjectFactory::CreateAnonymousTableSectionWithParent(*this); + AddChild(section, before_child); + section->AddChild(child); +} + +LayoutBox* LayoutNGTable::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableWithParent(*parent); +} + bool LayoutNGTable::IsFirstCell(const LayoutNGTableCellInterface& cell) const { const LayoutNGTableRowInterface* row = cell.RowInterface(); if (row->FirstCellInterface() != &cell) diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h index 06186b60675..e82945eff9a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h @@ -33,6 +33,12 @@ class CORE_EXPORT LayoutNGTable : public LayoutNGMixin<LayoutBlock>, void UpdateBlockLayout(bool relayout_children) override; + void AddChild(LayoutObject* child, + LayoutObject* before_child = nullptr) override; + + LayoutBox* CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const override; + // LayoutBlock methods end. // LayoutNGTableInterface methods start. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.cc b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.cc index 8f501b93c79..cca5eb8198d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.cc @@ -37,6 +37,11 @@ void LayoutNGTableCell::ColSpanOrRowSpanChanged() { UpdateColAndRowSpanFlags(); } +LayoutBox* LayoutNGTableCell::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableCellWithParent(*parent); +} + Length LayoutNGTableCell::StyleOrColLogicalWidth() const { // TODO(atotic) TablesNG cannot easily get col width before layout. return StyleRef().LogicalWidth(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h index 4c70aef0a31..c1295a4b73f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h @@ -37,6 +37,9 @@ class CORE_EXPORT LayoutNGTableCell // compat. const char* GetName() const final { return "LayoutNGTableCellNew"; } + LayoutBox* CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const override; + // LayoutBlockFlow methods end. // LayoutNGTableCellInterface methods start. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.cc b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.cc index 48db1645c21..d790450ca6a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h" #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row_interface.h" @@ -20,6 +21,57 @@ bool LayoutNGTableRow::IsEmpty() const { return !FirstChild(); } +void LayoutNGTableRow::AddChild(LayoutObject* child, + LayoutObject* before_child) { + if (!child->IsTableCell()) { + LayoutObject* last = before_child; + if (!last) + last = LastCell(); + if (last && last->IsAnonymous() && last->IsTableCell() && + !last->IsBeforeOrAfterContent()) { + LayoutBlockFlow* last_cell = To<LayoutBlockFlow>(last); + if (before_child == last_cell) + before_child = last_cell->FirstChild(); + last_cell->AddChild(child, before_child); + return; + } + + if (before_child && !before_child->IsAnonymous() && + before_child->Parent() == this) { + LayoutObject* cell = before_child->PreviousSibling(); + if (cell && cell->IsTableCell() && cell->IsAnonymous()) { + cell->AddChild(child); + return; + } + } + + // If before_child is inside an anonymous cell, insert into the cell. + if (last && !last->IsTableCell() && last->Parent() && + last->Parent()->IsAnonymous() && + !last->Parent()->IsBeforeOrAfterContent()) { + last->Parent()->AddChild(child, before_child); + return; + } + + LayoutBlockFlow* cell = + LayoutObjectFactory::CreateAnonymousTableCellWithParent(*this); + AddChild(cell, before_child); + cell->AddChild(child); + return; + } + + if (before_child && before_child->Parent() != this) + before_child = SplitAnonymousBoxesAroundChild(before_child); + + DCHECK(!before_child || before_child->IsTableCell()); + LayoutNGMixin<LayoutBlock>::AddChild(child, before_child); +} + +LayoutBox* LayoutNGTableRow::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableRowWithParent(*parent); +} + unsigned LayoutNGTableRow::RowIndex() const { unsigned index = 0; for (LayoutObject* child = Parent()->SlowFirstChild(); child; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h index 8b08dd07582..14048ea90b6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_row.h @@ -28,6 +28,12 @@ class CORE_EXPORT LayoutNGTableRow : public LayoutNGMixin<LayoutBlock>, const char* GetName() const override { return "LayoutNGTableRow"; } + void AddChild(LayoutObject* child, + LayoutObject* before_child = nullptr) override; + + LayoutBox* CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const override; + // Whether a row has opaque background depends on many factors, e.g. border // spacing, border collapsing, missing cells, etc. // For simplicity, just conservatively assume all table rows are not opaque. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.cc b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.cc index 06225acabcb..65b2c1fbbb3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h" #include "third_party/blink/renderer/core/layout/layout_analyzer.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h" #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h" @@ -25,6 +26,58 @@ bool LayoutNGTableSection::IsEmpty() const { return true; } +void LayoutNGTableSection::AddChild(LayoutObject* child, + LayoutObject* before_child) { + if (!child->IsTableRow()) { + LayoutObject* last = before_child; + if (!last) + last = LastChild(); + if (last && last->IsAnonymous() && last->IsTablePart() && + !last->IsBeforeOrAfterContent()) { + if (before_child == last) + before_child = last->SlowFirstChild(); + last->AddChild(child, before_child); + return; + } + + if (before_child && !before_child->IsAnonymous() && + before_child->Parent() == this) { + LayoutObject* row = before_child->PreviousSibling(); + if (row && row->IsTableRow() && row->IsAnonymous()) { + row->AddChild(child); + return; + } + } + + // If before_child is inside an anonymous cell/row, insert into the cell or + // into the anonymous row containing it, if there is one. + LayoutObject* last_box = last; + while (last_box && last_box->Parent()->IsAnonymous() && + !last_box->IsTableRow()) + last_box = last_box->Parent(); + if (last_box && last_box->IsAnonymous() && + !last_box->IsBeforeOrAfterContent()) { + last_box->AddChild(child, before_child); + return; + } + + LayoutObject* row = + LayoutObjectFactory::CreateAnonymousTableRowWithParent(*this); + AddChild(row, before_child); + row->AddChild(child); + return; + } + if (before_child && before_child->Parent() != this) + before_child = SplitAnonymousBoxesAroundChild(before_child); + + LayoutNGMixin<LayoutBlock>::AddChild(child, before_child); +} + +LayoutBox* LayoutNGTableSection::CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const { + return LayoutObjectFactory::CreateAnonymousTableSectionWithParent(*parent); +} + LayoutNGTableInterface* LayoutNGTableSection::TableInterface() const { return ToInterface<LayoutNGTableInterface>(Parent()); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h index 64136c51551..8e9420392a3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h @@ -27,6 +27,12 @@ class CORE_EXPORT LayoutNGTableSection : public LayoutNGMixin<LayoutBlock>, const char* GetName() const override { return "LayoutNGTableSection"; } + void AddChild(LayoutObject* child, + LayoutObject* before_child = nullptr) override; + + LayoutBox* CreateAnonymousBoxWithSameTypeAs( + const LayoutObject* parent) const override; + bool AllowsOverflowClip() const override { return false; } bool BackgroundIsKnownToBeOpaqueInRect(const PhysicalRect&) const override { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc new file mode 100644 index 00000000000..1d54f04fbe6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc @@ -0,0 +1,631 @@ +// Copyright 2020 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/table/ng_table_layout_algorithm_helpers.h" + +#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" + +namespace blink { + +namespace { + +// Implements spec distribution algorithm: +// https://www.w3.org/TR/css-tables-3/#width-distribution-algorithm +void DistributeInlineSizeToComputedInlineSizeAuto( + LayoutUnit target_inline_size, + LayoutUnit inline_border_spacing, + NGTableTypes::Column* start_column, + NGTableTypes::Column* end_column, + NGTableTypes::Columns* column_constraints) { + if (column_constraints->size() == 0) + return; + + unsigned all_columns_count = 0; + unsigned percent_columns_count = 0; + unsigned fixed_columns_count = 0; + unsigned auto_columns_count = 0; + + // What guesses mean is described in table specification. + // https://www.w3.org/TR/css-tables-3/#width-distribution-algorithm + enum { kMinGuess, kPercentageGuess, kSpecifiedGuess, kMaxGuess, kAboveMax }; + // sizes are collected for all guesses except kAboveMax + LayoutUnit guess_sizes[kAboveMax]; + LayoutUnit guess_size_total_increases[kAboveMax]; + float total_percent = 0.0f; + LayoutUnit total_auto_max_inline_size; + LayoutUnit total_fixed_max_inline_size; + + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + all_columns_count++; + if (!column->min_inline_size) + column->min_inline_size = LayoutUnit(); + if (!column->max_inline_size) + column->max_inline_size = LayoutUnit(); + if (column->percent) { + percent_columns_count++; + total_percent += *column->percent; + LayoutUnit percent_inline_size = + column->ResolvePercentInlineSize(target_inline_size); + guess_sizes[kMinGuess] += *column->min_inline_size; + guess_sizes[kPercentageGuess] += percent_inline_size; + guess_sizes[kSpecifiedGuess] += percent_inline_size; + guess_sizes[kMaxGuess] += percent_inline_size; + guess_size_total_increases[kPercentageGuess] += + percent_inline_size - *column->min_inline_size; + } else if (column->is_constrained) { // Fixed column + fixed_columns_count++; + total_fixed_max_inline_size += *column->max_inline_size; + guess_sizes[kMinGuess] += *column->min_inline_size; + guess_sizes[kPercentageGuess] += *column->min_inline_size; + guess_sizes[kSpecifiedGuess] += *column->max_inline_size; + guess_sizes[kMaxGuess] += *column->max_inline_size; + guess_size_total_increases[kSpecifiedGuess] += + *column->max_inline_size - *column->min_inline_size; + } else { // Auto column + auto_columns_count++; + total_auto_max_inline_size += *column->max_inline_size; + guess_sizes[kMinGuess] += *column->min_inline_size; + guess_sizes[kPercentageGuess] += *column->min_inline_size; + guess_sizes[kSpecifiedGuess] += *column->min_inline_size; + guess_sizes[kMaxGuess] += *column->max_inline_size; + guess_size_total_increases[kMaxGuess] += + *column->max_inline_size - *column->min_inline_size; + } + } + // Distributing inline sizes can never cause cells to be < min_inline_size. + // Target inline size must be wider than sum of min inline sizes. + // This is always true for assignable_table_inline_size, but not for + // colspan_cells. + target_inline_size = std::max(target_inline_size, guess_sizes[kMinGuess]); + + unsigned starting_guess = kAboveMax; + for (unsigned i = kMinGuess; i != kAboveMax; ++i) { + if (guess_sizes[i] >= target_inline_size) { + starting_guess = i; + break; + } + } + switch (starting_guess) { + case kMinGuess: { + // All columns are min inline size. + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + column->computed_inline_size = + column->min_inline_size.value_or(LayoutUnit()); + } + } break; + case kPercentageGuess: { + // Percent columns grow, auto/fixed get min inline size. + LayoutUnit percent_inline_size_increases = + guess_size_total_increases[kPercentageGuess]; + LayoutUnit distributable_inline_size = + target_inline_size - guess_sizes[kMinGuess]; + LayoutUnit rounding_error_inline_size = distributable_inline_size; + NGTableTypes::Column* last_column = nullptr; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) { + last_column = column; + LayoutUnit percent_inline_size = + column->ResolvePercentInlineSize(target_inline_size); + LayoutUnit column_inline_size_increase = + percent_inline_size - *column->min_inline_size; + LayoutUnit delta; + if (percent_inline_size_increases != LayoutUnit()) { + delta = LayoutUnit(distributable_inline_size * + column_inline_size_increase.ToFloat() / + percent_inline_size_increases); + } else { + delta = LayoutUnit(distributable_inline_size.ToFloat() / + percent_columns_count); + } + rounding_error_inline_size -= delta; + column->computed_inline_size = *column->min_inline_size + delta; + } else { + // Auto/Fixed columns get min inline size. + column->computed_inline_size = *column->min_inline_size; + } + } + if (rounding_error_inline_size != LayoutUnit()) { + DCHECK(last_column); + last_column->computed_inline_size += rounding_error_inline_size; + } + } break; + case kSpecifiedGuess: { + // Fixed columns grow, auto gets min, percent gets %max + LayoutUnit fixed_inline_size_increase = + guess_size_total_increases[kSpecifiedGuess]; + LayoutUnit distributable_inline_size = + target_inline_size - guess_sizes[kPercentageGuess]; + LayoutUnit rounding_error_inline_size = distributable_inline_size; + NGTableTypes::Column* last_column = nullptr; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) { + column->computed_inline_size = + column->ResolvePercentInlineSize(target_inline_size); + } else if (column->is_constrained) { + last_column = column; + LayoutUnit column_inline_size_increase = + *column->max_inline_size - *column->min_inline_size; + LayoutUnit delta; + if (fixed_inline_size_increase != LayoutUnit()) { + delta = LayoutUnit(distributable_inline_size * + column_inline_size_increase.ToFloat() / + fixed_inline_size_increase); + } else { + delta = LayoutUnit(distributable_inline_size.ToFloat() / + fixed_columns_count); + } + rounding_error_inline_size -= delta; + column->computed_inline_size = *column->min_inline_size + delta; + } else { + column->computed_inline_size = *column->min_inline_size; + } + } + if (rounding_error_inline_size != LayoutUnit()) { + DCHECK(last_column); + last_column->computed_inline_size += rounding_error_inline_size; + } + } break; + case kMaxGuess: { + // Auto columns grow, fixed gets max, percent gets %max + LayoutUnit auto_inline_size_increase = + guess_size_total_increases[kMaxGuess]; + LayoutUnit distributable_inline_size = + target_inline_size - guess_sizes[kSpecifiedGuess]; + LayoutUnit rounding_error_inline_size = distributable_inline_size; + NGTableTypes::Column* last_column = nullptr; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) { + column->computed_inline_size = + column->ResolvePercentInlineSize(target_inline_size); + } else if (column->is_constrained) { + column->computed_inline_size = *column->max_inline_size; + } else { + last_column = column; + LayoutUnit column_inline_size_increase = + *column->max_inline_size - *column->min_inline_size; + LayoutUnit delta; + if (auto_inline_size_increase != LayoutUnit()) { + delta = LayoutUnit(distributable_inline_size * + column_inline_size_increase.ToFloat() / + auto_inline_size_increase); + } else { + delta = LayoutUnit(distributable_inline_size.ToFloat() / + auto_columns_count); + } + rounding_error_inline_size -= delta; + column->computed_inline_size = *column->min_inline_size + delta; + } + } + if (rounding_error_inline_size != LayoutUnit()) { + DCHECK(last_column); + last_column->computed_inline_size += rounding_error_inline_size; + } + } break; + case kAboveMax: { + LayoutUnit distributable_inline_size = + target_inline_size - guess_sizes[kMaxGuess]; + if (auto_columns_count > 0) { + // Grow auto columns if available + LayoutUnit rounding_error_inline_size = distributable_inline_size; + NGTableTypes::Column* last_column = nullptr; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) { + column->computed_inline_size = + column->ResolvePercentInlineSize(target_inline_size); + } else if (column->is_constrained) { + column->computed_inline_size = *column->max_inline_size; + } else { + last_column = column; + LayoutUnit delta; + if (total_auto_max_inline_size > LayoutUnit()) { + delta = LayoutUnit(distributable_inline_size * + (*column->max_inline_size).ToFloat() / + total_auto_max_inline_size); + } else { + delta = distributable_inline_size / auto_columns_count; + } + rounding_error_inline_size -= delta; + column->computed_inline_size = *column->max_inline_size + delta; + } + } + if (rounding_error_inline_size != LayoutUnit()) { + DCHECK(last_column); + last_column->computed_inline_size += rounding_error_inline_size; + } + } else if (fixed_columns_count > 0) { + // Grow fixed columns if available. + LayoutUnit rounding_error_inline_size = distributable_inline_size; + NGTableTypes::Column* last_column = nullptr; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) { + column->computed_inline_size = + column->ResolvePercentInlineSize(target_inline_size); + } else if (column->is_constrained) { + last_column = column; + LayoutUnit delta; + if (total_fixed_max_inline_size > LayoutUnit()) { + delta = LayoutUnit(distributable_inline_size * + (*column->max_inline_size).ToFloat() / + total_fixed_max_inline_size); + } else { + delta = distributable_inline_size / fixed_columns_count; + } + rounding_error_inline_size -= delta; + column->computed_inline_size = *column->max_inline_size + delta; + } else { + DCHECK(false); + } + } + if (rounding_error_inline_size != LayoutUnit()) { + DCHECK(last_column); + last_column->computed_inline_size += rounding_error_inline_size; + } + } else if (percent_columns_count > 0) { + // Grow percent columns. + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) { + if (total_percent > 0.0f) { + column->computed_inline_size = LayoutUnit( + *column->percent / total_percent * target_inline_size); + } else { + column->computed_inline_size = + distributable_inline_size / percent_columns_count; + } + } else { + DCHECK(false); + } + } + } + } + } + +#if DCHECK_IS_ON() + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + DCHECK_NE(column->computed_inline_size, kIndefiniteSize); + } +#endif +} + +void SynchronizeAssignableTableInlineSizeAndColumnsFixed( + LayoutUnit target_inline_size, + LayoutUnit inline_border_spacing, + NGTableTypes::Column* start_column, + NGTableTypes::Column* end_column) { + DCHECK_NE(start_column, end_column); + unsigned all_columns_count = 0; + unsigned percent_columns_count = 0; + unsigned auto_columns_count = 0; + unsigned auto_empty_columns_count = 0; + unsigned fixed_columns_count = 0; + + float total_percent = 0.0f; + LayoutUnit total_percent_inline_size; + LayoutUnit total_auto_max_inline_size; + LayoutUnit total_fixed_inline_size; + LayoutUnit assigned_inline_size; + + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + all_columns_count++; + if (!column->min_inline_size) + column->min_inline_size = LayoutUnit(); + if (!column->max_inline_size) + column->max_inline_size = LayoutUnit(); + if (column->percent) { + percent_columns_count++; + total_percent += *column->percent; + total_percent_inline_size += + LayoutUnit(*column->percent / 100 * target_inline_size); + } else if (column->is_constrained) { // Fixed column + fixed_columns_count++; + total_fixed_inline_size += *column->max_inline_size; + } else { + auto_columns_count++; + if (*column->max_inline_size == LayoutUnit()) + auto_empty_columns_count++; + total_auto_max_inline_size += *column->max_inline_size; + } + } + + NGTableTypes::Column* last_distributed_column = nullptr; + // Distribute to fixed columns. + if (fixed_columns_count > 0) { + float scale = 1.0f; + bool scale_available = true; + LayoutUnit target_fixed_size = + (target_inline_size - total_percent_inline_size).ClampNegativeToZero(); + bool scale_up = + total_fixed_inline_size < target_fixed_size && auto_columns_count == 0; + // Fixed columns grow if there are no auto columns. They fill up space not + // taken up by percentage columns. + bool scale_down = total_fixed_inline_size > target_inline_size; + if (scale_up || scale_down) { + if (total_fixed_inline_size != LayoutUnit()) { + scale = target_fixed_size.ToFloat() / total_fixed_inline_size; + } else { + scale_available = false; + } + } + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (!column->IsFixed()) + continue; + last_distributed_column = column; + if (scale_available) { + column->computed_inline_size = + LayoutUnit(scale * *column->max_inline_size); + } else { + DCHECK_EQ(fixed_columns_count, all_columns_count); + column->computed_inline_size = + LayoutUnit(target_inline_size.ToFloat() / fixed_columns_count); + } + assigned_inline_size += column->computed_inline_size; + } + } + if (assigned_inline_size >= target_inline_size) + return; + // Distribute to percent columns. + if (percent_columns_count > 0) { + float scale = 1.0f; + bool scale_available = true; + // Percent columns only grow if there are no auto columns. + bool scale_up = total_percent_inline_size < + (target_inline_size - assigned_inline_size) && + auto_columns_count == 0; + bool scale_down = + total_percent_inline_size > (target_inline_size - assigned_inline_size); + if (scale_up || scale_down) { + if (total_percent_inline_size != LayoutUnit()) { + scale = (target_inline_size - assigned_inline_size).ToFloat() / + total_percent_inline_size; + } else { + scale_available = false; + } + } + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (!column->percent) + continue; + last_distributed_column = column; + if (scale_available) { + column->computed_inline_size = + LayoutUnit(scale * *column->percent / 100 * target_inline_size); + } else { + column->computed_inline_size = + LayoutUnit((target_inline_size - assigned_inline_size).ToFloat() / + percent_columns_count); + } + assigned_inline_size += column->computed_inline_size; + } + } + // Distribute to auto columns. + LayoutUnit distributing_inline_size = + target_inline_size - assigned_inline_size; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent || column->is_constrained) + continue; + last_distributed_column = column; + column->computed_inline_size = + LayoutUnit(distributing_inline_size / float(auto_columns_count)); + assigned_inline_size += column->computed_inline_size; + } + LayoutUnit delta = target_inline_size - assigned_inline_size; + last_distributed_column->computed_inline_size += delta; +} + +void DistributeColspanCellToColumnsFixed( + const NGTableTypes::ColspanCell& colspan_cell, + LayoutUnit inline_border_spacing, + NGTableTypes::Columns* column_constraints) { + // Fixed layout does not merge columns. + DCHECK_LE(colspan_cell.span, + column_constraints->size() - colspan_cell.start_column); + NGTableTypes::Column* start_column = + &(*column_constraints)[colspan_cell.start_column]; + NGTableTypes::Column* end_column = start_column + colspan_cell.span; + DCHECK_NE(start_column, end_column); + + LayoutUnit colspan_cell_min_inline_size; + LayoutUnit colspan_cell_max_inline_size; + if (colspan_cell.cell_inline_constraint.is_constrained) { + colspan_cell_min_inline_size = + (colspan_cell.cell_inline_constraint.min_inline_size - + (colspan_cell.span - 1) * inline_border_spacing) + .ClampNegativeToZero(); + colspan_cell_max_inline_size = + (colspan_cell.cell_inline_constraint.max_inline_size - + (colspan_cell.span - 1) * inline_border_spacing) + .ClampNegativeToZero(); + } + + // Distribute min/max/percentage evenly between all cells. + // Colspanned cells only distribute min inline size if constrained. + LayoutUnit rounding_error_min_inline_size = colspan_cell_min_inline_size; + LayoutUnit rounding_error_max_inline_size = colspan_cell_max_inline_size; + float rounding_error_percent = + colspan_cell.cell_inline_constraint.percent.value_or(0.0f); + + LayoutUnit new_min_size = LayoutUnit(colspan_cell_min_inline_size / + static_cast<float>(colspan_cell.span)); + LayoutUnit new_max_size = LayoutUnit(colspan_cell_max_inline_size / + static_cast<float>(colspan_cell.span)); + base::Optional<float> new_percent; + if (colspan_cell.cell_inline_constraint.percent) { + new_percent = + *colspan_cell.cell_inline_constraint.percent / colspan_cell.span; + } + + NGTableTypes::Column* last_column; + for (NGTableTypes::Column* column = start_column; column < end_column; + ++column) { + last_column = column; + rounding_error_min_inline_size -= new_min_size; + rounding_error_max_inline_size -= new_max_size; + if (new_percent) + rounding_error_percent -= *new_percent; + + if (!column->min_inline_size) { + column->is_constrained |= + colspan_cell.cell_inline_constraint.is_constrained; + column->min_inline_size = new_min_size; + } + if (!column->max_inline_size) { + column->is_constrained |= + colspan_cell.cell_inline_constraint.is_constrained; + column->max_inline_size = new_max_size; + } + if (!column->percent && new_percent) + column->percent = new_percent; + } + last_column->min_inline_size = + *last_column->min_inline_size + rounding_error_min_inline_size; + last_column->max_inline_size = + *last_column->max_inline_size + rounding_error_max_inline_size; + if (new_percent) + last_column->percent = *last_column->percent + rounding_error_percent; +} + +void DistributeColspanCellToColumnsAuto( + const NGTableTypes::ColspanCell& colspan_cell, + LayoutUnit inline_border_spacing, + NGTableTypes::Columns* column_constraints) { + unsigned effective_span = + std::min(colspan_cell.span, + column_constraints->size() - colspan_cell.start_column); + NGTableTypes::Column* start_column = + &(*column_constraints)[colspan_cell.start_column]; + NGTableTypes::Column* end_column = start_column + effective_span; + + // Inline sizes for redistribution exclude border spacing. + LayoutUnit colspan_cell_min_inline_size = + (colspan_cell.cell_inline_constraint.min_inline_size - + (effective_span - 1) * inline_border_spacing) + .ClampNegativeToZero(); + LayoutUnit colspan_cell_max_inline_size = + (colspan_cell.cell_inline_constraint.max_inline_size - + (effective_span - 1) * inline_border_spacing) + .ClampNegativeToZero(); + base::Optional<float> colspan_cell_percent = + colspan_cell.cell_inline_constraint.percent; + + if (colspan_cell_percent.has_value()) { + float columns_percent = 0.0f; + unsigned all_columns_count = 0; + unsigned percent_columns_count = 0; + unsigned nonpercent_columns_count = 0; + LayoutUnit nonpercent_columns_max_inline_size; + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (!column->max_inline_size) + column->max_inline_size = LayoutUnit(); + if (!column->min_inline_size) + column->min_inline_size = LayoutUnit(); + all_columns_count++; + if (column->percent) { + percent_columns_count++; + columns_percent += *column->percent; + } else { + nonpercent_columns_count++; + nonpercent_columns_max_inline_size += *column->max_inline_size; + } + } + float surplus_percent = *colspan_cell_percent - columns_percent; + if (surplus_percent > 0.0f && all_columns_count > percent_columns_count) { + // Distribute surplus percent to non-percent columns in proportion to + // max_inline_size. + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + if (column->percent) + continue; + float column_percent; + if (nonpercent_columns_max_inline_size != LayoutUnit()) { + // Column percentage is proportional to its max_inline_size. + column_percent = surplus_percent * + column->max_inline_size.value_or(LayoutUnit()) / + nonpercent_columns_max_inline_size; + } else { + // Distribute evenly instead. + // Legacy difference: Legacy forces max_inline_size to be at least + // 1px. + column_percent = surplus_percent / nonpercent_columns_count; + } + column->percent = column_percent; + } + } + } + + // TODO(atotic) See crbug.com/531752 for discussion about differences + // between FF/Chrome. + // Minimum inline size gets distributed with standard distribution algorithm. + DistributeInlineSizeToComputedInlineSizeAuto( + colspan_cell_min_inline_size, inline_border_spacing, start_column, + end_column, column_constraints); + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + column->min_inline_size = + std::max(*column->min_inline_size, column->computed_inline_size); + } + DistributeInlineSizeToComputedInlineSizeAuto( + colspan_cell_max_inline_size, inline_border_spacing, start_column, + end_column, column_constraints); + for (NGTableTypes::Column* column = start_column; column != end_column; + ++column) { + column->max_inline_size = + std::max(*column->max_inline_size, column->computed_inline_size); + } +} + +} // namespace + +void NGTableAlgorithmHelpers::DistributeColspanCellToColumns( + const NGTableTypes::ColspanCell& colspan_cell, + LayoutUnit inline_border_spacing, + bool is_fixed_layout, + NGTableTypes::Columns* column_constraints) { + // Clipped colspanned cells can end up having a span of 1 (which is not wide). + DCHECK_GT(colspan_cell.span, 1u); + + if (is_fixed_layout) { + DistributeColspanCellToColumnsFixed(colspan_cell, inline_border_spacing, + column_constraints); + } else { + DistributeColspanCellToColumnsAuto(colspan_cell, inline_border_spacing, + column_constraints); + } +} + +// Standard: https://www.w3.org/TR/css-tables-3/#width-distribution-algorithm +// After synchroniziation, assignable table inline size and sum of column +// final inline sizes will be equal. +void NGTableAlgorithmHelpers::SynchronizeAssignableTableInlineSizeAndColumns( + LayoutUnit assignable_table_inline_size, + LayoutUnit inline_border_spacing, + bool is_fixed_layout, + NGTableTypes::Columns* column_constraints) { + if (column_constraints->size() == 0) + return; + NGTableTypes::Column* start_column = &(*column_constraints)[0]; + NGTableTypes::Column* end_column = start_column + column_constraints->size(); + if (is_fixed_layout) { + SynchronizeAssignableTableInlineSizeAndColumnsFixed( + assignable_table_inline_size, inline_border_spacing, start_column, + end_column); + } else { + DistributeInlineSizeToComputedInlineSizeAuto( + assignable_table_inline_size, inline_border_spacing, start_column, + end_column, column_constraints); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h new file mode 100644 index 00000000000..dd1d9f0e694 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h @@ -0,0 +1,42 @@ +// Copyright 2020 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_TABLE_NG_TABLE_LAYOUT_ALGORITHM_HELPERS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_TABLE_NG_TABLE_LAYOUT_ALGORITHM_HELPERS_H_ + +#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h" +#include "third_party/blink/renderer/core/style/computed_style_constants.h" + +namespace blink { + +// Table size distribution algorithms. +class NGTableAlgorithmHelpers { + public: + // Compute maximum number of table columns that can deduced from + // single cell and its colspan. + static wtf_size_t ComputeMaxColumn(wtf_size_t current_column, + wtf_size_t colspan, + bool is_fixed_table_layout) { + // In fixed mode, every column is preserved. + if (is_fixed_table_layout) + return current_column + colspan; + return current_column + 1; + } + + static void DistributeColspanCellToColumns( + const NGTableTypes::ColspanCell& colspan_cell, + LayoutUnit inline_border_spacing, + bool is_fixed_layout, + NGTableTypes::Columns* column_constraints); + + static void SynchronizeAssignableTableInlineSizeAndColumns( + LayoutUnit assignable_table_inline_size, + LayoutUnit inline_border_spacing, + bool is_fixed_layout, + NGTableTypes::Columns* column_constraints); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_TABLE_NG_TABLE_LAYOUT_ALGORITHM_HELPERS_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc new file mode 100644 index 00000000000..43df69d30d8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc @@ -0,0 +1,360 @@ +// Copyright 2020 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/table/ng_table_layout_algorithm_types.h" + +#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" +#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_caption.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h" +#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_section.h" +#include "third_party/blink/renderer/core/style/computed_style.h" + +namespace blink { + +namespace { + +// Gathers css sizes. CSS values might be modified to enforce universal +// invariants: css_max_inline_size >= css_min_inline_size +// css_percentage_inline_size <= css_percentage_max_inline_size +inline void InlineSizesFromStyle( + const ComputedStyle& style, + LayoutUnit inline_border_padding, + base::Optional<LayoutUnit>* inline_size, + base::Optional<LayoutUnit>* min_inline_size, + base::Optional<LayoutUnit>* max_inline_size, + base::Optional<float>* percentage_inline_size) { + const Length& length = style.LogicalWidth(); + const Length& min_length = style.LogicalMinWidth(); + const Length& max_length = style.LogicalMaxWidth(); + bool is_content_box = style.BoxSizing() == EBoxSizing::kContentBox; + if (length.IsFixed()) { + *inline_size = LayoutUnit(length.Value()); + if (is_content_box) + *inline_size = **inline_size + inline_border_padding; + } + if (min_length.IsFixed()) { + *min_inline_size = LayoutUnit(min_length.Value()); + if (is_content_box) + *min_inline_size = **min_inline_size + inline_border_padding; + } + if (max_length.IsFixed()) { + *max_inline_size = LayoutUnit(max_length.Value()); + if (is_content_box) + *max_inline_size = **max_inline_size + inline_border_padding; + if (*min_inline_size) + *max_inline_size = std::max(**min_inline_size, **max_inline_size); + } + if (length.IsPercent()) + *percentage_inline_size = length.Percent(); + if (*percentage_inline_size && max_length.IsPercent()) { + *percentage_inline_size = + std::min(**percentage_inline_size, max_length.Percent()); + } + if (*min_inline_size && *max_inline_size) + DCHECK_GE(**max_inline_size, **min_inline_size); +} + +} // namespace + +constexpr LayoutUnit NGTableTypes::kTableMaxInlineSize; + +// Implements https://www.w3.org/TR/css-tables-3/#computing-cell-measures +// "outer min-content and outer max-content widths for colgroups" +NGTableTypes::Column NGTableTypes::CreateColumn( + const ComputedStyle& style, + bool is_fixed_layout, + base::Optional<LayoutUnit> default_inline_size) { + base::Optional<LayoutUnit> inline_size; + base::Optional<LayoutUnit> min_inline_size; + base::Optional<LayoutUnit> max_inline_size; + base::Optional<float> percentage_inline_size; + InlineSizesFromStyle(style, LayoutUnit(), &inline_size, &min_inline_size, + &max_inline_size, &percentage_inline_size); + if (!inline_size) + inline_size = default_inline_size; + if (min_inline_size && inline_size) + inline_size = std::max(*inline_size, *min_inline_size); + bool is_constrained = inline_size.has_value(); + if (percentage_inline_size && *percentage_inline_size == 0.0f) + percentage_inline_size.reset(); + return Column{min_inline_size.value_or(LayoutUnit()), inline_size, + percentage_inline_size, is_constrained, kIndefiniteSize}; +} + +// Implements https://www.w3.org/TR/css-tables-3/#computing-cell-measures +// "outer min-content and outer max-content widths for table cells" +// Note: this method calls NGBlockNode::ComputeMinMaxSizes. +NGTableTypes::CellInlineConstraint NGTableTypes::CreateCellInlineConstraint( + const NGLayoutInputNode& node, + WritingMode table_writing_mode, + bool is_fixed_layout, + const NGBoxStrut& cell_border, + const NGBoxStrut& cell_padding, + bool is_collapsed) { + base::Optional<LayoutUnit> css_inline_size; + base::Optional<LayoutUnit> css_min_inline_size; + base::Optional<LayoutUnit> css_max_inline_size; + base::Optional<float> css_percentage_inline_size; + + // Algorithm: + // - Compute cell's minmax sizes. + // - Constrain by css inline-size/max-inline-size. + InlineSizesFromStyle(node.Style(), (cell_border + cell_padding).InlineSum(), + &css_inline_size, &css_min_inline_size, + &css_max_inline_size, &css_percentage_inline_size); + + MinMaxSizesInput input(kIndefiniteSize, MinMaxSizesType::kContent); + MinMaxSizesResult min_max_size; + if (is_collapsed) { + NGConstraintSpaceBuilder builder(table_writing_mode, + node.Style().GetWritingMode(), + /* is_new_fc */ false); + builder.SetTableCellBorders(cell_border); + builder.SetIsTableCell(true); + NGConstraintSpace space = builder.ToConstraintSpace(); + // It'd be nice to avoid computing minmax if not needed, but the criteria + // is not clear. + min_max_size = To<NGBlockNode>(node).ComputeMinMaxSizes(table_writing_mode, + input, &space); + } else { + min_max_size = node.ComputeMinMaxSizes(table_writing_mode, input); + } + + // Compute min inline size. + LayoutUnit resolved_min_inline_size; + if (!is_fixed_layout) { + resolved_min_inline_size = + std::max(min_max_size.sizes.min_size, + css_min_inline_size.value_or(LayoutUnit())); + // https://quirks.spec.whatwg.org/#the-table-cell-nowrap-minimum-width-calculation-quirk + if (css_inline_size && node.GetDocument().InQuirksMode()) { + bool has_nowrap_attribute = + !To<Element>(node.GetLayoutBox()->GetNode()) + ->FastGetAttribute(html_names::kNowrapAttr) + .IsNull(); + if (has_nowrap_attribute && node.Style().AutoWrap()) { + resolved_min_inline_size = + std::max(resolved_min_inline_size, *css_inline_size); + } + } + } + + // Compute resolved max inline size. + LayoutUnit content_max; + if (css_inline_size) { + content_max = *css_inline_size; + } else { + content_max = min_max_size.sizes.max_size; + } + if (css_max_inline_size) + content_max = std::min(content_max, *css_max_inline_size); + LayoutUnit resolved_max_inline_size = + std::max(resolved_min_inline_size, content_max); + + bool is_constrained = css_inline_size.has_value(); + + DCHECK_LE(resolved_min_inline_size, resolved_max_inline_size); + return NGTableTypes::CellInlineConstraint{ + resolved_min_inline_size, resolved_max_inline_size, + css_percentage_inline_size, is_constrained}; +} + +NGTableTypes::Section NGTableTypes::CreateSection( + const NGLayoutInputNode& section, + wtf_size_t start_row, + wtf_size_t rows, + LayoutUnit block_size) { + const Length& section_css_block_size = section.Style().LogicalHeight(); + bool is_constrained = section_css_block_size.IsSpecified(); + base::Optional<float> percent; + if (section_css_block_size.IsPercent()) + percent = section_css_block_size.Percent(); + bool is_tbody = + section.GetLayoutBox()->GetNode()->HasTagName(html_names::kTbodyTag); + return Section{start_row, + rows, + block_size, + percent, + is_constrained, + is_tbody, + /* needs_redistribution */ false}; +} + +NGTableTypes::CellBlockConstraint NGTableTypes::CreateCellBlockConstraint( + const NGLayoutInputNode& node, + LayoutUnit computed_block_size, + LayoutUnit baseline, + const NGBoxStrut& border_box_borders, + wtf_size_t row_index, + wtf_size_t column_index, + wtf_size_t rowspan) { + bool is_constrained = node.Style().LogicalHeight().IsFixed(); + return CellBlockConstraint{computed_block_size, + baseline, + border_box_borders, + row_index, + column_index, + rowspan, + node.Style().VerticalAlign(), + is_constrained}; +} + +NGTableTypes::RowspanCell NGTableTypes::CreateRowspanCell( + wtf_size_t row_index, + wtf_size_t rowspan, + CellBlockConstraint* cell_block_constraint, + base::Optional<LayoutUnit> css_cell_block_size) { + if (css_cell_block_size) { + cell_block_constraint->min_block_size = + std::max(cell_block_constraint->min_block_size, *css_cell_block_size); + } + return RowspanCell{row_index, rowspan, *cell_block_constraint}; +} + +void NGTableTypes::CellInlineConstraint::Encompass( + const NGTableTypes::CellInlineConstraint& other) { + // Standard says: + // "A column is constrained if any of the cells spanning only that column has + // a computed width that is not "auto", and is not a percentage. This means + // that <td width=50></td><td max-width=100> would be treated with constrained + // column with width of 100. + if (other.min_inline_size > min_inline_size) + min_inline_size = other.min_inline_size; + if (is_constrained == other.is_constrained) { + max_inline_size = std::max(max_inline_size, other.max_inline_size); + } else if (is_constrained) { + max_inline_size = std::max(max_inline_size, other.min_inline_size); + } else { + DCHECK(other.is_constrained); + max_inline_size = std::max(min_inline_size, other.max_inline_size); + } + is_constrained = is_constrained || other.is_constrained; + max_inline_size = std::max(max_inline_size, other.max_inline_size); + percent = std::max(percent, other.percent); +} + +void NGTableTypes::Column::Encompass( + const base::Optional<NGTableTypes::CellInlineConstraint>& cell) { + if (!cell) + return; + + if (min_inline_size) { + if (min_inline_size < cell->min_inline_size) { + min_inline_size = cell->min_inline_size; + } + if (is_constrained) { + if (cell->is_constrained) + max_inline_size = std::max(*max_inline_size, cell->max_inline_size); + else + max_inline_size = std::max(*max_inline_size, cell->min_inline_size); + } else { // !is_constrained + max_inline_size = std::max(max_inline_size.value_or(LayoutUnit()), + cell->max_inline_size); + } + } else { + min_inline_size = cell->min_inline_size; + max_inline_size = cell->max_inline_size; + } + if (min_inline_size && max_inline_size) { + max_inline_size = std::max(*min_inline_size, *max_inline_size); + } + if (percent) { + if (cell->percent) + percent = std::max(*cell->percent, *percent); + } else { + percent = cell->percent; + } + is_constrained |= cell->is_constrained; +} + +NGTableGroupedChildren::NGTableGroupedChildren(const NGBlockNode& table) { + for (NGLayoutInputNode child = table.FirstChild(); child; + child = child.NextSibling()) { + NGBlockNode block_child = To<NGBlockNode>(child); + if (block_child.IsTableCaption()) { + captions.push_back(block_child); + } else { + switch (child.Style().Display()) { + case EDisplay::kTableColumn: + case EDisplay::kTableColumnGroup: + columns.push_back(block_child); + break; + case EDisplay::kTableHeaderGroup: + headers.push_back(block_child); + break; + case EDisplay::kTableRowGroup: + bodies.push_back(block_child); + break; + case EDisplay::kTableFooterGroup: + footers.push_back(block_child); + break; + default: + NOTREACHED() << "unexpected table child"; + } + } + } +} + +NGTableGroupedChildrenIterator NGTableGroupedChildren::begin() const { + return NGTableGroupedChildrenIterator(*this); +} + +NGTableGroupedChildrenIterator NGTableGroupedChildren::end() const { + return NGTableGroupedChildrenIterator(*this, /* is_end */ true); +} + +NGTableGroupedChildrenIterator::NGTableGroupedChildrenIterator( + const NGTableGroupedChildren& grouped_children, + bool is_end) + : grouped_children_(grouped_children), current_vector_(nullptr) { + if (is_end) { + current_vector_ = &grouped_children_.footers; + current_iterator_ = current_vector_->end(); + return; + } + AdvanceToNonEmptySection(); +} + +NGTableGroupedChildrenIterator& NGTableGroupedChildrenIterator::operator++() { + ++current_iterator_; + if (current_iterator_ == current_vector_->end()) + AdvanceToNonEmptySection(); + return *this; +} + +NGBlockNode NGTableGroupedChildrenIterator::operator*() const { + return *current_iterator_; +} + +bool NGTableGroupedChildrenIterator::operator==( + const NGTableGroupedChildrenIterator& rhs) const { + return rhs.current_vector_ == current_vector_ && + rhs.current_iterator_ == current_iterator_; +} + +bool NGTableGroupedChildrenIterator::operator!=( + const NGTableGroupedChildrenIterator& rhs) const { + return !(*this == rhs); +} + +void NGTableGroupedChildrenIterator::AdvanceToNonEmptySection() { + if (current_vector_ == &grouped_children_.footers) + return; + if (!current_vector_) { + current_vector_ = &grouped_children_.headers; + } else if (current_vector_ == &grouped_children_.headers) { + current_vector_ = &grouped_children_.bodies; + } else if (current_vector_ == &grouped_children_.bodies) { + current_vector_ = &grouped_children_.footers; + } + current_iterator_ = current_vector_->begin(); + // If new group is empty, recursively advance. + if (current_iterator_ == current_vector_->end()) { + AdvanceToNonEmptySection(); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h new file mode 100644 index 00000000000..715a7337dd3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h @@ -0,0 +1,287 @@ +// Copyright 2020 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_TABLE_NG_TABLE_LAYOUT_ALGORITHM_TYPES_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_TABLE_NG_TABLE_LAYOUT_ALGORITHM_TYPES_H_ + +#include "base/optional.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" +#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" +#include "third_party/blink/renderer/core/style/computed_style_constants.h" +#include "third_party/blink/renderer/platform/geometry/length.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class ComputedStyle; +class NGBlockNode; +class NGLayoutInputNode; + +// Define constraint classes for NGTableLayoutAlgorithm. +class NGTableTypes { + public: + static constexpr LayoutUnit kTableMaxInlineSize = LayoutUnit::Max(); + + // Inline constraint for a single cell. + // Takes into account the cell style, and min/max content-sizes. + struct CellInlineConstraint { + DISALLOW_NEW(); + LayoutUnit min_inline_size; + LayoutUnit max_inline_size; + base::Optional<float> percent; // 100% is stored as 100.0f + bool is_constrained; // True if this cell has a specified inline-size. + + void Encompass(const CellInlineConstraint&); + }; + + // Inline constraints for a cell that span multiple columns. + struct ColspanCell { + DISALLOW_NEW(); + CellInlineConstraint cell_inline_constraint; + wtf_size_t start_column; + wtf_size_t span; + ColspanCell(const CellInlineConstraint& cell_inline_constraint, + unsigned start_column, + unsigned span) + : cell_inline_constraint(cell_inline_constraint), + start_column(start_column), + span(span) {} + // ColspanCells are distributed in column order. + bool operator<(const NGTableTypes::ColspanCell& rhs) const { + // '<' means left to right sort. + // Legacy sorts right-to-left, FF, Edge left-to-right. + if (span == rhs.span) + return start_column < rhs.start_column; + return span < rhs.span; + } + }; + + // Constraint for a column. + struct Column { + DISALLOW_NEW(); + // These members are initialized from <col> and <colgroup>, then they + // accumulate data from |CellInlineConstraint|s. + base::Optional<LayoutUnit> min_inline_size; + base::Optional<LayoutUnit> max_inline_size; + base::Optional<float> percent; // 100% is stored as 100.0f + // True if any cell for this column is constrained. + bool is_constrained = false; + + // The final inline-size of the column after all constraints have been + // applied. + LayoutUnit computed_inline_size; + void Encompass(const base::Optional<NGTableTypes::CellInlineConstraint>&); + LayoutUnit ResolvePercentInlineSize( + LayoutUnit percentage_resolution_inline_size) { + return std::max( + min_inline_size.value_or(LayoutUnit()), + LayoutUnit(*percent * percentage_resolution_inline_size / 100)); + } + bool IsFixed() const { + return is_constrained && !percent && max_inline_size; + } + }; + + // Block constraint for a single cell. + struct CellBlockConstraint { + DISALLOW_NEW(); + LayoutUnit min_block_size; + LayoutUnit baseline; + NGBoxStrut border_box_borders; + wtf_size_t row_index; + wtf_size_t column_index; + wtf_size_t rowspan; + EVerticalAlign vertical_align; + bool is_constrained; // True if this cell has a specified block-size. + CellBlockConstraint(LayoutUnit min_block_size, + LayoutUnit baseline, + NGBoxStrut border_box_borders, + wtf_size_t row_index, + wtf_size_t column_index, + wtf_size_t rowspan, + EVerticalAlign vertical_align, + bool is_constrained) + : min_block_size(min_block_size), + baseline(baseline), + border_box_borders(border_box_borders), + row_index(row_index), + column_index(column_index), + rowspan(rowspan), + vertical_align(vertical_align), + is_constrained(is_constrained) {} + }; + + // RowspanCells span multiple rows. + struct RowspanCell { + DISALLOW_NEW(); + CellBlockConstraint cell_block_constraint; + wtf_size_t start_row; + wtf_size_t span; + RowspanCell(wtf_size_t start_row, + wtf_size_t span, + const CellBlockConstraint& cell_block_constraint) + : cell_block_constraint(cell_block_constraint), + start_row(start_row), + span(span) {} + + // Original Legacy sorting criteria from + // CompareRowspanCellsInHeightDistributionOrder + bool operator<(const NGTableTypes::RowspanCell& rhs) const { + // Returns true if a |RowspanCell| is completely contained within another + // |RowspanCell|. + auto IsEnclosed = [](const NGTableTypes::RowspanCell& c1, + const NGTableTypes::RowspanCell& c2) { + return (c1.start_row >= c2.start_row) && + (c1.start_row + c1.span) <= (c2.start_row + c2.span); + }; + + // If cells span the same rows, bigger cell is distributed first. + if (start_row == rhs.start_row && span == rhs.span) { + return cell_block_constraint.min_block_size > + rhs.cell_block_constraint.min_block_size; + } + // If one cell is fully enclosed by another, inner cell wins. + if (IsEnclosed(*this, rhs)) + return true; + if (IsEnclosed(rhs, *this)) + return false; + // Lower rows wins. + return start_row < rhs.start_row; + } + }; + + struct Row { + DISALLOW_NEW(); + LayoutUnit block_size; + LayoutUnit baseline; + base::Optional<float> percent; // 100% is stored as 100.0f + wtf_size_t start_cell_index; + wtf_size_t cell_count; + // |is_constrained| is true if row has specified block-size, or contains + // constrained cells. + bool is_constrained; + bool has_baseline_aligned_percentage_block_size_descendants; + bool has_rowspan_start; // True if row originates a TD with rowspan > 1 + bool is_collapsed; + }; + + struct ColumnLocation { + LayoutUnit offset; // inline offset from table edge. + LayoutUnit size; + }; + + struct Section { + wtf_size_t start_row; + wtf_size_t rowspan; + LayoutUnit block_size; + base::Optional<float> percent; + bool is_constrained; + bool is_tbody; + bool needs_redistribution; + }; + + static Column CreateColumn(const ComputedStyle&, + bool is_fixed_layout, + base::Optional<LayoutUnit> default_inline_size); + + static CellInlineConstraint CreateCellInlineConstraint( + const NGLayoutInputNode&, + WritingMode table_writing_mode, + bool is_fixed_layout, + const NGBoxStrut& cell_border, + const NGBoxStrut& cell_padding, + bool is_collapsed); + + static Section CreateSection(const NGLayoutInputNode&, + wtf_size_t start_row, + wtf_size_t rowspan, + LayoutUnit block_size); + + static CellBlockConstraint CreateCellBlockConstraint( + const NGLayoutInputNode&, + LayoutUnit computed_block_size, + LayoutUnit baseline, + const NGBoxStrut& border_box_borders, + wtf_size_t row_index, + wtf_size_t column_index, + wtf_size_t rowspan); + + static RowspanCell CreateRowspanCell( + wtf_size_t row_index, + wtf_size_t rowspan, + CellBlockConstraint*, + base::Optional<LayoutUnit> css_block_size); + + using Columns = Vector<Column>; + // Inline constraints are optional because we need to distinguish between an + // empty cell, and a non-existent cell. + using CellInlineConstraints = Vector<base::Optional<CellInlineConstraint>>; + using ColspanCells = Vector<ColspanCell>; + using Caption = MinMaxSizes; + using CellBlockConstraints = Vector<CellBlockConstraint>; + using RowspanCells = Vector<RowspanCell>; + using Rows = Vector<Row>; + using Sections = Vector<Section>; + using ColumnLocations = Vector<ColumnLocation>; +}; + +class NGTableGroupedChildrenIterator; + +// Table's children grouped by type. +// When iterating through members, make sure to handle out_of_flows correctly. +struct NGTableGroupedChildren { + DISALLOW_NEW(); + + public: + explicit NGTableGroupedChildren(const NGBlockNode& table); + + Vector<NGBlockNode> captions; // CAPTION + Vector<NGBlockNode> columns; // COLGROUP, COL + + Vector<NGBlockNode> headers; // THEAD + Vector<NGBlockNode> bodies; // TBODY + Vector<NGBlockNode> footers; // TFOOT + + // Default iterators iterate over tbody-like (THEAD/TBODY/TFOOT) elements. + NGTableGroupedChildrenIterator begin() const; + NGTableGroupedChildrenIterator end() const; +}; + +// Iterates table's sections in order: +// thead, tbody, tfoot +class NGTableGroupedChildrenIterator { + public: + explicit NGTableGroupedChildrenIterator( + const NGTableGroupedChildren& grouped_children, + bool is_end = false); + + NGTableGroupedChildrenIterator& operator++(); + NGBlockNode operator*() const; + bool operator==(const NGTableGroupedChildrenIterator& rhs) const; + bool operator!=(const NGTableGroupedChildrenIterator& rhs) const; + + private: + void AdvanceToNonEmptySection(); + const NGTableGroupedChildren& grouped_children_; + const Vector<NGBlockNode>* current_vector_; + Vector<NGBlockNode>::const_iterator current_iterator_; +}; + +} // namespace blink + +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGTableTypes::CellInlineConstraint) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGTableTypes::ColspanCell) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGTableTypes::Column) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGTableTypes::CellBlockConstraint) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGTableTypes::RowspanCell) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGTableTypes::Row) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGTableTypes::ColumnLocation) +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGTableTypes::Section) + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_TABLE_NG_TABLE_LAYOUT_ALGORITHM_TYPES_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/paint_containment_test.cc b/chromium/third_party/blink/renderer/core/layout/paint_containment_test.cc index ed44fd54edb..ba4f6fc31d6 100644 --- a/chromium/third_party/blink/renderer/core/layout/paint_containment_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/paint_containment_test.cc @@ -28,7 +28,7 @@ static void CheckIsClippingStackingContextAndContainer( // clipping and stacking performed by paint containment. DCHECK(obj.Layer()); PaintLayer* layer = obj.Layer(); - EXPECT_TRUE(layer->GetLayoutObject().StyleRef().IsStackingContext()); + EXPECT_TRUE(layer->GetLayoutObject().IsStackingContext()); } TEST_F(PaintContainmentTest, BlockPaintContainment) { diff --git a/chromium/third_party/blink/renderer/core/layout/scroll_anchor.cc b/chromium/third_party/blink/renderer/core/layout/scroll_anchor.cc index 3cc716658b7..117a16a2e5c 100644 --- a/chromium/third_party/blink/renderer/core/layout/scroll_anchor.cc +++ b/chromium/third_party/blink/renderer/core/layout/scroll_anchor.cc @@ -84,7 +84,10 @@ static LayoutRect RelativeBounds(const LayoutObject* layout_object, PhysicalRect local_bounds; if (layout_object->IsBox()) { local_bounds = ToLayoutBox(layout_object)->PhysicalBorderBoxRect(); - if (!layout_object->HasOverflowClip()) { + // If we clip overflow then we can use the `PhysicalBorderBoxRect()` + // as our bounds. If not, we expand the bounds by the layout overflow and + // lowest floating object. + if (!layout_object->ShouldClipOverflow()) { // BorderBoxRect doesn't include overflow content and floats. LayoutUnit max_y = std::max(local_bounds.Bottom(), diff --git a/chromium/third_party/blink/renderer/core/layout/scroll_anchor.h b/chromium/third_party/blink/renderer/core/layout/scroll_anchor.h index 463b1749bfe..bd1cdfb3f03 100644 --- a/chromium/third_party/blink/renderer/core/layout/scroll_anchor.h +++ b/chromium/third_party/blink/renderer/core/layout/scroll_anchor.h @@ -108,7 +108,7 @@ class CORE_EXPORT ScrollAnchor final { // Notifies us that an object will be removed from the layout tree. void NotifyRemoved(LayoutObject*); - void Trace(Visitor* visitor) { visitor->Trace(scroller_); } + void Trace(Visitor* visitor) const { visitor->Trace(scroller_); } private: void FindAnchor(); diff --git a/chromium/third_party/blink/renderer/core/layout/scroll_anchor_test.cc b/chromium/third_party/blink/renderer/core/layout/scroll_anchor_test.cc index 02472dc4cc3..0272d82efb7 100644 --- a/chromium/third_party/blink/renderer/core/layout/scroll_anchor_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/scroll_anchor_test.cc @@ -536,64 +536,6 @@ TEST_P(ScrollAnchorTest, FlexboxDelayedAdjustmentRespectsSANACLAP) { EXPECT_EQ(100, ScrollerForElement(scroller)->ScrollOffsetInt().Height()); } -// TODO(skobes): Convert this to web-platform-tests when document.rootScroller -// is launched (http://crbug.com/505516). -TEST_P(ScrollAnchorTest, NonDefaultRootScroller) { - SetBodyInnerHTML(R"HTML( - <style> - ::-webkit-scrollbar { - width: 0px; height: 0px; - } - body, html { - margin: 0px; width: 100%; height: 100%; - } - #rootscroller { - overflow: scroll; width: 100%; height: 100%; - } - .spacer { - height: 600px; width: 100px; - } - #target { - height: 100px; width: 100px; background-color: red; - } - </style> - <div id='rootscroller'> - <div id='firstChild' class='spacer'></div> - <div id='target'></div> - <div class='spacer'></div> - </div> - <div class='spacer'></div> - )HTML"); - - Element* root_scroller_element = GetDocument().getElementById("rootscroller"); - - NonThrowableExceptionState non_throw; - GetDocument().setRootScroller(root_scroller_element, non_throw); - UpdateAllLifecyclePhasesForTest(); - - ScrollableArea* scroller = ScrollerForElement(root_scroller_element); - - // By making the #rootScroller DIV the rootScroller, it should become the - // layout viewport on the RootFrameViewport. - ASSERT_EQ(scroller, - &GetDocument().View()->GetRootFrameViewport()->LayoutViewport()); - - // The #rootScroller DIV's anchor should have the RootFrameViewport set as - // the scroller, rather than the FrameView's anchor. - - root_scroller_element->setScrollTop(600); - - SetHeight(GetDocument().getElementById("firstChild"), 1000); - - // Scroll anchoring should be applied to #rootScroller. - EXPECT_EQ(1000, scroller->GetScrollOffset().Height()); - EXPECT_EQ(GetDocument().getElementById("target")->GetLayoutObject(), - GetScrollAnchor(scroller).AnchorObject()); - // Scroll anchoring should not apply within main frame. - EXPECT_EQ(0, LayoutViewport()->GetScrollOffset().Height()); - EXPECT_EQ(nullptr, GetScrollAnchor(LayoutViewport()).AnchorObject()); -} - // This test verifies that scroll anchoring is disabled when the document is in // printing mode. TEST_P(ScrollAnchorTest, AnchoringDisabledForPrinting) { @@ -1131,19 +1073,19 @@ class ScrollAnchorFindInPageTest : public testing::Test { test::RunPendingTasks(); } - mojom::blink::FindOptionsPtr FindOptions(bool find_next = false) { + mojom::blink::FindOptionsPtr FindOptions(bool new_session = true) { auto find_options = mojom::blink::FindOptions::New(); find_options->run_synchronously_for_testing = true; - find_options->find_next = find_next; + find_options->new_session = new_session; find_options->forward = true; return find_options; } void Find(String search_text, ScrollAnchorTestFindInPageClient& client, - bool find_next = false) { + bool new_session = true) { client.Reset(); - GetFindInPage()->Find(FAKE_FIND_ID, search_text, FindOptions(find_next)); + GetFindInPage()->Find(FAKE_FIND_ID, search_text, FindOptions(new_session)); test::RunPendingTasks(); } diff --git a/chromium/third_party/blink/renderer/core/layout/scrollbars_test.cc b/chromium/third_party/blink/renderer/core/layout/scrollbars_test.cc index 455216a3abc..e5737780c21 100644 --- a/chromium/third_party/blink/renderer/core/layout/scrollbars_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/scrollbars_test.cc @@ -2698,6 +2698,80 @@ TEST_F(ScrollbarsTest, CheckScrollCornerIfThereIsNoScrollbar) { EXPECT_FALSE(scrollable_container->ScrollCorner()); } +TEST_F(ScrollbarsTest, NoNeedsBeginFrameForCustomScrollbarAfterBeginFrame) { + WebView().MainFrameWidget()->Resize(WebSize(200, 200)); + + SimRequest request("https://example.com/test.html", "text/html"); + LoadURL("https://example.com/test.html"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + ::-webkit-scrollbar { height: 20px; } + ::-webkit-scrollbar-thumb { background-color: blue; } + #target { width: 200px; height: 200px; overflow: scroll; } + </style> + <div id="target"> + <div style="width: 500px; height: 500px"></div> + </div> + )HTML"); + + while (Compositor().NeedsBeginFrame()) + Compositor().BeginFrame(); + + auto* target = GetDocument().getElementById("target"); + auto* scrollbar = To<CustomScrollbar>( + target->GetLayoutBox()->GetScrollableArea()->HorizontalScrollbar()); + LayoutCustomScrollbarPart* thumb = scrollbar->GetPart(kThumbPart); + auto thumb_size = thumb->Size(); + EXPECT_FALSE(thumb->ShouldCheckForPaintInvalidation()); + EXPECT_FALSE(Compositor().NeedsBeginFrame()); + + WebView().MainFrameWidget()->UpdateAllLifecyclePhases( + DocumentUpdateReason::kTest); + EXPECT_FALSE(thumb->ShouldCheckForPaintInvalidation()); + EXPECT_FALSE(Compositor().NeedsBeginFrame()); + + target->setAttribute(html_names::kStyleAttr, "width: 400px"); + EXPECT_TRUE(Compositor().NeedsBeginFrame()); + Compositor().BeginFrame(); + EXPECT_FALSE(thumb->ShouldCheckForPaintInvalidation()); + EXPECT_FALSE(Compositor().NeedsBeginFrame()); + EXPECT_NE(thumb_size, thumb->Size()); +} + +TEST_F(ScrollbarsTest, CustomScrollbarHypotheticalThickness) { + WebView().MainFrameWidget()->Resize(WebSize(200, 200)); + + SimRequest request("https://example.com/test.html", "text/html"); + LoadURL("https://example.com/test.html"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + #target1::-webkit-scrollbar { width: 22px; height: 33px; } + #target2::-webkit-scrollbar:horizontal { height: 13px; } + ::-webkit-scrollbar:vertical { width: 21px; } + </style> + <div id="target1" style="width: 60px; height: 70px; overflow: scroll"></div> + <div id="target2" style="width: 80px; height: 90px; overflow: scroll"></div> + )HTML"); + + Compositor().BeginFrame(); + + auto* target1 = GetDocument().getElementById("target1"); + auto* scrollable_area1 = target1->GetLayoutBox()->GetScrollableArea(); + EXPECT_EQ(33, CustomScrollbar::HypotheticalScrollbarThickness( + scrollable_area1, kHorizontalScrollbar, target1)); + EXPECT_EQ(22, CustomScrollbar::HypotheticalScrollbarThickness( + scrollable_area1, kVerticalScrollbar, target1)); + + auto* target2 = GetDocument().getElementById("target2"); + auto* scrollable_area2 = target2->GetLayoutBox()->GetScrollableArea(); + EXPECT_EQ(13, CustomScrollbar::HypotheticalScrollbarThickness( + scrollable_area2, kHorizontalScrollbar, target2)); + EXPECT_EQ(21, CustomScrollbar::HypotheticalScrollbarThickness( + scrollable_area2, kVerticalScrollbar, target2)); +} + // For infinite scrolling page (load more content when scroll to bottom), user // press on scrollbar button should keep scrolling after content loaded. // Disable on Android since VirtualTime not work for Android. @@ -2776,7 +2850,7 @@ TEST_F(ScrollbarsTestWithVirtualTimer, // Verify that the scrollbar autopress timer requested some scrolls via // gestures. The button was pressed for 2 seconds and the timer fires // every 250ms - we should have at least 7 injected gesture updates. - EXPECT_GT(WebWidgetClient().GetInjectedScrollGestureData().size(), 6u); + EXPECT_GT(WebWidgetClient().GetInjectedScrollEvents().size(), 6u); } class ScrollbarTrackMarginsTest : public ScrollbarsTest { diff --git a/chromium/third_party/blink/renderer/core/layout/shapes/shape_outside_info.cc b/chromium/third_party/blink/renderer/core/layout/shapes/shape_outside_info.cc index c9f477b2f0d..671fc265eb7 100644 --- a/chromium/third_party/blink/renderer/core/layout/shapes/shape_outside_info.cc +++ b/chromium/third_party/blink/renderer/core/layout/shapes/shape_outside_info.cc @@ -148,11 +148,11 @@ static bool CheckShapeImageOrigin(Document& document, return true; DCHECK(style_image.CachedImage()); - ImageResourceContent& image_resource = *(style_image.CachedImage()); - if (image_resource.IsAccessAllowed()) + ImageResourceContent& image_content = *(style_image.CachedImage()); + if (image_content.IsAccessAllowed()) return true; - const KURL& url = image_resource.Url(); + const KURL& url = image_content.Url(); String url_string = url.IsNull() ? "''" : url.ElidedString(); document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kSecurity, diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc index 3d6fea7918c..0b8eb05e084 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc @@ -28,13 +28,16 @@ #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h" #include "third_party/blink/renderer/core/layout/svg/svg_resources.h" #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h" +#include "third_party/blink/renderer/core/layout/svg/transform_helper.h" #include "third_party/blink/renderer/core/style/shadow_list.h" #include "third_party/blink/renderer/core/svg/svg_element.h" namespace blink { LayoutSVGBlock::LayoutSVGBlock(SVGElement* element) - : LayoutBlockFlow(element) {} + : LayoutBlockFlow(element), + needs_transform_update_(true), + transform_uses_reference_box_(false) {} SVGElement* LayoutSVGBlock::GetElement() const { return To<SVGElement>(LayoutObject::GetNode()); @@ -51,8 +54,40 @@ void LayoutSVGBlock::UpdateFromStyle() { SetFloating(false); } +bool LayoutSVGBlock::CheckForImplicitTransformChange(bool bbox_changed) const { + // If the transform is relative to the reference box, check relevant + // conditions to see if we need to recompute the transform. + switch (StyleRef().TransformBox()) { + case ETransformBox::kViewBox: + return SVGLayoutSupport::LayoutSizeOfNearestViewportChanged(this); + case ETransformBox::kFillBox: + return bbox_changed; + } + NOTREACHED(); + return false; +} + +bool LayoutSVGBlock::UpdateTransformAfterLayout(bool bounds_changed) { + // If our transform depends on the reference box, we need to check if it needs + // to be updated. + if (!needs_transform_update_ && transform_uses_reference_box_) { + needs_transform_update_ = CheckForImplicitTransformChange(bounds_changed); + if (needs_transform_update_) + SetNeedsPaintPropertyUpdate(); + } + if (!needs_transform_update_) + return false; + local_transform_ = + GetElement()->CalculateTransform(SVGElement::kIncludeMotionTransform); + needs_transform_update_ = false; + return true; +} + void LayoutSVGBlock::StyleDidChange(StyleDifference diff, const ComputedStyle* old_style) { + transform_uses_reference_box_ = + TransformHelper::DependsOnReferenceBox(StyleRef()); + // Since layout depends on the bounds of the filter, we need to force layout // when the filter changes. if (diff.FilterChanged()) diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.h b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.h index e95571ca527..d13579e59be 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.h +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_block.h @@ -50,6 +50,7 @@ class LayoutSVGBlock : public LayoutBlockFlow { LayoutGeometryMap&) const final; AffineTransform LocalSVGTransform() const final { return local_transform_; } + void SetNeedsTransformUpdate() override { needs_transform_update_ = true; } PaintLayerType LayerTypeRequired() const override { return kNoPaintLayer; } @@ -63,11 +64,15 @@ class LayoutSVGBlock : public LayoutBlockFlow { VisualRectFlags = kDefaultVisualRectFlags) const final; AffineTransform local_transform_; + bool needs_transform_update_ : 1; + bool transform_uses_reference_box_ : 1; bool IsOfType(LayoutObjectType type) const override { return type == kLayoutObjectSVG || LayoutBlockFlow::IsOfType(type); } + bool CheckForImplicitTransformChange(bool bbox_changed) const; + bool UpdateTransformAfterLayout(bool bounds_changed); void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; private: diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc index 6c2f0e03a39..880a0925a42 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc @@ -32,7 +32,7 @@ namespace blink { LayoutSVGForeignObject::LayoutSVGForeignObject(SVGForeignObjectElement* node) - : LayoutSVGBlock(node), needs_transform_update_(true) {} + : LayoutSVGBlock(node) {} LayoutSVGForeignObject::~LayoutSVGForeignObject() = default; @@ -92,12 +92,15 @@ void LayoutSVGForeignObject::UpdateLayout() { auto* foreign = To<SVGForeignObjectElement>(GetElement()); - bool update_cached_boundaries_in_parents = false; + // Update our transform before layout, in case any of our descendants rely on + // the transform being somewhat accurate. The |needs_transform_update_| flag + // will be cleared after layout has been performed. + // TODO(fs): Remove this. AFAICS in all cases where we ancestors compute some + // form of CTM, they stop at their nearest ancestor LayoutSVGRoot, and thus + // will not care about this value. if (needs_transform_update_) { local_transform_ = foreign->CalculateTransform(SVGElement::kIncludeMotionTransform); - needs_transform_update_ = false; - update_cached_boundaries_in_parents = true; } LayoutRect old_viewport = FrameRect(); @@ -110,19 +113,24 @@ void LayoutSVGForeignObject::UpdateLayout() { SetX(ElementX()); SetY(ElementY()); - bool layout_changed = EverHadLayout() && SelfNeedsLayout(); + const bool layout_changed = EverHadLayout() && SelfNeedsLayout(); LayoutBlock::UpdateLayout(); DCHECK(!NeedsLayout()); + const bool bounds_changed = old_viewport != FrameRect(); - // If our bounds changed, notify the parents. - if (!update_cached_boundaries_in_parents) - update_cached_boundaries_in_parents = old_viewport != FrameRect(); - if (update_cached_boundaries_in_parents) + bool update_parent_boundaries = bounds_changed; + if (UpdateTransformAfterLayout(bounds_changed)) + update_parent_boundaries = true; + + // Notify ancestor about our bounds changing. + if (update_parent_boundaries) LayoutSVGBlock::SetNeedsBoundariesUpdate(); // Invalidate all resources of this client if our layout changed. if (layout_changed) SVGResourcesCache::ClientLayoutChanged(*this); + + DCHECK(!needs_transform_update_); } bool LayoutSVGForeignObject::NodeAtPointFromSVG( diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h index 9d38aab5c10..71d3180c865 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h @@ -81,8 +81,6 @@ class LayoutSVGForeignObject final : public LayoutSVGBlock { LayoutSVGBlock::IsOfType(type); } - void SetNeedsTransformUpdate() override { needs_transform_update_ = true; } - PaintLayerType LayerTypeRequired() const override; bool CreatesNewFormattingContext() const final { @@ -101,8 +99,6 @@ class LayoutSVGForeignObject final : public LayoutSVGBlock { LayoutUnit logical_top, LogicalExtentComputedValues&) const override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; - - bool needs_transform_update_; }; template <> diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.cc index fcfbf011d02..898ca496913 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.cc @@ -216,8 +216,7 @@ void LayoutSVGResourceClipper::CalculateLocalClipBounds() { SVGUnitTypes::SVGUnitType LayoutSVGResourceClipper::ClipPathUnits() const { return To<SVGClipPathElement>(GetElement()) ->clipPathUnits() - ->CurrentValue() - ->EnumValue(); + ->CurrentEnumValue(); } AffineTransform LayoutSVGResourceClipper::CalculateClipTransform( diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc index f275e5646e8..82822ddcdc4 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc @@ -52,17 +52,13 @@ FloatRect LayoutSVGResourceFilter::ResourceBoundingBox( } SVGUnitTypes::SVGUnitType LayoutSVGResourceFilter::FilterUnits() const { - return To<SVGFilterElement>(GetElement()) - ->filterUnits() - ->CurrentValue() - ->EnumValue(); + return To<SVGFilterElement>(GetElement())->filterUnits()->CurrentEnumValue(); } SVGUnitTypes::SVGUnitType LayoutSVGResourceFilter::PrimitiveUnits() const { return To<SVGFilterElement>(GetElement()) ->primitiveUnits() - ->CurrentValue() - ->EnumValue(); + ->CurrentEnumValue(); } bool LayoutSVGResourceFilter::FindCycleFromSelf( diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.cc index 9b7c605caa0..e2ebeba066b 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.cc @@ -80,17 +80,11 @@ float LayoutSVGResourceMarker::Angle() const { } SVGMarkerUnitsType LayoutSVGResourceMarker::MarkerUnits() const { - return To<SVGMarkerElement>(GetElement()) - ->markerUnits() - ->CurrentValue() - ->EnumValue(); + return To<SVGMarkerElement>(GetElement())->markerUnits()->CurrentEnumValue(); } SVGMarkerOrientType LayoutSVGResourceMarker::OrientType() const { - return To<SVGMarkerElement>(GetElement()) - ->orientType() - ->CurrentValue() - ->EnumValue(); + return To<SVGMarkerElement>(GetElement())->orientType()->CurrentEnumValue(); } AffineTransform LayoutSVGResourceMarker::MarkerTransformation( diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc index b1e201d9a8b..6519bf3bdcc 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc @@ -93,17 +93,13 @@ void LayoutSVGResourceMasker::CalculateMaskContentVisualRect() { } SVGUnitTypes::SVGUnitType LayoutSVGResourceMasker::MaskUnits() const { - return To<SVGMaskElement>(GetElement()) - ->maskUnits() - ->CurrentValue() - ->EnumValue(); + return To<SVGMaskElement>(GetElement())->maskUnits()->CurrentEnumValue(); } SVGUnitTypes::SVGUnitType LayoutSVGResourceMasker::MaskContentUnits() const { return To<SVGMaskElement>(GetElement()) ->maskContentUnits() - ->CurrentValue() - ->EnumValue(); + ->CurrentEnumValue(); } FloatRect LayoutSVGResourceMasker::ResourceBoundingBox( diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc index d38f416ffc2..d01837fe628 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc @@ -80,7 +80,10 @@ void LayoutSVGRoot::UnscaledIntrinsicSizingInfo( intrinsic_sizing_info.has_width = svg->HasIntrinsicWidth(); intrinsic_sizing_info.has_height = svg->HasIntrinsicHeight(); - if (!intrinsic_sizing_info.size.IsEmpty()) { + if (const base::Optional<IntSize>& aspect_ratio = StyleRef().AspectRatio()) { + intrinsic_sizing_info.aspect_ratio.SetWidth(aspect_ratio->Width()); + intrinsic_sizing_info.aspect_ratio.SetHeight(aspect_ratio->Height()); + } else if (!intrinsic_sizing_info.size.IsEmpty()) { intrinsic_sizing_info.aspect_ratio = intrinsic_sizing_info.size; } else { FloatSize view_box_size = svg->viewBox()->CurrentValue()->Value().Size(); diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc index 373e99fb652..c2161141311 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc @@ -67,7 +67,6 @@ LayoutSVGText::LayoutSVGText(SVGTextElement* node) : LayoutSVGBlock(node), needs_reordering_(false), needs_positioning_values_update_(false), - needs_transform_update_(true), needs_text_metrics_update_(false) {} LayoutSVGText::~LayoutSVGText() { @@ -248,18 +247,12 @@ void LayoutSVGText::UpdateLayout() { needs_reordering_ = false; - FloatRect new_boundaries = ObjectBoundingBox(); - bool bounds_changed = old_boundaries != new_boundaries; + const bool bounds_changed = old_boundaries != ObjectBoundingBox(); + if (bounds_changed) + update_parent_boundaries = true; - // Update the transform after laying out. Update if the bounds - // changed too, since the transform could depend on the bounding - // box. - if (bounds_changed || needs_transform_update_) { - local_transform_ = - GetElement()->CalculateTransform(SVGElement::kIncludeMotionTransform); - needs_transform_update_ = false; + if (UpdateTransformAfterLayout(bounds_changed)) update_parent_boundaries = true; - } ClearLayoutOverflow(); diff --git a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.h b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.h index c5ebeb9f698..207d0f5dbeb 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.h +++ b/chromium/third_party/blink/renderer/core/layout/svg/layout_svg_text.h @@ -39,7 +39,6 @@ class LayoutSVGText final : public LayoutSVGBlock { void SetNeedsPositioningValuesUpdate() { needs_positioning_values_update_ = true; } - void SetNeedsTransformUpdate() override { needs_transform_update_ = true; } void SetNeedsTextMetricsUpdate() { needs_text_metrics_update_ = true; } FloatRect VisualRectInLocalSVGCoordinates() const override; FloatRect ObjectBoundingBox() const override; @@ -97,7 +96,6 @@ class LayoutSVGText final : public LayoutSVGBlock { bool needs_reordering_ : 1; bool needs_positioning_values_update_ : 1; - bool needs_transform_update_ : 1; bool needs_text_metrics_update_ : 1; Vector<LayoutSVGInlineText*> descendant_text_nodes_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc b/chromium/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc index 14eabbb2fcc..996b0bb4e4e 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc @@ -531,10 +531,8 @@ bool SVGLayoutSupport::IsLayoutableTextNode(const LayoutObject* object) { bool SVGLayoutSupport::WillIsolateBlendingDescendantsForStyle( const ComputedStyle& style) { - const SVGComputedStyle& svg_style = style.SvgStyle(); - - return style.HasIsolation() || style.HasOpacity() || style.HasBlendMode() || - style.HasFilter() || svg_style.HasMasker() || style.ClipPath(); + return style.HasGroupingProperty(style.BoxReflect()) || + style.SvgStyle().HasMasker(); } bool SVGLayoutSupport::WillIsolateBlendingDescendantsForObject( diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.cc b/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.cc index fd553642211..1d4fbf0c274 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.cc @@ -740,7 +740,7 @@ bool FilterData::Invalidate(SVGFilterPrimitiveStandardAttributes& primitive, return true; } -void FilterData::Trace(Visitor* visitor) { +void FilterData::Trace(Visitor* visitor) const { visitor->Trace(last_effect_); visitor->Trace(node_map_); } @@ -842,7 +842,7 @@ bool SVGElementResourceClient::ClearFilterData() { return !!filter_data; } -void SVGElementResourceClient::Trace(Visitor* visitor) { +void SVGElementResourceClient::Trace(Visitor* visitor) const { visitor->Trace(element_); visitor->Trace(filter_data_); SVGResourceClient::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.h b/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.h index 7507e78f3fe..ad604b74b98 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.h +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_resources.h @@ -214,7 +214,7 @@ class FilterData final : public GarbageCollected<FilterData> { void Dispose(); - void Trace(Visitor*); + void Trace(Visitor*) const; private: Member<FilterEffect> last_effect_; @@ -256,7 +256,7 @@ class SVGElementResourceClient final FilterData* UpdateFilterData(); bool ClearFilterData(); - void Trace(Visitor*) override; + void Trace(Visitor*) const override; private: Member<SVGElement> element_; diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.cc b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.cc index 94ddd75bcb5..229d18e1e98 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.cc @@ -176,8 +176,7 @@ void SVGTextChunkBuilder::HandleTextChunk(BoxListConstIterator box_start, if (SVGTextContentElement* text_content_element = SVGTextContentElement::ElementFromLineLayoutItem( text_line_layout.Parent())) { - length_adjust = - text_content_element->lengthAdjust()->CurrentValue()->EnumValue(); + length_adjust = text_content_element->lengthAdjust()->CurrentEnumValue(); SVGLengthContext length_context(text_content_element); if (text_content_element->TextLengthIsSpecifiedByUser()) diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.cc b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.cc index 6f537e09faf..f7a392f868c 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.cc @@ -241,7 +241,7 @@ void SVGTextLayoutAttributesBuilder::FillCharacterDataMap( } void SVGTextLayoutAttributesBuilder::TextPosition::Trace( - blink::Visitor* visitor) { + blink::Visitor* visitor) const { visitor->Trace(element); } diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.h b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.h index 956c93cc526..28a4240ead9 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.h @@ -58,7 +58,7 @@ class SVGTextLayoutAttributesBuilder { unsigned new_length = 0) : element(new_element), start(new_start), length(new_length) {} - void Trace(Visitor*); + void Trace(Visitor*) const; Member<SVGTextPositioningElement> element; unsigned start; diff --git a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_engine.cc b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_engine.cc index 7628afd5795..764098650dc 100644 --- a/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_engine.cc +++ b/chromium/third_party/blink/renderer/core/layout/svg/svg_text_layout_engine.cc @@ -185,8 +185,7 @@ void SVGTextLayoutEngine::BeginTextPathLayout(SVGInlineFlowBox* flow_box) { if (SVGTextContentElement* text_content_element = SVGTextContentElement::ElementFromLineLayoutItem(text_path)) { SVGLengthContext length_context(text_content_element); - length_adjust = - text_content_element->lengthAdjust()->CurrentValue()->EnumValue(); + length_adjust = text_content_element->lengthAdjust()->CurrentEnumValue(); if (text_content_element->TextLengthIsSpecifiedByUser()) desired_text_length = text_content_element->textLength()->CurrentValue()->Value( @@ -252,7 +251,7 @@ static bool DefinesTextLengthWithSpacing(const InlineFlowBox* start) { SVGTextContentElement::ElementFromLineLayoutItem( start->GetLineLayoutItem()); return text_content_element && - text_content_element->lengthAdjust()->CurrentValue()->EnumValue() == + text_content_element->lengthAdjust()->CurrentEnumValue() == kSVGLengthAdjustSpacing && text_content_element->TextLengthIsSpecifiedByUser(); } diff --git a/chromium/third_party/blink/renderer/core/layout/text_autosizer.cc b/chromium/third_party/blink/renderer/core/layout/text_autosizer.cc index f3609dc9801..792b63379ec 100644 --- a/chromium/third_party/blink/renderer/core/layout/text_autosizer.cc +++ b/chromium/third_party/blink/renderer/core/layout/text_autosizer.cc @@ -1548,7 +1548,7 @@ void TextAutosizer::CheckSuperclusterConsistency() { potentially_inconsistent_superclusters.clear(); } -void TextAutosizer::Trace(Visitor* visitor) { +void TextAutosizer::Trace(Visitor* visitor) const { visitor->Trace(document_); } diff --git a/chromium/third_party/blink/renderer/core/layout/text_autosizer.h b/chromium/third_party/blink/renderer/core/layout/text_autosizer.h index 7a6390abf7b..923a6b2a7fc 100644 --- a/chromium/third_party/blink/renderer/core/layout/text_autosizer.h +++ b/chromium/third_party/blink/renderer/core/layout/text_autosizer.h @@ -80,7 +80,7 @@ class CORE_EXPORT TextAutosizer final : public GarbageCollected<TextAutosizer> { bool PageNeedsAutosizing() const; - void Trace(Visitor*); + void Trace(Visitor*) const; class LayoutScope { STACK_ALLOCATED(); diff --git a/chromium/third_party/blink/renderer/core/layout/text_decoration_offset.cc b/chromium/third_party/blink/renderer/core/layout/text_decoration_offset.cc index 7bcc89c6f33..590f7b11d83 100644 --- a/chromium/third_party/blink/renderer/core/layout/text_decoration_offset.cc +++ b/chromium/third_party/blink/renderer/core/layout/text_decoration_offset.cc @@ -27,10 +27,12 @@ int TextDecorationOffset::ComputeUnderlineOffsetForUnder( int offset_int = (farthest - logical_top).Floor(); // Gaps are not needed for TextTop because it generally has internal - // leadings. + // leadings. Overline needs to grow upwards, hence subtract thickness. if (position_type == FontVerticalPositionType::TextTop) - return offset_int; - return !IsLineOverSide(position_type) ? offset_int + 1 : offset_int - 1; + return offset_int - floorf(text_decoration_thickness); + return !IsLineOverSide(position_type) + ? offset_int + 1 + : offset_int - 1 - floorf(text_decoration_thickness); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/visual_rect_mapping_test.cc b/chromium/third_party/blink/renderer/core/layout/visual_rect_mapping_test.cc index cd5349c1f8f..2a0b93e9794 100644 --- a/chromium/third_party/blink/renderer/core/layout/visual_rect_mapping_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/visual_rect_mapping_test.cc @@ -1294,4 +1294,51 @@ TEST_P(VisualRectMappingTest, InclusiveIntersect) { kDefaultVisualRectFlags, false); } +TEST_P(VisualRectMappingTest, Perspective) { + ScopedTransformInteropForTest enabled(true); + + GetDocument().SetBaseURLOverride(KURL("http://test.com")); + SetBodyInnerHTML(R"HTML( + <style>body { margin:0; }</style> + <div id='ancestor' style='perspective: 100px'> + <div> + <div id='child' style='width: 10px; height: 10px; + transform: rotateY(45deg); position: absolute'></div> + </div> + </div> + )HTML"); + + auto* ancestor = + ToLayoutBox(GetDocument().getElementById("ancestor")->GetLayoutObject()); + auto* child = + ToLayoutBox(GetDocument().getElementById("child")->GetLayoutObject()); + + PhysicalRect rect(0, 0, 10, 10); + child->MapToVisualRectInAncestorSpace(ancestor, rect); + EXPECT_EQ(IntRect(1, 0, 8, 10), EnclosingIntRect(rect)); +} + +TEST_P(VisualRectMappingTest, PerspectiveWithAnonymousTable) { + ScopedTransformInteropForTest enabled(true); + + GetDocument().SetBaseURLOverride(KURL("http://test.com")); + SetBodyInnerHTML(R"HTML( + <style>body { margin:0; }</style> + <div id='ancestor' style='display: table; perspective: 100px; width: 10px; + height: 10px;'> + <div id='child' style='display: table-cell; width: 10px; height: 10px; + transform: rotateY(45deg); position: absolute'></div> + </table> + )HTML"); + + auto* ancestor = + ToLayoutBox(GetDocument().getElementById("ancestor")->GetLayoutObject()); + auto* child = + ToLayoutBox(GetDocument().getElementById("child")->GetLayoutObject()); + + PhysicalRect rect(0, 0, 10, 10); + child->MapToVisualRectInAncestorSpace(ancestor, rect); + EXPECT_EQ(IntRect(1, -1, 8, 12), EnclosingIntRect(rect)); +} + } // namespace blink |