summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/layout/ng/inline
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-07-31 15:50:41 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-08-30 12:35:23 +0000
commit7b2ffa587235a47d4094787d72f38102089f402a (patch)
tree30e82af9cbab08a7fa028bb18f4f2987a3f74dfa /chromium/third_party/blink/renderer/core/layout/ng/inline
parentd94af01c90575348c4e81a418257f254b6f8d225 (diff)
downloadqtwebengine-chromium-7b2ffa587235a47d4094787d72f38102089f402a.tar.gz
BASELINE: Update Chromium to 76.0.3809.94
Change-Id: I321c3f5f929c105aec0f98c5091ef6108822e647 Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout/ng/inline')
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/README.md6
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h6
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc50
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h1
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h11
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc426
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h215
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc461
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc13
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc80
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc50
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h91
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc69
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h15
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h2
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc32
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h29
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc137
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h35
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc19
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc100
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h2
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc42
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc322
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h22
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc259
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc12
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h43
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc278
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h35
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc17
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc42
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc67
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h14
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc1
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc62
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc105
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h22
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc53
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc132
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h52
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc46
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc16
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h6
48 files changed, 1646 insertions, 1867 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md b/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md
index c604426d29e..a9b42d7c716 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/README.md
@@ -309,11 +309,6 @@ In a bird's‐eye view, it consists of two parts:
content of an inline formatting context (computed in [pre-layout]) and DOM
positions in the context. See [design doc](https://goo.gl/CJbxky) for details.
-[NGCaretNavigator] provides functions for inspecting bidi levels and visual
-ordering of text content, and supports visual left/right caret movements in the
-text. See [design doc](http://bit.ly/2QVAwGq) for details.
-
-
[ICU BiDi]: http://userguide.icu-project.org/transforms/bidi
[UAX#9 Unicode Bidirectional Algorithm]: http://unicode.org/reports/tr9/
[UAX#9 Resolving Embedding Levels]: http://www.unicode.org/reports/tr9/#Resolving_Embedding_Levels
@@ -322,7 +317,6 @@ text. See [design doc](http://bit.ly/2QVAwGq) for details.
[FontBaseline]: ../../../platform/fonts/FontBaseline.h
[NGBaselineAlgorithmType]: ng_baseline.h
[NGBaselineRequest]: ng_baseline.h
-[NGCaretNavigator]: ng_caret_navigator.h
[NGBidiParagraph]: ng_bidi_paragraph.h
[NGBlockNode]: ../ng_block_node.h
[NGBoxFragment]: ../ng_box_fragment.h
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
index d922dbf5684..4e2517fba32 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
@@ -23,12 +23,6 @@ class CORE_EXPORT LayoutNGText : public LayoutText {
}
bool IsLayoutNGObject() const override { return true; }
- protected:
- void InsertedIntoTree() override {
- valid_ng_items_ = false;
- LayoutText::InsertedIntoTree();
- }
-
private:
const NGInlineItems* GetNGInlineItems() const final { return &inline_items_; }
NGInlineItems* GetNGInlineItems() final { return &inline_items_; }
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc
index 0a93d49372b..7fe1900ab55 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc
@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
@@ -26,12 +27,16 @@ scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::GetOrCreate(
new FragmentToNGAbstractInlineTextBoxHashMap();
}
const auto it = g_abstract_inline_text_box_map_->find(&fragment);
- if (it != g_abstract_inline_text_box_map_->end())
+ LayoutText* const layout_text =
+ ToLayoutText(fragment.GetMutableLayoutObject());
+ if (it != g_abstract_inline_text_box_map_->end()) {
+ CHECK(layout_text->HasAbstractInlineTextBox());
return it->value;
- scoped_refptr<AbstractInlineTextBox> obj =
- base::AdoptRef(new NGAbstractInlineTextBox(
- LineLayoutText(ToLayoutText(fragment.GetLayoutObject())), fragment));
+ }
+ scoped_refptr<AbstractInlineTextBox> obj = base::AdoptRef(
+ new NGAbstractInlineTextBox(LineLayoutText(layout_text), fragment));
g_abstract_inline_text_box_map_->Set(&fragment, obj);
+ layout_text->SetHasAbstractInlineTextBox();
return obj;
}
@@ -65,12 +70,6 @@ void NGAbstractInlineTextBox::Detach() {
fragment_ = nullptr;
}
-bool NGAbstractInlineTextBox::HasSoftWrapToNextLine() const {
- return To<NGPhysicalLineBoxFragment>(
- fragment_->ContainerLineBox()->PhysicalFragment())
- .HasSoftWrapToNextLine();
-}
-
const NGPhysicalTextFragment& NGAbstractInlineTextBox::PhysicalTextFragment()
const {
return To<NGPhysicalTextFragment>(fragment_->PhysicalFragment());
@@ -81,13 +80,34 @@ bool NGAbstractInlineTextBox::NeedsLayout() const {
}
bool NGAbstractInlineTextBox::NeedsTrailingSpace() const {
- if (!HasSoftWrapToNextLine())
+ if (!fragment_->Style().CollapseWhiteSpace())
return false;
- const NGPaintFragment* next_fragment = NextTextFragmentForSameLayoutObject();
- if (!next_fragment)
+ const NGPaintFragment& line_box = *fragment_->ContainerLineBox();
+ if (!To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment())
+ .HasSoftWrapToNextLine())
+ return false;
+ const NGPhysicalTextFragment& text_fragment = PhysicalTextFragment();
+ if (text_fragment.EndOffset() >= text_fragment.TextContent().length())
+ return false;
+ if (text_fragment.TextContent()[text_fragment.EndOffset()] != ' ')
+ return false;
+ const NGInlineBreakToken& break_token = *To<NGInlineBreakToken>(
+ To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment()).BreakToken());
+ // TODO(yosin): We should support OOF fragments between |fragment_| and
+ // break token.
+ if (break_token.TextOffset() != text_fragment.EndOffset() + 1)
+ return false;
+ // Check a character in text content after |fragment_| comes from same
+ // layout text of |fragment_|.
+ const NGOffsetMapping& mapping =
+ *NGOffsetMapping::GetFor(fragment_->GetLayoutObject());
+ const NGMappingUnitRange& mapping_units =
+ mapping.GetMappingUnitsForTextContentOffsetRange(
+ text_fragment.EndOffset(), text_fragment.EndOffset() + 1);
+ if (mapping_units.begin() == mapping_units.end())
return false;
- return To<NGPhysicalTextFragment>(next_fragment->PhysicalFragment())
- .StartOffset() != PhysicalTextFragment().EndOffset();
+ const NGOffsetMappingUnit* const mapping_unit = mapping_units.begin();
+ return mapping_unit->GetLayoutObject() == fragment_->GetLayoutObject();
}
const NGPaintFragment*
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
index d74e962fc41..fb2368ed230 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h
@@ -32,7 +32,6 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox {
NGAbstractInlineTextBox(LineLayoutText line_layout_item,
const NGPaintFragment& fragment);
- bool HasSoftWrapToNextLine() const;
const NGPhysicalTextFragment& PhysicalTextFragment() const;
bool NeedsLayout() const;
bool NeedsTrailingSpace() const;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
index 22fc93bf751..be83e72efc7 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
@@ -149,6 +149,17 @@ class CORE_EXPORT NGBaselineList {
void emplace_back(NGBaselineRequest request, LayoutUnit offset);
+#if DCHECK_IS_ON()
+ bool operator==(const NGBaselineList& other) const {
+ for (wtf_size_t i = 0; i < NGBaselineRequest::kTypeIdCount; ++i) {
+ if (offsets_[i] != other.offsets_[i])
+ return false;
+ }
+
+ return true;
+ }
+#endif
+
class const_iterator {
public:
explicit const_iterator(unsigned type_id, const LayoutUnit* offset)
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc
deleted file mode 100644
index 5b2f37f4e13..00000000000
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc
+++ /dev/null
@@ -1,426 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
-
-#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
-
-namespace blink {
-
-std::ostream& operator<<(std::ostream& out,
- const NGCaretNavigator::Position& position) {
- return out << position.index << "/"
- << (position.IsBeforeCharacter() ? "BeforeCharacter"
- : "AfterCharacter");
-}
-
-NGCaretNavigator::~NGCaretNavigator() = default;
-
-NGCaretNavigator::NGCaretNavigator(const LayoutBlockFlow& context)
- : context_(context),
- disallow_transition_(context.GetDocument().Lifecycle()) {
- DCHECK(RuntimeEnabledFeatures::BidiCaretAffinityEnabled());
- DCHECK(context.IsLayoutNGMixin());
- DCHECK(context.ChildrenInline());
- DCHECK(context.GetNGInlineNodeData());
- DCHECK(!context.GetDocument().NeedsLayoutTreeUpdate());
-}
-
-const NGInlineNodeData& NGCaretNavigator::GetData() const {
- return *context_.GetNGInlineNodeData();
-}
-
-const String& NGCaretNavigator::GetText() const {
- return GetData().text_content;
-}
-
-bool NGCaretNavigator::IsBidiEnabled() const {
- return GetData().IsBidiEnabled();
-}
-
-UBiDiLevel NGCaretNavigator::BidiLevelAt(unsigned index) const {
- DCHECK_LT(index, GetText().length());
- if (!IsBidiEnabled())
- return 0;
- return GetData().FindItemForTextOffset(index).BidiLevel();
-}
-
-TextDirection NGCaretNavigator::TextDirectionAt(unsigned index) const {
- UBiDiLevel level = BidiLevelAt(index);
- return DirectionFromLevel(level);
-}
-
-bool NGCaretNavigator::OffsetIsBidiBoundary(unsigned offset) const {
- DCHECK_LE(offset, GetText().length());
- if (!IsBidiEnabled())
- return false;
- if (!offset || offset == GetText().length())
- return false;
- return BidiLevelAt(offset - 1) != BidiLevelAt(offset);
-}
-
-NGCaretNavigator::Position
-NGCaretNavigator::CaretPositionFromTextContentOffsetAndAffinity(
- unsigned offset,
- TextAffinity affinity) const {
- DCHECK_LE(offset, GetText().length());
- // Callers sometimes pass in (0, upstream) or (length, downstream), which
- // originate from legacy callers. Make sure they are fixed up.
- // TODO(xiaochengh): Catch and eliminate such callers.
- if (affinity == TextAffinity::kUpstream) {
- if (offset)
- return {offset - 1, PositionAnchorType::kAfter};
- return {0, PositionAnchorType::kBefore};
- }
-
- if (offset < GetText().length())
- return {offset, PositionAnchorType::kBefore};
- return {GetText().length() - 1, PositionAnchorType::kAfter};
-}
-
-// static
-NGCaretNavigator::MoveDirection NGCaretNavigator::OppositeDirectionOf(
- MoveDirection direction) {
- if (direction == MoveDirection::kTowardsLeft)
- return MoveDirection::kTowardsRight;
- return MoveDirection::kTowardsLeft;
-}
-
-// static
-bool NGCaretNavigator::TowardsSameDirection(MoveDirection move_direction,
- TextDirection text_direction) {
- if (IsLtr(text_direction))
- return move_direction == MoveDirection::kTowardsRight;
- return move_direction == MoveDirection::kTowardsLeft;
-}
-
-NGCaretNavigator::Line NGCaretNavigator::ContainingLineOf(
- unsigned index) const {
- DCHECK_LT(index, GetText().length());
- // TODO(xiaochengh): Make it work for multi-col
- DCHECK(context_.CurrentFragment());
- unsigned last_line_end = 0;
- for (const auto child : context_.CurrentFragment()->Children()) {
- if (!child->IsLineBox())
- continue;
- const auto* line = To<NGPhysicalLineBoxFragment>(child.get());
- const auto* token = To<NGInlineBreakToken>(line->BreakToken());
- const unsigned line_end =
- token->IsFinished() ? GetText().length() : token->TextOffset();
- if (line_end > index)
- return {last_line_end, line_end, line->BaseDirection()};
- last_line_end = line_end;
- }
- NOTREACHED();
- return {};
-}
-
-bool NGCaretNavigator::IsValidCaretPosition(const Position& position) const {
- unsigned index = position.index;
- if (position.IsAfterCharacter() && IsLineBreak(index))
- return false;
- if (IsCollapsedSpaceByLineWrap(index))
- return false;
- if (IsIgnoredInCaretMovement(index))
- return false;
- return true;
-}
-
-bool NGCaretNavigator::IsCollapsibleWhitespace(unsigned index) const {
- DCHECK_LT(index, GetText().length());
- if (GetText()[index] != kSpaceCharacter)
- return false;
- const NGInlineItem& item = GetData().FindItemForTextOffset(index);
- return item.Style()->CollapseWhiteSpace();
-}
-
-bool NGCaretNavigator::IsLineBreak(unsigned index) const {
- DCHECK_LT(index, GetText().length());
- return GetText()[index] == kNewlineCharacter;
-}
-
-bool NGCaretNavigator::IsCollapsedSpaceByLineWrap(unsigned index) const {
- DCHECK_LT(index, GetText().length());
- if (!IsCollapsibleWhitespace(index))
- return false;
- return index + 1 == ContainingLineOf(index).end_offset;
-}
-
-bool NGCaretNavigator::IsIgnoredInCaretMovement(unsigned index) const {
- DCHECK_LT(index, GetText().length());
- const NGInlineItem& item = GetData().FindItemForTextOffset(index);
-
- // Caret navigation works on text, atomic inlines and non-ZWS controls only.
- switch (item.Type()) {
- case NGInlineItem::kText:
- case NGInlineItem::kAtomicInline:
- break;
- case NGInlineItem::kControl:
- if (GetText()[index] == kZeroWidthSpaceCharacter)
- return true;
- break;
- default:
- return true;
- }
-
- // Ignore CSS generate contents.
- // TODO(xiaochengh): This might be general enough to be merged into
- // |NGInlineItem| as a member function.
- DCHECK(item.GetLayoutObject());
- const LayoutObject* object = item.GetLayoutObject();
- if (const auto* text_fragment = ToLayoutTextFragmentOrNull(object)) {
- // ::first-letter |LayoutTextFragment| returns null for |GetNode()|. Check
- // |AssociatedTextNode()| to see if it's created by a text node.
- if (!text_fragment->AssociatedTextNode())
- return true;
- } else {
- if (!object->NonPseudoNode())
- return true;
- }
-
- // Ignore collapsed whitespaces that not visually at line end due to bidi.
- // Caret movement should move over them as if they don't exist to match the
- // existing behavior.
- return IsCollapsedSpaceByLineWrap(index) &&
- index != VisualLastCharacterOf(ContainingLineOf(index));
-}
-
-bool NGCaretNavigator::IsEnterableChildContext(unsigned index) const {
- DCHECK_LT(index, GetText().length());
- if (GetText()[index] != kObjectReplacementCharacter)
- return false;
-
- const NGInlineItem& item = GetData().FindItemForTextOffset(index);
- if (item.Type() != NGInlineItem::kAtomicInline)
- return false;
- DCHECK(item.GetLayoutObject());
- const LayoutObject* object = item.GetLayoutObject();
- if (!object->IsLayoutBlockFlow())
- return false;
- if (!object->NonPseudoNode() || !object->GetNode()->IsElementNode())
- return false;
- const Element* node = ToElement(object->GetNode());
- return !node->GetShadowRoot() || !node->GetShadowRoot()->IsUserAgent();
-}
-
-NGCaretNavigator::Position NGCaretNavigator::LeftEdgeOf(unsigned index) const {
- return EdgeOfInternal(index, MoveDirection::kTowardsLeft);
-}
-
-NGCaretNavigator::Position NGCaretNavigator::RightEdgeOf(unsigned index) const {
- return EdgeOfInternal(index, MoveDirection::kTowardsRight);
-}
-
-NGCaretNavigator::Position NGCaretNavigator::EdgeOfInternal(
- unsigned index,
- MoveDirection edge_direction) const {
- DCHECK_LT(index, GetText().length());
- const TextDirection character_direction = TextDirectionAt(index);
- return {index, TowardsSameDirection(edge_direction, character_direction)
- ? PositionAnchorType::kAfter
- : PositionAnchorType::kBefore};
-}
-
-NGCaretNavigator::VisualCharacterMovementResult
-NGCaretNavigator::LeftCharacterOf(unsigned index) const {
- return MoveCharacterInternal(index, MoveDirection::kTowardsLeft);
-}
-
-NGCaretNavigator::VisualCharacterMovementResult
-NGCaretNavigator::RightCharacterOf(unsigned index) const {
- return MoveCharacterInternal(index, MoveDirection::kTowardsRight);
-}
-
-Vector<int32_t, 32> NGCaretNavigator::CharacterIndicesInVisualOrder(
- const Line& line) const {
- DCHECK(IsBidiEnabled());
-
- Vector<UBiDiLevel, 32> levels;
- levels.ReserveCapacity(line.end_offset - line.start_offset);
- for (unsigned i = line.start_offset; i < line.end_offset; ++i)
- levels.push_back(BidiLevelAt(i));
-
- Vector<int32_t, 32> indices(levels.size());
- NGBidiParagraph::IndicesInVisualOrder(levels, &indices);
-
- for (auto& index : indices)
- index += line.start_offset;
- return indices;
-}
-
-unsigned NGCaretNavigator::VisualMostForwardCharacterOf(
- const Line& line,
- MoveDirection direction) const {
- if (!IsBidiEnabled()) {
- if (direction == MoveDirection::kTowardsLeft)
- return line.start_offset;
- return line.end_offset - 1;
- }
-
- const auto indices_in_visual_order = CharacterIndicesInVisualOrder(line);
- if (direction == MoveDirection::kTowardsLeft)
- return indices_in_visual_order.front();
- return indices_in_visual_order.back();
-}
-
-unsigned NGCaretNavigator::VisualFirstCharacterOf(const Line& line) const {
- return VisualMostForwardCharacterOf(line, IsLtr(line.base_direction)
- ? MoveDirection::kTowardsLeft
- : MoveDirection::kTowardsRight);
-}
-
-unsigned NGCaretNavigator::VisualLastCharacterOf(const Line& line) const {
- return VisualMostForwardCharacterOf(line, IsLtr(line.base_direction)
- ? MoveDirection::kTowardsRight
- : MoveDirection::kTowardsLeft);
-}
-
-NGCaretNavigator::VisualCharacterMovementResult
-NGCaretNavigator::MoveCharacterInternal(unsigned index,
- MoveDirection move_direction) const {
- const Line line = ContainingLineOf(index);
-
- if (index == VisualMostForwardCharacterOf(line, move_direction)) {
- if (TowardsSameDirection(move_direction, line.base_direction)) {
- if (line.end_offset == GetText().length())
- return {VisualMovementResultType::kAfterContext, base::nullopt};
- const Line next_line = ContainingLineOf(line.end_offset);
- return {VisualMovementResultType::kWithinContext,
- VisualFirstCharacterOf(next_line)};
- }
-
- if (!line.start_offset)
- return {VisualMovementResultType::kBeforeContext, base::nullopt};
- const Line last_line = ContainingLineOf(line.start_offset - 1);
- return {VisualMovementResultType::kWithinContext,
- VisualLastCharacterOf(last_line)};
- }
-
- if (!IsBidiEnabled()) {
- if (move_direction == MoveDirection::kTowardsLeft)
- return {VisualMovementResultType::kWithinContext, index - 1};
- return {VisualMovementResultType::kWithinContext, index + 1};
- }
-
- Vector<int32_t, 32> indices_in_visual_order =
- CharacterIndicesInVisualOrder(line);
- const int32_t* visual_location = std::find(
- indices_in_visual_order.begin(), indices_in_visual_order.end(), index);
- DCHECK_NE(visual_location, indices_in_visual_order.end());
- if (move_direction == MoveDirection::kTowardsLeft) {
- DCHECK_NE(visual_location, indices_in_visual_order.begin());
- return {VisualMovementResultType::kWithinContext,
- *std::prev(visual_location)};
- }
- DCHECK_NE(std::next(visual_location), indices_in_visual_order.end());
- return {VisualMovementResultType::kWithinContext,
- *std::next(visual_location)};
-}
-
-NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::LeftPositionOf(
- const Position& caret_position) const {
- return MoveCaretInternal(caret_position, MoveDirection::kTowardsLeft);
-}
-
-NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::RightPositionOf(
- const Position& caret_position) const {
- return MoveCaretInternal(caret_position, MoveDirection::kTowardsRight);
-}
-
-NGCaretNavigator::UnvalidatedVisualCaretMovementResult
-NGCaretNavigator::MoveCaretWithoutValidation(
- const Position& caret_position,
- MoveDirection move_direction) const {
- const unsigned index = caret_position.index;
- const MoveDirection opposite_direction = OppositeDirectionOf(move_direction);
- if (caret_position == EdgeOfInternal(index, opposite_direction)) {
- // TODO(xiaochengh): Consider grapheme cluster
- return {VisualMovementResultType::kWithinContext,
- EdgeOfInternal(index, move_direction),
- !IsIgnoredInCaretMovement(index)};
- }
-
- VisualCharacterMovementResult forward_character =
- MoveCharacterInternal(index, move_direction);
- if (!forward_character.IsWithinContext())
- return {forward_character.type};
-
- DCHECK(forward_character.index.has_value());
- const Position forward_caret =
- EdgeOfInternal(*forward_character.index, opposite_direction);
- return {VisualMovementResultType::kWithinContext, forward_caret};
-}
-
-NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::MoveCaretInternal(
- const Position& caret_position,
- MoveDirection move_direction) const {
- bool has_passed_character = false;
- base::Optional<Position> last_position;
- for (Position runner = caret_position;
- !has_passed_character || !IsValidCaretPosition(runner);) {
- const UnvalidatedVisualCaretMovementResult next =
- MoveCaretWithoutValidation(runner, move_direction);
- if (next.type != VisualMovementResultType::kWithinContext)
- return {next.type, base::nullopt};
-
- if (next.has_passed_character) {
- has_passed_character = true;
-
- const unsigned last_passed_character = next.position->index;
- if (IsEnterableChildContext(last_passed_character))
- return {VisualMovementResultType::kEnteredChildContext, runner};
- }
-
- runner = *next.position;
- last_position = runner;
-
- // TODO(xiaochengh): Handle the case where we reach a different line with a
- // different base direction, which occurs with 'unicode-bidi: plain-text'.
- }
- DCHECK(last_position.has_value());
- return {VisualMovementResultType::kWithinContext, *last_position};
-}
-
-NGCaretNavigator::Position NGCaretNavigator::LeftmostPositionInFirstLine()
- const {
- Line first_line = ContainingLineOf(0);
- unsigned leftmost_character =
- VisualMostForwardCharacterOf(first_line, MoveDirection::kTowardsLeft);
- // TODO(xiaochengh): Handle if the caret position is invalid.
- return LeftEdgeOf(leftmost_character);
-}
-
-NGCaretNavigator::Position NGCaretNavigator::RightmostPositionInFirstLine()
- const {
- Line first_line = ContainingLineOf(0);
- unsigned rightmost_character =
- VisualMostForwardCharacterOf(first_line, MoveDirection::kTowardsRight);
- // TODO(xiaochengh): Handle if the caret position is invalid.
- return RightEdgeOf(rightmost_character);
-}
-
-NGCaretNavigator::Position NGCaretNavigator::LeftmostPositionInLastLine()
- const {
- Line last_line = ContainingLineOf(GetText().length() - 1);
- unsigned leftmost_character =
- VisualMostForwardCharacterOf(last_line, MoveDirection::kTowardsLeft);
- // TODO(xiaochengh): Handle if the caret position is invalid.
- return LeftEdgeOf(leftmost_character);
-}
-
-NGCaretNavigator::Position NGCaretNavigator::RightmostPositionInLastLine()
- const {
- Line last_line = ContainingLineOf(GetText().length() - 1);
- unsigned rightmost_character =
- VisualMostForwardCharacterOf(last_line, MoveDirection::kTowardsRight);
- // TODO(xiaochengh): Handle if the caret position is invalid.
- return RightEdgeOf(rightmost_character);
-}
-
-} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h
deleted file mode 100644
index 6c60d7f9f90..00000000000
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_
-
-#include "base/optional.h"
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/dom/document_lifecycle.h"
-#include "third_party/blink/renderer/core/editing/text_affinity.h"
-#include "third_party/blink/renderer/platform/text/text_direction.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
-
-#include <unicode/ubidi.h>
-
-namespace blink {
-
-class LayoutBlockFlow;
-struct NGInlineNodeData;
-
-// Hosts the |text_content| of an inline formatting context and provides
-// bidi-related utilities, including checking bidi levels, computing visual
-// left/right characters and visual left/right caret movements.
-// Design doc: http://bit.ly/2QVAwGq
-class CORE_EXPORT NGCaretNavigator {
- STACK_ALLOCATED();
-
- public:
- explicit NGCaretNavigator(const LayoutBlockFlow&);
- ~NGCaretNavigator();
-
- const String& GetText() const;
- bool IsBidiEnabled() const;
-
- // Abstraction of a caret position in |text_|.
- enum class PositionAnchorType { kBefore, kAfter };
- struct Position {
- // |index| is character index the |text_| string.
- unsigned index;
- PositionAnchorType type;
-
- bool IsBeforeCharacter() const {
- return type == PositionAnchorType::kBefore;
- }
-
- bool IsAfterCharacter() const { return type == PositionAnchorType::kAfter; }
-
- bool operator==(const Position& other) const {
- return index == other.index && type == other.type;
- }
- };
-
- // Returns the bidi level or resolved direction of the character at the given
- // logical |index|.
- UBiDiLevel BidiLevelAt(unsigned index) const;
- TextDirection TextDirectionAt(unsigned index) const;
-
- // Returns true if the characters at indexes |offset - 1| and |offset| both
- // exist and are at different bidi levels.
- bool OffsetIsBidiBoundary(unsigned offset) const;
-
- // Converts an (offset, affinity) pair into a |Position| type of this class.
- // Intiontionally long name to indicate the hackiness for handling legacy
- // callers.
- Position CaretPositionFromTextContentOffsetAndAffinity(
- unsigned offset,
- TextAffinity affinity) const;
-
- // Returns the visual left/right edge caret position of the character at the
- // given logical |index|.
- Position LeftEdgeOf(unsigned index) const;
- Position RightEdgeOf(unsigned index) const;
-
- // Left/right visual movements
- // TODO(xiaochengh): Handle the following
- // - Grapheme clusters
-
- enum class VisualMovementResultType {
- kWithinContext,
- kBeforeContext,
- kAfterContext,
- kEnteredChildContext
- };
-
- // Given the character at the logical |index|, returns the logical index of
- // the character at its left/right side.
- struct VisualCharacterMovementResult {
- bool IsWithinContext() const {
- return type == VisualMovementResultType::kWithinContext;
- }
- bool IsBeforeContext() const {
- return type == VisualMovementResultType::kBeforeContext;
- }
- bool IsAfterContext() const {
- return type == VisualMovementResultType::kAfterContext;
- }
-
- VisualMovementResultType type;
- base::Optional<unsigned> index;
- };
- VisualCharacterMovementResult LeftCharacterOf(unsigned index) const;
- VisualCharacterMovementResult RightCharacterOf(unsigned index) const;
-
- // Given a caret position, moves it left/right by one grapheme cluster and
- // returns the result.
- // Note: If we end up entering an inline block, the result |Position| is
- // either before or after the inline block, depending on from which side the
- // inline block is entered. For example:
- // RightPositionOf(abc|<inline-block>def</inline-block>ghi)
- // -> {inline-block, PositionAnchorType::kBefore}
- // LeftPositionOf(abc<inline-block>def</inline-block>|ghi)
- // -> {inline-block, PositionAnchorType::kAfter}
- struct VisualCaretMovementResult {
- bool IsWithinContext() const {
- return type == VisualMovementResultType::kWithinContext;
- }
- bool IsBeforeContext() const {
- return type == VisualMovementResultType::kBeforeContext;
- }
- bool IsAfterContext() const {
- return type == VisualMovementResultType::kAfterContext;
- }
- bool HasEnteredChildContext() const {
- return type == VisualMovementResultType::kEnteredChildContext;
- }
-
- VisualMovementResultType type;
- base::Optional<Position> position;
- };
- VisualCaretMovementResult LeftPositionOf(const Position&) const;
- VisualCaretMovementResult RightPositionOf(const Position&) const;
-
- // TODO(xiaochengh): Specify and implement the behavior in edge cases, e.g.,
- // when the leftmost character of the first line is CSS-generated.
- Position LeftmostPositionInFirstLine() const;
- Position RightmostPositionInFirstLine() const;
- Position LeftmostPositionInLastLine() const;
- Position RightmostPositionInLastLine() const;
-
- private:
- // A caret position is invalid if it is:
- // - kAfter to a line break character.
- // - Anchored to a collapsible space that's removed by line wrap.
- // - Anchored to a character that's ignored in caret movement.
- bool IsValidCaretPosition(const Position&) const;
- bool IsLineBreak(unsigned index) const;
- bool IsCollapsibleWhitespace(unsigned index) const;
- bool IsCollapsedSpaceByLineWrap(unsigned index) const;
- bool IsIgnoredInCaretMovement(unsigned index) const;
-
- // Returns true if the character at |index| represents a child block
- // formatting context that can be entered by caret navigation. Such contexts
- // must be atomic inlines (inline block, inline table, ...) and must not host
- // user agent shadow tree (which excludes, e.g., <input> and image alt text).
- bool IsEnterableChildContext(unsigned index) const;
-
- enum class MoveDirection { kTowardsLeft, kTowardsRight };
- static MoveDirection OppositeDirectionOf(MoveDirection);
- static bool TowardsSameDirection(MoveDirection, TextDirection);
-
- // ------ Line-related functions ------
-
- // A line contains a consecutive substring of |GetText()|. The lines should
- // not overlap, and should together cover the entire |GetText()|.
- struct Line {
- unsigned start_offset;
- unsigned end_offset;
- TextDirection base_direction;
- };
- Line ContainingLineOf(unsigned index) const;
- Vector<int32_t, 32> CharacterIndicesInVisualOrder(const Line&) const;
- unsigned VisualMostForwardCharacterOf(const Line&,
- MoveDirection direction) const;
- unsigned VisualLastCharacterOf(const Line&) const;
- unsigned VisualFirstCharacterOf(const Line&) const;
-
- // ------ Implementation of public visual movement functions ------
-
- Position EdgeOfInternal(unsigned index, MoveDirection) const;
- VisualCharacterMovementResult MoveCharacterInternal(unsigned index,
- MoveDirection) const;
- VisualCaretMovementResult MoveCaretInternal(const Position&,
- MoveDirection) const;
-
- // Performs a "minimal" caret movement to the left/right without validating
- // the result. The result might be invalid due to, e.g., anchored to an
- // unallowed character, being visually the same as the input, etc. It's a
- // subroutine of |MoveCaretInternal|, who keeps calling it until both of the
- // folliwng are satisfied:
- // - We've reached a valid caret position.
- // - During the process, the caret has moved passing a character on which
- // |IsIgnoredInCaretMovement| is false (indicated by |has_passed_character|).
- struct UnvalidatedVisualCaretMovementResult {
- VisualMovementResultType type;
- base::Optional<Position> position;
- bool has_passed_character = false;
- };
- UnvalidatedVisualCaretMovementResult MoveCaretWithoutValidation(
- const Position&,
- MoveDirection) const;
-
- const NGInlineNodeData& GetData() const;
-
- const LayoutBlockFlow& context_;
- DocumentLifecycle::DisallowTransitionScope disallow_transition_;
-};
-
-CORE_EXPORT std::ostream& operator<<(std::ostream&,
- const NGCaretNavigator::Position&);
-
-} // namespace blink
-
-#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc
deleted file mode 100644
index 70c99450dd9..00000000000
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator_test.cc
+++ /dev/null
@@ -1,461 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
-
-#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
-
-namespace blink {
-
-class NGCaretNavigatorTest : public RenderingTest,
- private ScopedBidiCaretAffinityForTest {
- public:
- NGCaretNavigatorTest() : ScopedBidiCaretAffinityForTest(true) {}
-
- void SetupHtml(const char* id, String html) {
- SetBodyInnerHTML(html);
-
- block_flow_ = To<LayoutBlockFlow>(GetLayoutObjectByElementId(id));
- DCHECK(block_flow_);
- DCHECK(block_flow_->IsLayoutNGMixin());
- DCHECK(block_flow_->ChildrenInline());
- }
-
- UBiDiLevel BidiLevelAt(unsigned index) const {
- return NGCaretNavigator(*block_flow_).BidiLevelAt(index);
- }
-
- NGCaretNavigator::VisualCharacterMovementResult LeftCharacterOf(
- unsigned index) const {
- return NGCaretNavigator(*block_flow_).LeftCharacterOf(index);
- }
-
- NGCaretNavigator::VisualCharacterMovementResult RightCharacterOf(
- unsigned index) const {
- return NGCaretNavigator(*block_flow_).RightCharacterOf(index);
- }
-
- NGCaretNavigator::Position CaretBefore(unsigned index) const {
- return {index, NGCaretNavigator::PositionAnchorType::kBefore};
- }
-
- NGCaretNavigator::Position CaretAfter(unsigned index) const {
- return {index, NGCaretNavigator::PositionAnchorType::kAfter};
- }
-
- NGCaretNavigator::VisualCaretMovementResult LeftPositionOf(
- const NGCaretNavigator::Position& position) const {
- return NGCaretNavigator(*block_flow_).LeftPositionOf(position);
- }
-
- NGCaretNavigator::VisualCaretMovementResult RightPositionOf(
- const NGCaretNavigator::Position& position) const {
- return NGCaretNavigator(*block_flow_).RightPositionOf(position);
- }
-
- protected:
- const LayoutBlockFlow* block_flow_;
-};
-
-TEST_F(NGCaretNavigatorTest, BidiLevelAtBasic) {
- SetupHtml("container",
- "<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
-
- EXPECT_EQ(0u, BidiLevelAt(0));
- EXPECT_EQ(0u, BidiLevelAt(1));
- EXPECT_EQ(0u, BidiLevelAt(2));
- EXPECT_EQ(1u, BidiLevelAt(3));
- EXPECT_EQ(1u, BidiLevelAt(4));
- EXPECT_EQ(1u, BidiLevelAt(5));
- EXPECT_EQ(2u, BidiLevelAt(6));
- EXPECT_EQ(2u, BidiLevelAt(7));
- EXPECT_EQ(2u, BidiLevelAt(8));
-}
-
-TEST_F(NGCaretNavigatorTest, LeftCharacterOfBasic) {
- SetupHtml("container",
- "<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
-
- EXPECT_TRUE(LeftCharacterOf(0).IsBeforeContext());
-
- EXPECT_TRUE(LeftCharacterOf(1).IsWithinContext());
- EXPECT_EQ(0u, *LeftCharacterOf(1).index);
-
- EXPECT_TRUE(LeftCharacterOf(2).IsWithinContext());
- EXPECT_EQ(1u, *LeftCharacterOf(2).index);
-
- EXPECT_TRUE(LeftCharacterOf(3).IsWithinContext());
- EXPECT_EQ(4u, *LeftCharacterOf(3).index);
-
- EXPECT_TRUE(LeftCharacterOf(4).IsWithinContext());
- EXPECT_EQ(5u, *LeftCharacterOf(4).index);
-
- EXPECT_TRUE(LeftCharacterOf(5).IsWithinContext());
- EXPECT_EQ(8u, *LeftCharacterOf(5).index);
-
- EXPECT_TRUE(LeftCharacterOf(6).IsWithinContext());
- EXPECT_EQ(2u, *LeftCharacterOf(6).index);
-
- EXPECT_TRUE(LeftCharacterOf(7).IsWithinContext());
- EXPECT_EQ(6u, *LeftCharacterOf(7).index);
-
- EXPECT_TRUE(LeftCharacterOf(8).IsWithinContext());
- EXPECT_EQ(7u, *LeftCharacterOf(8).index);
-}
-
-TEST_F(NGCaretNavigatorTest, RightCharacterOfBasic) {
- SetupHtml("container",
- "<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
-
- EXPECT_TRUE(RightCharacterOf(0).IsWithinContext());
- EXPECT_EQ(1u, *RightCharacterOf(0).index);
-
- EXPECT_TRUE(RightCharacterOf(1).IsWithinContext());
- EXPECT_EQ(2u, *RightCharacterOf(1).index);
-
- EXPECT_TRUE(RightCharacterOf(2).IsWithinContext());
- EXPECT_EQ(6u, *RightCharacterOf(2).index);
-
- EXPECT_TRUE(RightCharacterOf(3).IsAfterContext());
-
- EXPECT_TRUE(RightCharacterOf(4).IsWithinContext());
- EXPECT_EQ(3u, *RightCharacterOf(4).index);
-
- EXPECT_TRUE(RightCharacterOf(5).IsWithinContext());
- EXPECT_EQ(4u, *RightCharacterOf(5).index);
-
- EXPECT_TRUE(RightCharacterOf(6).IsWithinContext());
- EXPECT_EQ(7u, *RightCharacterOf(6).index);
-
- EXPECT_TRUE(RightCharacterOf(7).IsWithinContext());
- EXPECT_EQ(8u, *RightCharacterOf(7).index);
-
- EXPECT_TRUE(RightCharacterOf(8).IsWithinContext());
- EXPECT_EQ(5u, *RightCharacterOf(8).index);
-}
-
-TEST_F(NGCaretNavigatorTest, LeftPositionOfBasic) {
- SetupHtml("container",
- "<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(0)).IsWithinContext());
- EXPECT_EQ(CaretBefore(0), *LeftPositionOf(CaretAfter(0)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(1)).IsWithinContext());
- EXPECT_EQ(CaretBefore(0), *LeftPositionOf(CaretBefore(1)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(1)).IsWithinContext());
- EXPECT_EQ(CaretBefore(1), *LeftPositionOf(CaretAfter(1)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(2)).IsWithinContext());
- EXPECT_EQ(CaretBefore(1), *LeftPositionOf(CaretBefore(2)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(2)).IsWithinContext());
- EXPECT_EQ(CaretBefore(2), *LeftPositionOf(CaretAfter(2)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(3)).IsWithinContext());
- EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretBefore(3)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext());
- EXPECT_EQ(CaretAfter(4), *LeftPositionOf(CaretAfter(3)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretAfter(4), *LeftPositionOf(CaretBefore(4)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext());
- EXPECT_EQ(CaretAfter(5), *LeftPositionOf(CaretAfter(4)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext());
- EXPECT_EQ(CaretAfter(5), *LeftPositionOf(CaretBefore(5)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(5)).IsWithinContext());
- EXPECT_EQ(CaretBefore(8), *LeftPositionOf(CaretAfter(5)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(6)).IsWithinContext());
- EXPECT_EQ(CaretBefore(2), *LeftPositionOf(CaretBefore(6)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(6)).IsWithinContext());
- EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(6)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(7)).IsWithinContext());
- EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretBefore(7)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(7)).IsWithinContext());
- EXPECT_EQ(CaretBefore(7), *LeftPositionOf(CaretAfter(7)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(8)).IsWithinContext());
- EXPECT_EQ(CaretBefore(7), *LeftPositionOf(CaretBefore(8)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretAfter(8)).IsWithinContext());
- EXPECT_EQ(CaretBefore(8), *LeftPositionOf(CaretAfter(8)).position);
-}
-
-TEST_F(NGCaretNavigatorTest, RightPositionOfBasic) {
- SetupHtml("container",
- "<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(0)).IsWithinContext());
- EXPECT_EQ(CaretAfter(0), *RightPositionOf(CaretBefore(0)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(0)).IsWithinContext());
- EXPECT_EQ(CaretAfter(1), *RightPositionOf(CaretAfter(0)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(1)).IsWithinContext());
- EXPECT_EQ(CaretAfter(1), *RightPositionOf(CaretBefore(1)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(1)).IsWithinContext());
- EXPECT_EQ(CaretAfter(2), *RightPositionOf(CaretAfter(1)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(2)).IsWithinContext());
- EXPECT_EQ(CaretAfter(2), *RightPositionOf(CaretBefore(2)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
- EXPECT_EQ(CaretAfter(6), *RightPositionOf(CaretAfter(2)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsAfterContext());
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(3)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretAfter(3)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretBefore(4)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(4)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(5)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(5)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(5)).IsWithinContext());
- EXPECT_EQ(CaretBefore(5), *RightPositionOf(CaretAfter(5)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(6)).IsWithinContext());
- EXPECT_EQ(CaretAfter(6), *RightPositionOf(CaretBefore(6)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsWithinContext());
- EXPECT_EQ(CaretAfter(7), *RightPositionOf(CaretAfter(6)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(7)).IsWithinContext());
- EXPECT_EQ(CaretAfter(7), *RightPositionOf(CaretBefore(7)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(7)).IsWithinContext());
- EXPECT_EQ(CaretAfter(8), *RightPositionOf(CaretAfter(7)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(8)).IsWithinContext());
- EXPECT_EQ(CaretAfter(8), *RightPositionOf(CaretBefore(8)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(8)).IsWithinContext());
- EXPECT_EQ(CaretBefore(5), *RightPositionOf(CaretAfter(8)).position);
-}
-
-// Tests below check caret movement crossing line boundaries
-
-TEST_F(NGCaretNavigatorTest, HardLineBreak) {
- SetupHtml("container", "<div id=container>abc<br>def</div>");
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(2)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(3)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext());
-}
-
-TEST_F(NGCaretNavigatorTest, SoftLineWrapAtSpace) {
- SetupHtml("container", "<div id=container style=\"width:0\">abc def</div>");
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(2)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(3)).position);
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretAfter(2), *LeftPositionOf(CaretBefore(4)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext());
-}
-
-TEST_F(NGCaretNavigatorTest, BidiAndSoftLineWrapAtSpaceLtr) {
- LoadAhem();
- SetupHtml("container",
- "<div id=container style='font: 10px/10px Ahem; width: 100px'>"
- "before &#x05D0;&#x05D1;&#x05D2;&#x05D3; "
- "&#x05D4;&#x05D5;&#x05D6;&#x05D7;&#x05D8;&#x05D9;"
- "&#x05DA;&#x05DB;&#x05DC;&#x05DD;&#x05DE;&#x05DF;"
- "&#x05E0;&#x05E1;&#x05E2;&#x05E3;&#x05E4;&#x05E5;"
- "</div>");
-
- // Moving left from "|before DCBA" should be before context
- EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
-
- // Moving right from "before |DCBA" should yield "before D|CBA"
- EXPECT_TRUE(RightPositionOf(CaretAfter(10)).IsWithinContext());
- EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(10)).position);
- EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsWithinContext());
- EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(6)).position);
-
- // Moving left from "before |DCBA" should yield "before| DCBA"
- EXPECT_TRUE(LeftPositionOf(CaretAfter(10)).IsWithinContext());
- EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(10)).position);
- EXPECT_TRUE(LeftPositionOf(CaretAfter(6)).IsWithinContext());
- EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(6)).position);
-
- // Moving right from "before DCBA|" should yield "V|UTSRQPONMLKJIHGFE"
- EXPECT_TRUE(RightPositionOf(CaretBefore(7)).IsWithinContext());
- EXPECT_EQ(CaretBefore(29), *RightPositionOf(CaretBefore(7)).position);
-
- // Moving left from "|VUTSRQPONMLKJIHGFE" should yield "before DCB|A"
- EXPECT_TRUE(LeftPositionOf(CaretAfter(29)).IsWithinContext());
- EXPECT_EQ(CaretAfter(7), *LeftPositionOf(CaretAfter(29)).position);
-
- // Moving right from "VUTSRQPONMLKJIHGFE|" should be after context
- EXPECT_TRUE(RightPositionOf(CaretBefore(12)).IsAfterContext());
-}
-
-TEST_F(NGCaretNavigatorTest, BidiAndSoftLineWrapAtSpaceRtl) {
- LoadAhem();
- SetupHtml(
- "container",
- "<div dir=rtl id=container style='font: 10px/10px Ahem; width: 120px'>"
- "&#x05D0;&#x05D1;&#x05D2;&#x05D3; after encyclopedia"
- "</div>");
-
- // Moving right from "after DCBA|" should be before context
- EXPECT_TRUE(RightPositionOf(CaretBefore(0)).IsBeforeContext());
-
- // Moving left from "after| DCBA" should yield "afte|r DCBA"
- EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(9), *LeftPositionOf(CaretAfter(4)).position);
- EXPECT_TRUE(LeftPositionOf(CaretAfter(9)).IsWithinContext());
- EXPECT_EQ(CaretBefore(9), *LeftPositionOf(CaretAfter(9)).position);
-
- // Moving right from "after| DCBA" should yield "after |DCBA"
- EXPECT_TRUE(RightPositionOf(CaretAfter(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(4)).position);
- EXPECT_TRUE(RightPositionOf(CaretAfter(9)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(9)).position);
-
- // Moving left from "|after DCBA" should yield "encyclopedi|a"
- EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext());
- EXPECT_EQ(CaretBefore(22), *LeftPositionOf(CaretBefore(5)).position);
-
- // Moving right from "encyclopedia|" should yield "a|fter DCBA"
- EXPECT_TRUE(RightPositionOf(CaretAfter(22)).IsWithinContext());
- EXPECT_EQ(CaretAfter(5), *RightPositionOf(CaretAfter(22)).position);
-
- // Moving left from "|encyclopedia" should be after context
- EXPECT_TRUE(LeftPositionOf(CaretBefore(11)).IsAfterContext());
-}
-
-TEST_F(NGCaretNavigatorTest, SoftLineWrapAtHyphen) {
- SetupHtml("container", "<div id=container style=\"width:0\">abc-def</div>");
-
- EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
-
- // 3 -> 4
- EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
- EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretAfter(2)).position);
- EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
- EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretBefore(3)).position);
-
- // 4 -> 5
- EXPECT_TRUE(RightPositionOf(CaretAfter(3)).IsWithinContext());
- EXPECT_EQ(CaretAfter(4), *RightPositionOf(CaretAfter(3)).position);
- EXPECT_TRUE(RightPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretAfter(4), *RightPositionOf(CaretBefore(4)).position);
-
- // 5 -> 4
- EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *LeftPositionOf(CaretBefore(5)).position);
- EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(4), *LeftPositionOf(CaretAfter(4)).position);
-
- // 4 -> 3
- EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position);
- EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretAfter(3)).position);
-
- EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext());
-}
-
-TEST_F(NGCaretNavigatorTest, MoveOverPseudoElementInBidi) {
- SetupHtml("container",
- "<style>.bidi::before,.bidi::after{content:'a\\05D0 b'}</style>"
- "<div id=container>&#x05D0;&#x05D1; &#x05D2;&#x05D3; "
- "<span class=bidi>&#x05D4;&#x05D5;</span>"
- " &#x05D6;&#x05D7; &#x05D8;&#x05D9;</div>");
-
- // Text: "AB CD aAbEFaAb GH IJ"
- // Rendered as: "DC BA aAbFEaAb JI HG"
-
- // Moving right from "BA |" should arrive at "F|E"
- EXPECT_TRUE(RightPositionOf(CaretAfter(5)).IsWithinContext());
- EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(5)).position);
-
- // Moving left from "|FE" should arrive at "BA| "
- EXPECT_TRUE(LeftPositionOf(CaretAfter(10)).IsWithinContext());
- EXPECT_EQ(CaretBefore(5), *LeftPositionOf(CaretAfter(10)).position);
-
- // Moving right from "FE|" should arrive at " |JI"
- EXPECT_TRUE(RightPositionOf(CaretBefore(9)).IsWithinContext());
- EXPECT_EQ(CaretAfter(14), *RightPositionOf(CaretBefore(9)).position);
-
- // Moving left from "| JI" should arrive at "F|E"
- EXPECT_TRUE(LeftPositionOf(CaretBefore(14)).IsWithinContext());
- EXPECT_EQ(CaretAfter(9), *LeftPositionOf(CaretBefore(14)).position);
-}
-
-TEST_F(NGCaretNavigatorTest, EnterableInlineBlock) {
- SetupHtml("container",
- "<div id=container>foo"
- "<span style='display:inline-block'>bar</span>"
- "baz</div>");
-
- // Moving right from "foo|" should enter the span from front.
- EXPECT_TRUE(RightPositionOf(CaretAfter(2)).HasEnteredChildContext());
- EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretAfter(2)).position);
- EXPECT_TRUE(RightPositionOf(CaretBefore(3)).HasEnteredChildContext());
- EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretBefore(3)).position);
-
- // Moving left from "|baz" should enter the span from behind.
- EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).HasEnteredChildContext());
- EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretBefore(4)).position);
- EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).HasEnteredChildContext());
- EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretAfter(3)).position);
-}
-
-TEST_F(NGCaretNavigatorTest, UnenterableInlineBlock) {
- SetupHtml("container",
- "<div id=container>foo"
- "<input value=bar>"
- "baz</div>");
-
- // Moving right from "foo|" should reach "<input>|".
- EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
- EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretAfter(2)).position);
- EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
- EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretBefore(3)).position);
-
- // Moving left from "|baz" should reach "|<input>".
- EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position);
- EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext());
- EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretAfter(3)).position);
-}
-
-} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
index 00c6d5ee3ed..01ea3fc7cdc 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
@@ -19,16 +19,6 @@ namespace blink {
namespace {
-void AssertValidPositionForCaretPositionComputation(
- const PositionWithAffinity& position) {
-#if DCHECK_IS_ON()
- DCHECK(NGOffsetMapping::AcceptsPosition(position.GetPosition()));
- const LayoutObject* layout_object = position.AnchorNode()->GetLayoutObject();
- DCHECK(layout_object);
- DCHECK(layout_object->IsText() || layout_object->IsAtomicInlineLevel());
-#endif
-}
-
// The calculation takes the following input:
// - An inline formatting context as a |LayoutBlockFlow|
// - An offset in the |text_content_| string of the above context
@@ -97,7 +87,7 @@ CaretPositionResolution TryResolveCaretPositionInTextFragment(
TextAffinity affinity) {
const auto& fragment =
To<NGPhysicalTextFragment>(paint_fragment.PhysicalFragment());
- if (fragment.IsAnonymousText())
+ if (fragment.IsGeneratedText())
return CaretPositionResolution();
const NGOffsetMapping& mapping =
@@ -306,7 +296,6 @@ NGCaretPosition ComputeNGCaretPosition(const LayoutBlockFlow& context,
}
NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) {
- AssertValidPositionForCaretPositionComputation(position);
LayoutBlockFlow* context =
NGInlineFormattingContextOf(position.GetPosition());
if (!context)
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc
index 97c719d8e96..4149569a048 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc
@@ -6,8 +6,8 @@
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
@@ -16,13 +16,12 @@ namespace blink {
namespace {
-NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide(
- const NGPaintFragment& fragment,
- NGCaretPositionType position_type) {
+PhysicalRect ComputeLocalCaretRectByBoxSide(const NGPaintFragment& fragment,
+ NGCaretPositionType position_type) {
const bool is_horizontal = fragment.Style().IsHorizontalWritingMode();
DCHECK(fragment.ContainerLineBox());
const NGPaintFragment& line_box = *fragment.ContainerLineBox();
- const NGPhysicalOffset offset_to_line_box =
+ const PhysicalOffset offset_to_line_box =
fragment.InlineOffsetToContainerBox() -
line_box.InlineOffsetToContainerBox();
LayoutUnit caret_height =
@@ -48,12 +47,12 @@ NGPhysicalOffsetRect ComputeLocalCaretRectByBoxSide(
std::swap(caret_width, caret_height);
}
- const NGPhysicalOffset caret_location(caret_left, caret_top);
- const NGPhysicalSize caret_size(caret_width, caret_height);
- return NGPhysicalOffsetRect(caret_location, caret_size);
+ const PhysicalOffset caret_location(caret_left, caret_top);
+ const PhysicalSize caret_size(caret_width, caret_height);
+ return PhysicalRect(caret_location, caret_size);
}
-NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
+PhysicalRect ComputeLocalCaretRectAtTextOffset(
const NGPaintFragment& paint_fragment,
unsigned offset) {
const auto& fragment =
@@ -81,16 +80,15 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
}
// Adjust the location to be relative to the inline formatting context.
- NGPhysicalOffset caret_location = NGPhysicalOffset(caret_left, caret_top) +
- paint_fragment.InlineOffsetToContainerBox();
- NGPhysicalSize caret_size(caret_width, caret_height);
+ PhysicalOffset caret_location = PhysicalOffset(caret_left, caret_top) +
+ paint_fragment.InlineOffsetToContainerBox();
+ PhysicalSize caret_size(caret_width, caret_height);
const NGPaintFragment& context_fragment =
*NGPaintFragment::GetForInlineContainer(fragment.GetLayoutObject());
const NGPaintFragment* line_box = paint_fragment.ContainerLineBox();
- const NGPhysicalOffset line_box_offset =
- line_box->InlineOffsetToContainerBox();
- const NGPhysicalOffsetRect line_box_rect(line_box_offset, line_box->Size());
+ const PhysicalOffset line_box_offset = line_box->InlineOffsetToContainerBox();
+ const PhysicalRect line_box_rect(line_box_offset, line_box->Size());
// For horizontal text, adjust the location in the x direction to ensure that
// it completely falls in the union of line box and containing block, and
@@ -102,7 +100,7 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
std::max(context_fragment.Size().width, line_box_rect.Right());
caret_location.left = std::min(caret_location.left, max_x - caret_width);
caret_location.left = LayoutUnit(caret_location.left.Round());
- return NGPhysicalOffsetRect(caret_location, caret_size);
+ return PhysicalRect(caret_location, caret_size);
}
// Similar adjustment and rounding for vertical text.
@@ -112,7 +110,7 @@ NGPhysicalOffsetRect ComputeLocalCaretRectAtTextOffset(
std::max(context_fragment.Size().height, line_box_rect.Bottom());
caret_location.top = std::min(caret_location.top, max_y - caret_height);
caret_location.top = LayoutUnit(caret_location.top.Round());
- return NGPhysicalOffsetRect(caret_location, caret_size);
+ return PhysicalRect(caret_location, caret_size);
}
LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) {
@@ -125,32 +123,21 @@ LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) {
case NGCaretPositionType::kBeforeBox:
case NGCaretPositionType::kAfterBox: {
DCHECK(fragment.PhysicalFragment().IsBox());
- const NGPhysicalOffsetRect fragment_local_rect =
- ComputeLocalCaretRectByBoxSide(fragment,
- caret_position.position_type);
- return {layout_object, fragment_local_rect.ToLayoutRect()};
+ const PhysicalRect fragment_local_rect = ComputeLocalCaretRectByBoxSide(
+ fragment, caret_position.position_type);
+ return {layout_object, fragment_local_rect};
}
case NGCaretPositionType::kAtTextOffset: {
DCHECK(fragment.PhysicalFragment().IsText());
DCHECK(caret_position.text_offset.has_value());
- const NGPhysicalOffsetRect caret_rect = ComputeLocalCaretRectAtTextOffset(
+ const PhysicalRect caret_rect = ComputeLocalCaretRectAtTextOffset(
fragment, *caret_position.text_offset);
- LayoutRect layout_rect = caret_rect.ToLayoutRect();
-
- // For vertical-rl, convert to "flipped block-flow" coordinates space.
- // See core/layout/README.md#coordinate-spaces for details.
- if (fragment.Style().IsFlippedBlocksWritingMode()) {
- const LayoutBlockFlow* container =
- layout_object->ContainingNGBlockFlow();
- container->FlipForWritingMode(layout_rect);
- }
-
- return {layout_object, layout_rect};
+ return {layout_object, caret_rect};
}
}
NOTREACHED();
- return {layout_object, LayoutRect()};
+ return {layout_object, PhysicalRect()};
}
LocalCaretRect ComputeLocalSelectionRect(
@@ -159,32 +146,21 @@ LocalCaretRect ComputeLocalSelectionRect(
if (!caret_rect.layout_object)
return caret_rect;
- const LayoutObject* layout_object = caret_rect.layout_object;
- const LayoutRect rect = caret_rect.rect;
-
const NGPaintFragment& fragment = *caret_position.fragment;
const NGPaintFragment* line_box = fragment.ContainerLineBox();
// TODO(xiaochengh): We'll hit this DCHECK for caret in empty block if we
// enable LayoutNG in contenteditable.
DCHECK(line_box);
+ PhysicalRect rect = caret_rect.rect;
if (fragment.Style().IsHorizontalWritingMode()) {
- const LayoutUnit line_top = line_box->InlineOffsetToContainerBox().top;
- const LayoutUnit line_height = line_box->Size().height;
- return LocalCaretRect(layout_object, LayoutRect(rect.X(), line_top,
- rect.Width(), line_height));
- }
-
- const LayoutUnit line_top = line_box->InlineOffsetToContainerBox().left;
- const LayoutUnit line_height = line_box->Size().width;
- LayoutRect layout_rect(line_top, rect.Y(), line_height, rect.Height());
- // For vertical-rl, convert to "flipped block-flow" coordinates space.
- // See core/layout/README.md#coordinate-spaces for details.
- if (fragment.Style().IsFlippedBlocksWritingMode()) {
- const LayoutBlockFlow* container = layout_object->ContainingNGBlockFlow();
- container->FlipForWritingMode(layout_rect);
+ rect.SetY(line_box->InlineOffsetToContainerBox().top);
+ rect.SetHeight(line_box->Size().height);
+ } else {
+ rect.SetX(line_box->InlineOffsetToContainerBox().left);
+ rect.SetHeight(line_box->Size().width);
}
- return LocalCaretRect(layout_object, layout_rect);
+ return {caret_rect.layout_object, rect};
}
} // namespace
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc
new file mode 100644
index 00000000000..61a0ecf53b5
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.cc
@@ -0,0 +1,50 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h"
+
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
+
+namespace blink {
+
+void NGDirtyLines::MarkLastFragment() {
+ if (last_fragment_) {
+ // Changes in this LayoutObject may affect the line that contains its
+ // previous object. Mark the line box that contains the last fragment
+ // of the previous object.
+ last_fragment_->LastForSameLayoutObject()->MarkContainingLineBoxDirty();
+ } else {
+ // If there were no fragments so far in this pre-order traversal, mark
+ // the first line box dirty.
+ DCHECK(block_fragment_);
+ if (NGPaintFragment* first_line = block_fragment_->FirstLineBox())
+ first_line->MarkLineBoxDirty();
+ }
+}
+
+void NGDirtyLines::MarkAtTextOffset(unsigned offset) {
+ for (NGPaintFragment* child : block_fragment_->Children()) {
+ // Only the first dirty line is relevant.
+ if (child->IsDirty())
+ break;
+
+ const auto* line =
+ DynamicTo<NGPhysicalLineBoxFragment>(child->PhysicalFragment());
+ if (!line)
+ continue;
+
+ const auto* break_token = To<NGInlineBreakToken>(line->BreakToken());
+ DCHECK(break_token);
+ if (break_token->IsFinished())
+ break;
+
+ if (offset < break_token->TextOffset()) {
+ child->MarkLineBoxDirty();
+ break;
+ }
+ }
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h
new file mode 100644
index 00000000000..5e97423cd95
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h
@@ -0,0 +1,91 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_DIRTY_LINES_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_DIRTY_LINES_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/layout_box.h"
+#include "third_party/blink/renderer/core/layout/layout_inline.h"
+#include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
+
+namespace blink {
+
+// This class computes dirty line boxes.
+class CORE_EXPORT NGDirtyLines {
+ STACK_ALLOCATED();
+
+ public:
+ explicit NGDirtyLines(const NGPaintFragment* block_fragment)
+ : block_fragment_(block_fragment) {
+ DCHECK(block_fragment_);
+ }
+
+ // Call |Handle*| functions for each object by traversing the LayoutObject
+ // tree in pre-order DFS.
+ //
+ // They return |true| when a dirty line was found. Because only the first
+ // dirty line is relevant, no further calls are necessary.
+ bool HandleText(LayoutText* layout_text) {
+ if (layout_text->SelfNeedsLayout()) {
+ MarkLastFragment();
+ return true;
+ }
+ UpdateLastFragment(layout_text->FirstInlineFragment());
+ return false;
+ }
+
+ bool HandleInlineBox(LayoutInline* layout_inline) {
+ if (layout_inline->SelfNeedsLayout()) {
+ MarkLastFragment();
+ return true;
+ }
+ // Do not keep fragments of LayoutInline unless it's a leaf, because
+ // the last fragment of LayoutInline is not the previous fragment of its
+ // descendants.
+ if (UNLIKELY(!layout_inline->FirstChild()))
+ UpdateLastFragment(layout_inline->FirstInlineFragment());
+ return false;
+ }
+
+ bool HandleAtomicInline(LayoutBox* layout_box) {
+ if (layout_box->NeedsLayout()) {
+ MarkLastFragment();
+ return true;
+ }
+ UpdateLastFragment(layout_box->FirstInlineFragment());
+ return false;
+ }
+
+ bool HandleFloatingOrOutOfFlowPositioned(LayoutObject* layout_object) {
+ DCHECK(layout_object->IsFloatingOrOutOfFlowPositioned());
+ if (layout_object->NeedsLayout()) {
+ MarkLastFragment();
+ return true;
+ }
+ // Don't update last fragment. Floats and OOF are opaque.
+ return false;
+ }
+
+ // Mark the line box at the specified text offset dirty.
+ void MarkAtTextOffset(unsigned offset);
+
+ private:
+ void UpdateLastFragment(NGPaintFragment* fragment) {
+ if (fragment)
+ last_fragment_ = fragment;
+ }
+
+ // Mark the line box that contains |last_fragment_| dirty. If |last_fragment_|
+ // is |nullptr|, the first line box is marked as dirty.
+ void MarkLastFragment();
+
+ const NGPaintFragment* block_fragment_;
+ NGPaintFragment* last_fragment_ = nullptr;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_DIRTY_LINES_H_
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
index 1899fd16482..3fa122f9709 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
@@ -4,8 +4,8 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
@@ -254,8 +254,8 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder(
DCHECK(box->style);
const ComputedStyle& style = *box->style;
- NGLogicalOffset offset;
- NGLogicalSize size;
+ LogicalOffset offset;
+ LogicalSize size;
if (!is_empty_line_) {
// The inline box should have the height of the font metrics without the
// line-height property. Compute from style because |box->metrics| includes
@@ -394,12 +394,15 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange(
const unsigned box_data_index = start->box_data_index;
if (!box_data_index)
continue;
+ // |box_data_list_[box_data_index - 1]| is the box for |start| child.
+ // Avoid keeping a pointer to the |BoxData| because it maybe invalidated as
+ // we add to |box_data_list_|.
// As |box_data_index| is converted to start/end of BoxData, update
// |box_data_index| to the parent box, or to 0 if no parent boxes.
// This allows including this box to the nested parent box.
- BoxData* box_data = &box_data_list_[box_data_index - 1];
- start->box_data_index = box_data->parent_box_data_index;
+ start->box_data_index =
+ box_data_list_[box_data_index - 1].parent_box_data_index;
// Find the end line box item.
const unsigned start_index = index;
@@ -415,27 +418,33 @@ unsigned NGInlineLayoutStateStack::UpdateBoxDataFragmentRange(
// because the update is limited only when its |box_data_index| is lower.
while (end->box_data_index && end->box_data_index < box_data_index) {
UpdateBoxDataFragmentRange(line_box, index);
- // Re-compute |box_data| in case |box_data_list_| was reallocated when
- // |UpdateBoxDataFragmentRange| added new fragments.
- box_data = &box_data_list_[box_data_index - 1];
}
if (box_data_index != end->box_data_index)
break;
- end->box_data_index = box_data->parent_box_data_index;
+ end->box_data_index =
+ box_data_list_[box_data_index - 1].parent_box_data_index;
}
// If this is the first range for this BoxData, set it.
- if (!box_data->fragment_end) {
- box_data->fragment_start = start_index;
- box_data->fragment_end = index;
+ if (!box_data_list_[box_data_index - 1].fragment_end) {
+ box_data_list_[box_data_index - 1].SetFragmentRange(start_index, index);
} else {
// This box is fragmented by BiDi reordering. Add a new BoxData for the
// fragmented range.
- box_data->fragmented_box_data_index = box_data_list_.size();
- box_data_list_.emplace_back(*box_data, start_index, index);
+ box_data_list_[box_data_index - 1].fragmented_box_data_index =
+ box_data_list_.size();
+ // Do not use `emplace_back()` here because adding to |box_data_list_| may
+ // reallocate the buffer, but the `BoxData` ctor must run before the
+ // reallocation. Create a new instance and |push_back()| instead.
+ BoxData fragmented_box_data(box_data_list_[box_data_index - 1],
+ start_index, index);
+ box_data_list_.push_back(fragmented_box_data);
}
- return box_data->parent_box_data_index ? start_index : index;
+ // If this box has parent boxes, we need to process it again.
+ if (box_data_list_[box_data_index - 1].parent_box_data_index)
+ return start_index;
+ return index;
}
return index;
}
@@ -572,10 +581,18 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
DCHECK(item);
DCHECK(item->Style());
const ComputedStyle& style = *item->Style();
+
+ NGFragmentGeometry fragment_geometry;
+ fragment_geometry.border_box_size = size;
+ fragment_geometry.border_box_size.inline_size.ClampNegativeToZero();
+ fragment_geometry.padding =
+ NGBoxStrut(padding, IsFlippedLinesWritingMode(style.GetWritingMode()));
+
// Because children are already in the visual order, use LTR for the
// fragment builder so that it should not transform the coordinates for RTL.
NGBoxFragmentBuilder box(item->GetLayoutObject(), &style,
style.GetWritingMode(), TextDirection::kLtr);
+ box.SetInitialFragmentGeometry(fragment_geometry);
box.SetBoxType(NGPhysicalFragment::kInlineBox);
box.SetStyleVariant(item->StyleVariant());
@@ -583,14 +600,12 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
// was fragmented. Fragmenting a line box in block direction is not
// supported today.
box.SetBorderEdges({true, has_line_right_edge, true, has_line_left_edge});
- box.SetInlineSize(size.inline_size.ClampNegativeToZero());
- box.SetBlockSize(size.block_size);
- box.SetPadding(padding);
for (unsigned i = fragment_start; i < fragment_end; i++) {
NGLineBoxFragmentBuilder::Child& child = (*line_box)[i];
if (child.layout_result) {
- box.AddChild(*child.layout_result, child.offset - offset);
+ box.AddChild(child.layout_result->PhysicalFragment(),
+ child.offset - offset);
child.layout_result.reset();
} else if (child.fragment) {
box.AddChild(std::move(child.fragment), child.offset - offset);
@@ -601,7 +616,7 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
// child.offset is the static position wrt. the linebox. As we are adding
// this as a child of an inline level fragment, we adjust the static
// position to be relative to this fragment.
- NGLogicalOffset static_offset = child.offset - offset;
+ LogicalOffset static_offset = child.offset - offset;
box.AddOutOfFlowChildCandidate(oof_box, static_offset,
child.container_direction);
@@ -609,6 +624,10 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
}
}
+ // Inline boxes that produce DisplayItemClient should do full paint
+ // invalidations.
+ item->GetLayoutObject()->SetShouldDoFullPaintInvalidation();
+
box.MoveOutOfFlowDescendantCandidatesToDescendants();
return box.ToInlineBoxFragment();
}
@@ -747,10 +766,14 @@ NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign(
// BoxData contains inline boxes to be created later. Take them into account.
for (const BoxData& box_data : box_data_list_) {
+ // |block_offset| is the top position when the baseline is at 0.
LayoutUnit box_ascent =
-line_box[box_data.fragment_end].offset.block_offset;
- metrics.Unite(
- NGLineHeightMetrics(box_ascent, box_data.size.block_size - box_ascent));
+ LayoutUnit box_descent = box_data.size.block_size - box_ascent;
+ // The top/bottom of inline boxes should not include their paddings.
+ box_ascent -= box_data.padding.line_over;
+ box_descent -= box_data.padding.line_under;
+ metrics.Unite(NGLineHeightMetrics(box_ascent, box_descent));
}
// In quirks mode, metrics is empty if no content.
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
index f6ea8e18c77..2c6d9afb49d 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
@@ -5,7 +5,7 @@
#ifndef NGInlineBoxState_h
#define NGInlineBoxState_h
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h"
#include "third_party/blink/renderer/core/style/computed_style_constants.h"
@@ -106,7 +106,7 @@ struct NGInlineBoxState {
// 2) Performs layout when the positin/size of a box was computed.
// 3) Cache common values for a box.
class CORE_EXPORT NGInlineLayoutStateStack {
- STACK_ALLOCATED();
+ DISALLOW_NEW();
public:
// The box state for the line box.
@@ -204,7 +204,7 @@ class CORE_EXPORT NGInlineLayoutStateStack {
BoxData(unsigned start,
unsigned end,
const NGInlineItem* item,
- NGLogicalSize size)
+ LogicalSize size)
: fragment_start(start), fragment_end(end), item(item), size(size) {}
BoxData(const BoxData& other, unsigned start, unsigned end)
@@ -214,12 +214,17 @@ class CORE_EXPORT NGInlineLayoutStateStack {
size(other.size),
offset(other.offset) {}
+ void SetFragmentRange(unsigned start_index, unsigned end_index) {
+ fragment_start = start_index;
+ fragment_end = end_index;
+ }
+
// The range of child fragments this box contains.
unsigned fragment_start;
unsigned fragment_end;
const NGInlineItem* item;
- NGLogicalSize size;
+ LogicalSize size;
bool has_line_left_edge = false;
bool has_line_right_edge = false;
@@ -230,7 +235,7 @@ class CORE_EXPORT NGInlineLayoutStateStack {
LayoutUnit margin_border_padding_line_left;
LayoutUnit margin_border_padding_line_right;
- NGLogicalOffset offset;
+ LogicalOffset offset;
unsigned parent_box_data_index = 0;
unsigned fragmented_box_data_index = 0;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc
index 23f9cdbfa28..3a9b6f7814d 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc
@@ -41,7 +41,7 @@ NGInlineBreakToken::NGInlineBreakToken(NGLayoutInputNode node)
NGInlineBreakToken::~NGInlineBreakToken() = default;
-#ifndef NDEBUG
+#if DCHECK_IS_ON()
String NGInlineBreakToken::ToString() const {
StringBuilder string_builder;
@@ -55,6 +55,6 @@ String NGInlineBreakToken::ToString() const {
return string_builder.ToString();
}
-#endif // NDEBUG
+#endif // DCHECK_IS_ON()
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h
index f457fe494e2..ba6599469b6 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h
@@ -78,9 +78,9 @@ class CORE_EXPORT NGInlineBreakToken final : public NGBreakToken {
void SetIgnoreFloats() { ignore_floats_ = true; }
bool IgnoreFloats() const { return ignore_floats_; }
-#ifndef NDEBUG
+#if DCHECK_IS_ON()
String ToString() const override;
-#endif // NDEBUG
+#endif
private:
NGInlineBreakToken(NGInlineNode node,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h
index 059776134d3..5992007f223 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h
@@ -18,7 +18,7 @@ class NGInlineItem;
// Because this context is in initial state for when fragmentation occurs and
// some other cases, do not add things that are too expensive to rebuild.
class NGInlineChildLayoutContext {
- STACK_ALLOCATED();
+ DISALLOW_NEW();
public:
// Returns the NGInlineLayoutStateStack in this context.
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
index 9c4720ab64e..5e20fc2a028 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc
@@ -72,7 +72,7 @@ class NGPhysicalFragmentCollectorBase {
for (const auto& child :
To<NGPhysicalContainerFragment>(fragment).Children()) {
- base::AutoReset<NGPhysicalOffset> offset_resetter(
+ base::AutoReset<PhysicalOffset> offset_resetter(
&current_offset_to_root_, current_offset_to_root_ + child.Offset());
base::AutoReset<const NGPhysicalFragment*> fragment_resetter(
&current_fragment_, child.get());
@@ -86,7 +86,7 @@ class NGPhysicalFragmentCollectorBase {
private:
const NGPhysicalFragment* root_fragment_ = nullptr;
const NGPhysicalFragment* current_fragment_ = nullptr;
- NGPhysicalOffset current_offset_to_root_;
+ PhysicalOffset current_offset_to_root_;
Vector<Result> results_;
bool should_stop_traversing_ = false;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
index 77cfcafd79e..801aaf305c2 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc
@@ -110,6 +110,38 @@ unsigned NGLineInfo::InflowEndOffset() const {
return StartOffset();
}
+bool NGLineInfo::ShouldHangTrailingSpaces() const {
+ DCHECK(HasTrailingSpaces());
+ if (!line_style_->AutoWrap())
+ return false;
+ switch (text_align_) {
+ case ETextAlign::kStart:
+ case ETextAlign::kJustify:
+ return true;
+ case ETextAlign::kEnd:
+ case ETextAlign::kCenter:
+ case ETextAlign::kWebkitCenter:
+ return false;
+ case ETextAlign::kLeft:
+ case ETextAlign::kWebkitLeft:
+ return IsLtr(BaseDirection());
+ case ETextAlign::kRight:
+ case ETextAlign::kWebkitRight:
+ return IsRtl(BaseDirection());
+ }
+ NOTREACHED();
+}
+
+void NGLineInfo::UpdateTextAlign() {
+ text_align_ = line_style_->GetTextAlign(IsLastLine());
+
+ if (HasTrailingSpaces() && ShouldHangTrailingSpaces()) {
+ hang_width_ = ComputeTrailingSpaceWidth(&end_offset_for_justify_);
+ } else if (text_align_ == ETextAlign::kJustify) {
+ end_offset_for_justify_ = InflowEndOffset();
+ }
+}
+
LayoutUnit NGLineInfo::ComputeTrailingSpaceWidth(
unsigned* end_offset_out) const {
if (!has_trailing_spaces_) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
index 9e0253f842b..505bf23e4d6 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h
@@ -176,17 +176,22 @@ class CORE_EXPORT NGLineInfo {
void SetTextIndent(LayoutUnit indent) { text_indent_ = indent; }
LayoutUnit TextIndent() const { return text_indent_; }
+ ETextAlign TextAlign() const { return text_align_; }
+ // Update |TextAlign()| and related fields. This depends on |IsLastLine()| and
+ // that must be called after |SetIsLastLine()|.
+ void UpdateTextAlign();
+
NGBfcOffset BfcOffset() const { return bfc_offset_; }
LayoutUnit AvailableWidth() const { return available_width_; }
// The width of this line. Includes trailing spaces if they were preserved.
// Negative width created by negative 'text-indent' is clamped to zero.
LayoutUnit Width() const { return width_.ClampNegativeToZero(); }
- // Same as |Width()| but returns negative value as is.
- LayoutUnit WidthForAlignment() const { return width_; }
- // The width of preserved trailing spaces.
- LayoutUnit ComputeTrailingSpaceWidth(
- unsigned* end_offset_out = nullptr) const;
+ // Same as |Width()| but returns negative value as is. Preserved trailing
+ // spaces may or may not be included, depends on |ShouldHangTrailingSpaces()|.
+ LayoutUnit WidthForAlignment() const { return width_ - hang_width_; }
+ // Width that hangs over the end of the line; e.g., preserved trailing spaces.
+ LayoutUnit HangWidth() const { return hang_width_; }
// Compute |Width()| from |Results()|. Used during line breaking, before
// |Width()| is set. After line breaking, this should match to |Width()|
// without clamping.
@@ -194,6 +199,7 @@ class CORE_EXPORT NGLineInfo {
bool HasTrailingSpaces() const { return has_trailing_spaces_; }
void SetHasTrailingSpaces() { has_trailing_spaces_ = true; }
+ bool ShouldHangTrailingSpaces() const;
// True if this line has overflow, excluding preserved trailing spaces.
bool HasOverflow() const { return has_overflow_; }
@@ -211,6 +217,12 @@ class CORE_EXPORT NGLineInfo {
// End text offset of this line, excluding out-of-flow objects such as
// floating or positioned.
unsigned InflowEndOffset() const;
+ // End text offset for `text-align: justify`. This excludes preserved trailing
+ // spaces. Available only when |TextAlign()| is |kJustify|.
+ unsigned EndOffsetForJustify() const {
+ DCHECK_EQ(text_align_, ETextAlign::kJustify);
+ return end_offset_for_justify_;
+ }
// End item index of this line.
unsigned EndItemIndex() const { return end_item_index_; }
void SetEndItemIndex(unsigned index) { end_item_index_ = index; }
@@ -234,6 +246,10 @@ class CORE_EXPORT NGLineInfo {
private:
bool ComputeNeedsAccurateEndPosition() const;
+ // The width of preserved trailing spaces.
+ LayoutUnit ComputeTrailingSpaceWidth(
+ unsigned* end_offset_out = nullptr) const;
+
const NGInlineItemsData* items_data_ = nullptr;
const ComputedStyle* line_style_ = nullptr;
NGInlineItemResults results_;
@@ -243,11 +259,14 @@ class CORE_EXPORT NGLineInfo {
LayoutUnit available_width_;
LayoutUnit width_;
+ LayoutUnit hang_width_;
LayoutUnit text_indent_;
unsigned start_offset_;
unsigned end_item_index_;
+ unsigned end_offset_for_justify_;
+ ETextAlign text_align_ = ETextAlign::kLeft;
TextDirection base_direction_ = TextDirection::kLtr;
bool use_first_line_style_ = false;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
index d05f2ce5e12..e881df5256a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
@@ -9,7 +9,9 @@
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
@@ -150,6 +152,8 @@ inline bool IsCollapsibleSpace(UChar c) {
// It makes the line breaker easier to handle.
inline bool IsControlItemCharacter(UChar c) {
return c == kNewlineCharacter || c == kTabulationCharacter ||
+ // Make ZWNJ a control character so that it can prevent kerning.
+ c == kZeroWidthNonJoinerCharacter ||
// Include ignorable character here to avoids shaping/rendering
// these glyphs, and to help the line breaker to ignore them.
ShouldIgnore(c);
@@ -275,7 +279,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
template <typename OffsetMappingBuilder>
bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing(
- const String& original_string,
+ const NGInlineNodeData& original_data,
LayoutText* layout_text) {
DCHECK(layout_text);
const NGInlineItems& items = layout_text->InlineItems();
@@ -283,6 +287,8 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing(
if (!old_item0.Length())
return false;
+ const String& original_string = original_data.text_content;
+
// Don't reuse existing items if they might be affected by whitespace
// collapsing.
// TODO(layout-dev): This could likely be optimized further.
@@ -311,12 +317,26 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing(
}
break;
case NGInlineItem::kNotCollapsible: {
- // If the start of the original string was collapsed, it may be
- // restored.
const String& source_text = layout_text->GetText();
- if (source_text.length() && IsCollapsibleSpace(source_text[0]) &&
- original_string[old_item0.StartOffset()] != kSpaceCharacter)
- return false;
+ if (source_text.length() && IsCollapsibleSpace(source_text[0])) {
+ // If the start of the original string was collapsed, it may be
+ // restored.
+ if (original_string[old_item0.StartOffset()] != kSpaceCharacter)
+ return false;
+ // If the start of the original string was not collapsed, and the
+ // collapsible space run contains newline, the newline may be
+ // removed.
+ unsigned offset = 0;
+ UChar c = source_text[0];
+ bool contains_newline =
+ MoveToEndOfCollapsibleSpaces(source_text, &offset, &c);
+ if (contains_newline &&
+ ShouldRemoveNewline(text_, text_.length(), last_item->Style(),
+ StringView(source_text, offset),
+ &new_style)) {
+ return false;
+ }
+ }
break;
}
case NGInlineItem::kCollapsed:
@@ -340,12 +360,22 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing(
return false;
}
- if (bidi_context_.size() && new_style.PreserveNewline()) {
- // We exit and then re-enter all bidi contexts around a forced breaks. We
+ if (new_style.PreserveNewline()) {
+ // We exit and then re-enter all bidi contexts around a forced break. So, We
// must go through the full pipeline to ensure that we exit and enter the
- // contexts in the same in the re-layout.
- if (layout_text->GetText().Contains(kNewlineCharacter))
- return false;
+ // correct bidi contexts the re-layout.
+ if (bidi_context_.size() || layout_text->HasBidiControlInlineItems()) {
+ if (layout_text->GetText().Contains(kNewlineCharacter))
+ return false;
+ }
+ }
+
+ if (UNLIKELY(old_item0.StartOffset() > 0 &&
+ ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
+ layout_text->GetText(), new_style))) {
+ // e.g. <p>abc xyz</p> => <p> xyz</p> where "abc" and " xyz" are different
+ // Text node. |text_| is " \u200Bxyz".
+ return false;
}
for (const NGInlineItem& item : items) {
@@ -398,7 +428,7 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextReusing(
template <>
bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::AppendTextReusing(
- const String&,
+ const NGInlineNodeData&,
LayoutText*) {
NOTREACHED();
return false;
@@ -406,6 +436,30 @@ bool NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::AppendTextReusing(
template <typename OffsetMappingBuilder>
void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendText(
+ LayoutText* layout_text,
+ const NGInlineNodeData* previous_data) {
+ // Mark dirty lines. Clear if marked, only the first dirty line is relevant.
+ if (dirty_lines_ && dirty_lines_->HandleText(layout_text))
+ dirty_lines_ = nullptr;
+
+ // If the LayoutText element hasn't changed, reuse the existing items.
+ if (previous_data && layout_text->HasValidInlineItems()) {
+ if (AppendTextReusing(*previous_data, layout_text)) {
+ return;
+ }
+ }
+
+ // If not create a new item as needed.
+ if (UNLIKELY(layout_text->IsWordBreak())) {
+ AppendBreakOpportunity(layout_text);
+ return;
+ }
+
+ AppendText(layout_text->GetText(), layout_text);
+}
+
+template <typename OffsetMappingBuilder>
+void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendText(
const String& string,
LayoutText* layout_object) {
DCHECK(layout_object);
@@ -623,6 +677,18 @@ void NGInlineItemsBuilderTemplate<
is_empty_inline_ = false; // text item is not empty.
}
+template <typename OffsetMappingBuilder>
+bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
+ ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
+ const String& string,
+ const ComputedStyle& style) const {
+ return text_.IsEmpty() && string.length() > 0 &&
+ string[0] == kSpaceCharacter && !style.CollapseWhiteSpace() &&
+ style.AutoWrap();
+}
+
+// TODO(yosin): We should remove |style| and |string| parameter because of
+// except for testing, we can get them from |LayoutText|.
// Even when without whitespace collapsing, control characters (newlines and
// tabs) are in their own control items to make the line breaker not special.
template <typename OffsetMappingBuilder>
@@ -638,8 +704,8 @@ void NGInlineItemsBuilderTemplate<
// opportunity after leading preserved spaces needs a special code in the line
// breaker. Generate an opportunity to make it easy.
unsigned start = 0;
- if (UNLIKELY(text_.IsEmpty() && string[start] == kSpaceCharacter &&
- style->AutoWrap())) {
+ if (UNLIKELY(ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
+ string, *style))) {
do {
++start;
} while (start < string.length() && string[start] == kSpaceCharacter);
@@ -653,7 +719,9 @@ void NGInlineItemsBuilderTemplate<
if (c == kNewlineCharacter) {
AppendForcedBreak(layout_object);
start++;
- } else if (c == kTabulationCharacter) {
+ continue;
+ }
+ if (c == kTabulationCharacter) {
wtf_size_t end = string.Find(
[](UChar c) { return c != kTabulationCharacter; }, start + 1);
if (end == kNotFound)
@@ -661,11 +729,14 @@ void NGInlineItemsBuilderTemplate<
AppendTextItem(NGInlineItem::kControl,
StringView(string, start, end - start), layout_object);
start = end;
- } else {
+ continue;
+ }
+ // ZWNJ splits item, but it should be text.
+ if (c != kZeroWidthNonJoinerCharacter) {
Append(NGInlineItem::kControl, c, layout_object);
start++;
+ continue;
}
- continue;
}
wtf_size_t end = string.Find(IsControlItemCharacter, start + 1);
@@ -773,6 +844,11 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendAtomicInline(
Append(NGInlineItem::kAtomicInline, kObjectReplacementCharacter,
layout_object);
+ // Mark dirty lines. Clear if marked, only the first dirty line is relevant.
+ if (dirty_lines_ &&
+ dirty_lines_->HandleAtomicInline(ToLayoutBox(layout_object)))
+ dirty_lines_ = nullptr;
+
// When this atomic inline is inside of an inline box, the height of the
// inline box can be different from the height of the atomic inline. Ensure
// the inline box creates a box fragment so that its height is available in
@@ -787,17 +863,25 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendAtomicInline(
template <typename OffsetMappingBuilder>
void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendFloating(
LayoutObject* layout_object) {
- changes_may_affect_earlier_lines_ = true;
AppendOpaque(NGInlineItem::kFloating, kObjectReplacementCharacter,
layout_object);
+
+ // Mark dirty lines. Clear if marked, only the first dirty line is relevant.
+ if (dirty_lines_ &&
+ dirty_lines_->HandleFloatingOrOutOfFlowPositioned(layout_object))
+ dirty_lines_ = nullptr;
}
template <typename OffsetMappingBuilder>
void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
AppendOutOfFlowPositioned(LayoutObject* layout_object) {
- changes_may_affect_earlier_lines_ = true;
AppendOpaque(NGInlineItem::kOutOfFlowPositioned, kObjectReplacementCharacter,
layout_object);
+
+ // Mark dirty lines. Clear if marked, only the first dirty line is relevant.
+ if (dirty_lines_ &&
+ dirty_lines_->HandleFloatingOrOutOfFlowPositioned(layout_object))
+ dirty_lines_ = nullptr;
}
template <typename OffsetMappingBuilder>
@@ -857,6 +941,12 @@ void NGInlineItemsBuilderTemplate<
text_.erase(space_offset);
mapping_builder_.CollapseTrailingSpace(space_offset);
+ // Mark dirty lines. Clear if marked, only the first dirty line is relevant.
+ if (dirty_lines_) {
+ dirty_lines_->MarkAtTextOffset(space_offset);
+ dirty_lines_ = nullptr;
+ }
+
// Keep the item even if the length became zero. This is not needed for
// the layout purposes, but needed to maintain LayoutObject states. See
// |AddEmptyTextItem()|.
@@ -972,7 +1062,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterBlock(
template <typename OffsetMappingBuilder>
void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterInline(
- LayoutObject* node) {
+ LayoutInline* node) {
DCHECK(node);
// https://drafts.csswg.org/css-writing-modes-3/#bidi-control-codes-injection-table
@@ -1013,6 +1103,10 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::EnterInline(
AppendOpaque(NGInlineItem::kOpenTag, node);
+ // Mark dirty lines. Clear if marked, only the first dirty line is relevant.
+ if (dirty_lines_ && dirty_lines_->HandleInlineBox(node))
+ dirty_lines_ = nullptr;
+
if (!NeedsBoxInfo())
return;
@@ -1072,7 +1166,8 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::SetIsSymbolMarker(
template <typename OffsetMappingBuilder>
void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::ClearInlineFragment(
LayoutObject* object) {
- NGInlineNode::ClearInlineFragment(object);
+ object->SetIsInLayoutNGInlineFormattingContext(true);
+ object->SetFirstInlineFragment(nullptr);
}
template <typename OffsetMappingBuilder>
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
index 9d04bba9549..b7c9530edd1 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
@@ -21,6 +21,8 @@ class ComputedStyle;
class LayoutInline;
class LayoutObject;
class LayoutText;
+struct NGInlineNodeData;
+class NGDirtyLines;
// NGInlineItemsBuilder builds a string and a list of NGInlineItem from inlines.
//
@@ -42,8 +44,13 @@ class NGInlineItemsBuilderTemplate {
STACK_ALLOCATED();
public:
- explicit NGInlineItemsBuilderTemplate(Vector<NGInlineItem>* items)
- : items_(items) {}
+ // Create a builder that appends items to |items|.
+ //
+ // If |dirty_lines| is given, this builder calls its functions to mark lines
+ // dirty.
+ explicit NGInlineItemsBuilderTemplate(Vector<NGInlineItem>* items,
+ NGDirtyLines* dirty_lines = nullptr)
+ : items_(items), dirty_lines_(dirty_lines) {}
~NGInlineItemsBuilderTemplate();
String ToString();
@@ -62,11 +69,19 @@ class NGInlineItemsBuilderTemplate {
return changes_may_affect_earlier_lines_;
}
+ // Append a string from |LayoutText|.
+ //
+ // If |previous_data| is given, reuse existing items if they exist and are
+ // reusable. Otherwise appends new items.
+ void AppendText(LayoutText* layout_text,
+ const NGInlineNodeData* previous_data);
+
// Append existing items from an unchanged LayoutObject.
// Returns whether the existing items could be reused.
// NOTE: The state of the builder remains unchanged if the append operation
// fails (i.e. if it returns false).
- bool AppendTextReusing(const String& previous_text, LayoutText* layout_text);
+ bool AppendTextReusing(const NGInlineNodeData& previous_data,
+ LayoutText* layout_text);
// Append a string.
// When appending, spaces are collapsed according to CSS Text, The white space
@@ -114,7 +129,7 @@ class NGInlineItemsBuilderTemplate {
void EnterBlock(const ComputedStyle*);
void ExitBlock();
- void EnterInline(LayoutObject*);
+ void EnterInline(LayoutInline*);
void ExitInline(LayoutObject*);
OffsetMappingBuilder& GetOffsetMappingBuilder() { return mapping_builder_; }
@@ -134,6 +149,8 @@ class NGInlineItemsBuilderTemplate {
Vector<NGInlineItem>* items_;
StringBuilder text_;
+ NGDirtyLines* dirty_lines_;
+
// |mapping_builder_| builds the whitespace-collapsed offset mapping
// during inline collection. It is updated whenever |text_| is modified or a
// white space is collapsed.
@@ -200,11 +217,17 @@ class NGInlineItemsBuilderTemplate {
void AppendGeneratedBreakOpportunity(LayoutObject*);
void Exit(LayoutObject*);
+
+ bool ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
+ const String&,
+ const ComputedStyle&) const;
};
template <>
-CORE_EXPORT bool NGInlineItemsBuilderTemplate<
- NGOffsetMappingBuilder>::AppendTextReusing(const String&, LayoutText*);
+CORE_EXPORT bool
+NGInlineItemsBuilderTemplate<NGOffsetMappingBuilder>::AppendTextReusing(
+ const NGInlineNodeData&,
+ LayoutText*);
template <>
CORE_EXPORT void
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
index 868bbe90a35..56fa24daadc 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
@@ -7,13 +7,12 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
-namespace {
-
// The spec turned into a discussion that may change. Put this logic on hold
// until CSSWG resolves the issue.
// https://github.com/w3c/csswg-drafts/issues/337
@@ -79,7 +78,7 @@ class NGInlineItemsBuilderTest : public NGLayoutTest {
builder.ExitBlock();
text_ = builder.ToString();
ValidateItems();
- CheckReuseItemsProducesSameResult(inputs);
+ CheckReuseItemsProducesSameResult(inputs, builder.HasBidiControls());
for (LayoutObject* anonymous_object : anonymous_objects)
anonymous_object->Destroy();
return text_;
@@ -111,7 +110,12 @@ class NGInlineItemsBuilderTest : public NGLayoutTest {
EXPECT_EQ(current_offset, text_.length());
}
- void CheckReuseItemsProducesSameResult(Vector<Input> inputs) {
+ void CheckReuseItemsProducesSameResult(Vector<Input> inputs,
+ bool has_bidi_controls) {
+ NGInlineNodeData fake_data;
+ fake_data.text_content = text_;
+ fake_data.is_bidi_enabled_ = has_bidi_controls;
+
Vector<NGInlineItem> reuse_items;
NGInlineItemsBuilder reuse_builder(&reuse_items);
for (Input& input : inputs) {
@@ -131,8 +135,9 @@ class NGInlineItemsBuilderTest : public NGLayoutTest {
}
// Try to re-use previous items, or Append if it was not re-usable.
- bool reused = input.layout_text->HasValidInlineItems() &&
- reuse_builder.AppendTextReusing(text_, input.layout_text);
+ bool reused =
+ input.layout_text->HasValidInlineItems() &&
+ reuse_builder.AppendTextReusing(fake_data, input.layout_text);
if (!reused) {
reuse_builder.AppendText(input.text, input.layout_text);
}
@@ -493,6 +498,4 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) {
builder.ToString());
}
-} // namespace
-
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index c8c01e84f7c..f3d4a644b8c 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -96,8 +96,10 @@ NGInlineBoxState* NGInlineLayoutAlgorithm::HandleCloseTag(
box->EnsureTextMetrics(*item.Style(), baseline_type_);
box = box_states_->OnCloseTag(&line_box_, box, baseline_type_,
item.HasEndEdge());
- item.GetLayoutObject()->SetShouldDoFullPaintInvalidation();
- ClearNeedsLayoutIfNeeded(item.GetLayoutObject());
+ // Just clear |NeedsLayout| flags. Culled inline boxes do not need paint
+ // invalidations. If this object produces box fragments,
+ // |NGInlineBoxStateStack| takes care of invalidations.
+ item.GetLayoutObject()->ClearNeedsLayoutWithoutPaintInvalidation();
return box;
}
@@ -239,7 +241,8 @@ void NGInlineLayoutAlgorithm::CreateLine(
}
line_box_.AddChild(text_builder.ToTextFragment(), box->text_top,
item_result.inline_size, item.BidiLevel());
- ClearNeedsLayoutIfNeeded(item.GetLayoutObject());
+ // Text boxes always need full paint invalidations.
+ item.GetLayoutObject()->ClearNeedsLayoutWithFullPaintInvalidation();
} else if (item.Type() == NGInlineItem::kControl) {
PlaceControlItem(item, *line_info, &item_result, box);
} else if (item.Type() == NGInlineItem::kOpenTag) {
@@ -290,6 +293,14 @@ void NGInlineLayoutAlgorithm::CreateLine(
box_states_->UpdateAfterReorder(&line_box_);
}
LayoutUnit inline_size = box_states_->ComputeInlinePositions(&line_box_);
+ if (LayoutUnit hang_width = line_info->HangWidth()) {
+ inline_size -= hang_width;
+ container_builder_.SetHangInlineSize(hang_width);
+
+ if (IsRtl(line_info->BaseDirection())) {
+ line_box_.MoveInInlineDirection(-hang_width);
+ }
+ }
// Truncate the line if 'text-overflow: ellipsis' is set.
if (UNLIKELY(inline_size > line_info->AvailableWidth() &&
@@ -346,6 +357,7 @@ void NGInlineLayoutAlgorithm::CreateLine(
// Even if we have something in-flow, it may just be empty items that
// shouldn't trigger creation of a line. Exit now if that's the case.
if (line_info->IsEmptyLine()) {
+ container_builder_.SetIsSelfCollapsing();
container_builder_.SetIsEmptyLineBox();
container_builder_.SetBaseDirection(line_info->BaseDirection());
container_builder_.AddChildren(line_box_);
@@ -413,7 +425,7 @@ void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item,
// Place a generated content that does not exist in DOM nor in LayoutObject
// tree.
void NGInlineLayoutAlgorithm::PlaceGeneratedContent(
- scoped_refptr<const NGPhysicalFragment> fragment,
+ scoped_refptr<const NGPhysicalTextFragment> fragment,
UBiDiLevel bidi_level,
NGInlineBoxState* box) {
LayoutUnit inline_size = IsHorizontalWritingMode() ? fragment->Size().width
@@ -466,7 +478,7 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result,
NGBoxFragment fragment(ConstraintSpace().GetWritingMode(),
ConstraintSpace().Direction(),
To<NGPhysicalBoxFragment>(
- *item_result->layout_result->PhysicalFragment()));
+ item_result->layout_result->PhysicalFragment()));
NGLineHeightMetrics metrics = fragment.BaselineMetrics(
{NGBaselineAlgorithmType::kAtomicInline, baseline_type_},
ConstraintSpace());
@@ -475,7 +487,7 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result,
LayoutUnit line_top = item_result->margins.line_over - metrics.ascent;
line_box_.AddChild(std::move(item_result->layout_result),
- NGLogicalOffset{inline_offset, line_top},
+ LogicalOffset{inline_offset, line_top},
item_result->inline_size, item.BidiLevel());
}
@@ -486,6 +498,8 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects(
DCHECK(line_info.IsEmptyLine() || !line_box_metrics.IsEmpty())
<< "Non-empty lines must have a valid set of linebox metrics.";
+ bool is_empty_inline = Node().IsEmptyInline();
+
// All children within the linebox are positioned relative to the baseline,
// then shifted later using NGLineBoxFragmentBuilder::MoveInBlockDirection.
LayoutUnit baseline_adjustment =
@@ -516,25 +530,34 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects(
// To correctly determine which "line" block-level out-of-flow positioned
// object is placed on, we need to keep track of if there is any inline-level
// content preceeding it.
- bool has_preceeding_inline_level_content = false;
+ bool has_preceding_inline_level_content = false;
for (NGLineBoxFragmentBuilder::Child& child : line_box_) {
- has_preceeding_inline_level_content |= child.HasInFlowFragment();
+ has_preceding_inline_level_content |= child.HasInFlowFragment();
LayoutObject* box = child.out_of_flow_positioned_box;
if (!box)
continue;
- NGLogicalOffset static_offset(LayoutUnit(), baseline_adjustment);
+ LogicalOffset static_offset(LayoutUnit(), baseline_adjustment);
if (box->StyleRef().IsOriginalDisplayInlineType()) {
// An inline-level OOF element positions itself within the line, at the
// position it would have been if it was in-flow.
static_offset.inline_offset = child.offset.inline_offset;
+
+ // The static-position of inline-level OOF-positioned nodes depends on
+ // previous floats (if any).
+ //
+ // If we are an empty-inline we may not have the correct BFC block-offset
+ // yet. Due to this we need to mark this node as having adjoining
+ // objects, and perform a re-layout if our position shifts.
+ if (is_empty_inline)
+ container_builder_.AddAdjoiningFloatTypes(kAdjoiningInlineOutOfFlow);
} else {
// A block-level OOF element positions itself on the "next" line. However
// only shifts down if there is inline-level content.
static_offset.inline_offset = block_level_inline_offset;
- if (has_preceeding_inline_level_content)
+ if (has_preceding_inline_level_content)
static_offset.block_offset += line_height;
}
@@ -566,9 +589,8 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects(
bool is_empty_inline = Node().IsEmptyInline();
LayoutUnit bfc_block_offset = line_info.BfcOffset().block_offset;
- if (is_empty_inline && ConstraintSpace().FloatsBfcBlockOffset()) {
- bfc_block_offset = *ConstraintSpace().FloatsBfcBlockOffset();
- }
+ if (is_empty_inline && ConstraintSpace().ForcedBfcBlockOffset())
+ bfc_block_offset = *ConstraintSpace().ForcedBfcBlockOffset();
LayoutUnit bfc_line_offset = container_builder_.BfcLineOffset();
@@ -587,7 +609,7 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects(
// Skip any children which aren't positioned floats.
if (!child.layout_result ||
- !child.layout_result->PhysicalFragment()->IsFloating())
+ !child.layout_result->PhysicalFragment().IsFloating())
continue;
LayoutUnit block_offset =
@@ -595,9 +617,8 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects(
// We need to manually account for the flipped-lines writing mode here :(.
if (IsFlippedLinesWritingMode(ConstraintSpace().GetWritingMode())) {
- NGFragment fragment(
- ConstraintSpace().GetWritingMode(),
- To<NGPhysicalBoxFragment>(*child.layout_result->PhysicalFragment()));
+ NGFragment fragment(ConstraintSpace().GetWritingMode(),
+ child.layout_result->PhysicalFragment());
block_offset = -fragment.BlockSize() - block_offset;
}
@@ -629,10 +650,7 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space,
return false;
// Justify the end of visible text, ignoring preserved trailing spaces.
- unsigned end_offset;
- LayoutUnit trailing_spaces_width =
- line_info->ComputeTrailingSpaceWidth(&end_offset);
- space += trailing_spaces_width;
+ unsigned end_offset = line_info->EndOffsetForJustify();
// If this line overflows, fallback to 'text-align: start'.
if (space <= 0)
@@ -700,8 +718,7 @@ LayoutUnit NGInlineLayoutAlgorithm::ApplyTextAlign(NGLineInfo* line_info) {
LayoutUnit space =
line_info->AvailableWidth() - line_info->WidthForAlignment();
- const ComputedStyle& line_style = line_info->LineStyle();
- ETextAlign text_align = line_style.GetTextAlign(line_info->IsLastLine());
+ ETextAlign text_align = line_info->TextAlign();
if (text_align == ETextAlign::kJustify) {
// If justification succeeds, no offset is needed. Expansions are set to
// each |NGInlineItemResult| in |line_info|.
@@ -781,16 +798,22 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
unsigned handled_leading_floats_index =
PositionLeadingFloats(&initial_exclusion_space, &leading_floats);
+ // Only empty-inlines should have the "forced" BFC block-offset set.
+ DCHECK(is_empty_inline || !ConstraintSpace().ForcedBfcBlockOffset());
+
// We query all the layout opportunities on the initial exclusion space up
// front, as if the line breaker may add floats and change the opportunities.
const LayoutOpportunityVector opportunities =
initial_exclusion_space.AllLayoutOpportunities(
- ConstraintSpace().BfcOffset(),
+ {ConstraintSpace().BfcOffset().line_offset,
+ ConstraintSpace().ForcedBfcBlockOffset().value_or(
+ ConstraintSpace().BfcOffset().block_offset)},
ConstraintSpace().AvailableSize().inline_size);
NGExclusionSpace exclusion_space;
const NGInlineBreakToken* break_token = BreakToken();
+ bool is_line_created = false;
LayoutUnit line_block_size;
LayoutUnit block_delta;
const auto* opportunities_it = opportunities.begin();
@@ -818,6 +841,7 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// Reset any state that may have been modified in a previous pass.
container_builder_.Reset();
exclusion_space = initial_exclusion_space;
+ is_line_created = false;
NGLineLayoutOpportunity line_opportunity =
opportunity.ComputeLineLayoutOpportunity(ConstraintSpace(),
@@ -834,8 +858,8 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// *and* the opportunity is smaller than the available inline-size, and the
// container autowraps, continue to the next opportunity.
if (line_info.HasOverflow() &&
- ConstraintSpace().AvailableSize().inline_size !=
- line_opportunity.AvailableFloatInlineSize() &&
+ !line_opportunity.IsEqualToAvailableFloatInlineSize(
+ ConstraintSpace().AvailableSize().inline_size) &&
Node().Style().AutoWrap()) {
// Shapes are *special*. We need to potentially increment the block-delta
// by 1px each loop to properly test each potential position of the line.
@@ -851,11 +875,15 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
line_block_size = LayoutUnit();
++opportunities_it;
}
+ // There must be at least one more opportunity, or we fail to call
+ // |CreateLine()|.
+ DCHECK_NE(opportunities_it, opportunities.end());
continue;
}
PrepareBoxStates(line_info, break_token);
CreateLine(line_opportunity, &line_info, &exclusion_space);
+ is_line_created = true;
// We now can check the block-size of the fragment, and it fits within the
// opportunity.
@@ -894,10 +922,6 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
continue;
}
- if (opportunity.rect.BlockStartOffset() >
- ConstraintSpace().BfcOffset().block_offset)
- container_builder_.SetIsPushedByFloats();
-
// Success!
container_builder_.SetBreakToken(line_breaker.CreateBreakToken(line_info));
@@ -909,10 +933,20 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
// TODO(ikilpatrick): Move this into ng_block_layout_algorithm.
container_builder_.SetBlockSize(
ComputeContentSize(line_info, exclusion_space, line_height));
+
+ // As we aren't an empty inline we should have correctly placed all
+ // our adjoining floats, and shouldn't propagate this information
+ // to siblings.
+ container_builder_.ResetAdjoiningFloatTypes();
+
+ if (opportunity.rect.BlockStartOffset() >
+ ConstraintSpace().BfcOffset().block_offset)
+ container_builder_.SetIsPushedByFloats();
}
break;
}
+ CHECK(is_line_created);
container_builder_.SetExclusionSpace(std::move(exclusion_space));
container_builder_.MoveOutOfFlowDescendantCandidatesToDescendants();
return container_builder_.ToLineBoxFragment();
@@ -948,13 +982,13 @@ unsigned NGInlineLayoutAlgorithm::PositionLeadingFloats(
? kFloatTypeLeft
: kFloatTypeRight);
- // If we are an empty inline, and don't have the special floats BFC
+ // If we are an empty inline, and don't have the special forced BFC
// block-offset yet, there is no way to position any floats.
- if (is_empty_inline && !ConstraintSpace().FloatsBfcBlockOffset())
+ if (is_empty_inline && !ConstraintSpace().ForcedBfcBlockOffset())
continue;
LayoutUnit origin_bfc_block_offset =
- is_empty_inline ? *ConstraintSpace().FloatsBfcBlockOffset()
+ is_empty_inline ? *ConstraintSpace().ForcedBfcBlockOffset()
: ConstraintSpace().BfcOffset().block_offset;
NGPositionedFloat positioned_float = PositionFloat(
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h
index e12d473e761..d93ab75c097 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h
@@ -80,7 +80,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
const NGLineInfo&,
NGInlineItemResult*,
NGInlineBoxState*);
- void PlaceGeneratedContent(scoped_refptr<const NGPhysicalFragment>,
+ void PlaceGeneratedContent(scoped_refptr<const NGPhysicalTextFragment>,
UBiDiLevel,
NGInlineBoxState*);
NGInlineBoxState* PlaceAtomicInline(const NGInlineItem&,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
index f1d65e70440..1c01a0a125e 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
@@ -41,7 +41,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) {
auto* block_flow =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"));
NGInlineNode inline_node(block_flow);
- NGLogicalSize size(LayoutUnit(50), LayoutUnit(20));
+ LogicalSize size(LayoutUnit(50), LayoutUnit(20));
NGConstraintSpace constraint_space =
NGConstraintSpaceBuilder(
@@ -53,23 +53,20 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) {
NGInlineChildLayoutContext context;
scoped_refptr<const NGLayoutResult> layout_result =
inline_node.Layout(constraint_space, nullptr, &context);
- auto* line1 =
- To<NGPhysicalLineBoxFragment>(layout_result->PhysicalFragment());
- EXPECT_FALSE(line1->BreakToken()->IsFinished());
+ const auto& line1 = layout_result->PhysicalFragment();
+ EXPECT_FALSE(line1.BreakToken()->IsFinished());
// Perform 2nd layout with the break token from the 1st line.
scoped_refptr<const NGLayoutResult> layout_result2 =
- inline_node.Layout(constraint_space, line1->BreakToken(), &context);
- auto* line2 =
- To<NGPhysicalLineBoxFragment>(layout_result2->PhysicalFragment());
- EXPECT_FALSE(line2->BreakToken()->IsFinished());
+ inline_node.Layout(constraint_space, line1.BreakToken(), &context);
+ const auto& line2 = layout_result2->PhysicalFragment();
+ EXPECT_FALSE(line2.BreakToken()->IsFinished());
// Perform 3rd layout with the break token from the 2nd line.
scoped_refptr<const NGLayoutResult> layout_result3 =
- inline_node.Layout(constraint_space, line2->BreakToken(), &context);
- auto* line3 =
- To<NGPhysicalLineBoxFragment>(layout_result3->PhysicalFragment());
- EXPECT_TRUE(line3->BreakToken()->IsFinished());
+ inline_node.Layout(constraint_space, line2.BreakToken(), &context);
+ const auto& line3 = layout_result3->PhysicalFragment();
+ EXPECT_TRUE(line3.BreakToken()->IsFinished());
}
TEST_F(NGInlineLayoutAlgorithmTest, GenerateHyphen) {
@@ -168,10 +165,10 @@ TEST_F(NGInlineLayoutAlgorithmTest,
To<NGPhysicalLineBoxFragment>(*container->Children()[0]);
EXPECT_EQ(1u, linebox.Children().size());
- EXPECT_EQ(NGPhysicalSize(), linebox.Size());
+ EXPECT_EQ(PhysicalSize(), linebox.Size());
const auto& oof_container = To<NGPhysicalBoxFragment>(*linebox.Children()[0]);
- EXPECT_EQ(NGPhysicalSize(), oof_container.Size());
+ EXPECT_EQ(PhysicalSize(), oof_container.Size());
}
// This test ensures that if an inline box generates (or does not generate) box
@@ -234,13 +231,12 @@ TEST_F(NGInlineLayoutAlgorithmTest, ContainerBorderPadding) {
NGConstraintSpace::CreateFromLayoutObject(*block_flow);
scoped_refptr<const NGLayoutResult> layout_result = block_node.Layout(space);
- auto* block_box =
- To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment());
EXPECT_TRUE(layout_result->BfcBlockOffset().has_value());
EXPECT_EQ(0, *layout_result->BfcBlockOffset());
EXPECT_EQ(0, layout_result->BfcLineOffset());
- NGPhysicalOffset line_offset = block_box->Children()[0].Offset();
+ PhysicalOffset line_offset =
+ layout_result->PhysicalFragment().Children()[0].Offset();
EXPECT_EQ(5, line_offset.left);
EXPECT_EQ(10, line_offset.top);
}
@@ -270,9 +266,9 @@ TEST_F(NGInlineLayoutAlgorithmTest, MAYBE_VerticalAlignBottomReplaced) {
scoped_refptr<const NGLayoutResult> layout_result =
inline_node.Layout(space, nullptr, &context);
- auto* line = To<NGPhysicalLineBoxFragment>(layout_result->PhysicalFragment());
- EXPECT_EQ(LayoutUnit(96), line->Size().height);
- NGPhysicalOffset img_offset = line->Children()[0].Offset();
+ const auto& line = layout_result->PhysicalFragment();
+ EXPECT_EQ(LayoutUnit(96), line.Size().height);
+ PhysicalOffset img_offset = line.Children()[0].Offset();
EXPECT_EQ(LayoutUnit(0), img_offset.top);
}
@@ -316,7 +312,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, TextFloatsAroundFloatsBefore) {
To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get());
auto* container_fragment =
To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get());
- Vector<NGPhysicalOffset> line_offsets;
+ Vector<PhysicalOffset> line_offsets;
for (const auto& child : container_fragment->Children()) {
if (!child->IsLineBox())
continue;
@@ -368,7 +364,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, TextFloatsAroundInlineFloatThatFitsOnLine) {
// Two lines.
EXPECT_EQ(2u, block_box->Children().size());
- NGPhysicalOffset first_line_offset = block_box->Children()[1].Offset();
+ PhysicalOffset first_line_offset = block_box->Children()[1].Offset();
// 30 == narrow-float's width.
EXPECT_EQ(LayoutUnit(30), first_line_offset.left);
@@ -500,7 +496,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, InkOverflow) {
EXPECT_EQ(LayoutUnit(10), box_fragment.Size().height);
- NGPhysicalOffsetRect ink_overflow = paint_fragment->InkOverflow();
+ PhysicalRect ink_overflow = paint_fragment->InkOverflow();
EXPECT_EQ(LayoutUnit(-5), ink_overflow.offset.top);
EXPECT_EQ(LayoutUnit(20), ink_overflow.size.height);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
index e2e32f01d1b..846eb8c7ef1 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include <memory>
+#include "build/build_config.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_list_marker.h"
@@ -15,6 +16,7 @@
#include "third_party/blink/renderer/core/layout/logical_values.h"
#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_dirty_lines.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h"
@@ -62,83 +64,28 @@ unsigned EstimateOffsetMappingItemsCount(const LayoutBlockFlow& block) {
return EstimateInlineItemsCount(block) / 4;
}
-// This class marks appropriate line box fragments as dirty.
-//
-// |CollectInlinesInternal| calls this class when traversing the LayoutObject
-// tree in pre-order DFS
-class NGLineBoxMarker {
- STACK_ALLOCATED();
-
- public:
- NGLineBoxMarker(NGPaintFragment* block_fragment)
- : block_fragment_(block_fragment) {
- DCHECK(block_fragment_);
- }
-
- bool HandleText(LayoutText* layout_text) {
- if (layout_text->SelfNeedsLayout())
- return Mark();
- return UpdateLastFragment(layout_text->FirstInlineFragment());
- }
-
- bool HandleInlineBox(LayoutInline* layout_inline) {
- if (layout_inline->SelfNeedsLayout())
- return Mark();
-
- // Do not keep fragments of LayoutInline unless it's a leaf, because
- // the last fragment of LayoutInline is not the previous fragment of its
- // descendants.
- if (layout_inline->FirstChild())
- return false;
- return UpdateLastFragment(layout_inline->FirstInlineFragment());
- }
-
- bool HandleAtomicInline(LayoutBox* layout_box) {
- if (layout_box->NeedsLayout())
- return Mark();
- return UpdateLastFragment(layout_box->FirstInlineFragment());
- }
-
- private:
- bool Mark() {
- if (last_fragment_) {
- // Changes in this LayoutObject may affect the line that contains its
- // previous object. Mark the line box that contains the last fragment
- // of the previous object.
- last_fragment_->LastForSameLayoutObject()->MarkContainingLineBoxDirty();
- } else {
- // If there were no fragments so far in this pre-order traversal, mark
- // the first line box dirty.
- DCHECK(block_fragment_);
- if (NGPaintFragment* first_line = block_fragment_->FirstLineBox())
- first_line->MarkLineBoxDirty();
- }
- return true;
- }
-
- bool UpdateLastFragment(NGPaintFragment* fragment) {
- if (fragment)
- last_fragment_ = fragment;
- return false;
- }
-
- NGPaintFragment* block_fragment_;
- NGPaintFragment* last_fragment_ = nullptr;
-};
-
// This class has the same interface as NGInlineItemsBuilder but does nothing
// except tracking if floating or out-of-flow objects are added.
//
// |MarkLineBoxesDirty| uses this class to traverse tree without buildling
// |NGInlineItem|.
class ItemsBuilderForMarkLineBoxesDirty {
+ STACK_ALLOCATED();
+
public:
- void AppendText(const String&, LayoutText*) {}
- bool AppendTextReusing(const String&, LayoutText*) { return false; }
+ ItemsBuilderForMarkLineBoxesDirty(NGDirtyLines* dirty_lines)
+ : dirty_lines_(dirty_lines) {}
+ void AppendText(LayoutText* layout_text, const NGInlineItemsData*) {
+ if (dirty_lines_ && dirty_lines_->HandleText(layout_text))
+ dirty_lines_ = nullptr;
+ }
void AppendOpaque(NGInlineItem::NGInlineItemType,
LayoutObject*) {}
- void AppendBreakOpportunity(LayoutObject*) {}
- void AppendAtomicInline(LayoutObject*) {}
+ void AppendAtomicInline(LayoutObject* layout_object) {
+ if (dirty_lines_ &&
+ dirty_lines_->HandleAtomicInline(ToLayoutBox(layout_object)))
+ dirty_lines_ = nullptr;
+ }
void AppendFloating(LayoutObject*) {
has_floating_or_out_of_flow_positioned_ = true;
}
@@ -148,7 +95,10 @@ class ItemsBuilderForMarkLineBoxesDirty {
void SetIsSymbolMarker(bool) {}
void EnterBlock(const ComputedStyle*) {}
void ExitBlock() {}
- void EnterInline(LayoutObject*) {}
+ void EnterInline(LayoutInline* layout_inline) {
+ if (dirty_lines_ && dirty_lines_->HandleInlineBox(layout_inline))
+ dirty_lines_ = nullptr;
+ }
void ExitInline(LayoutObject*) {}
bool ShouldAbort() const {
@@ -164,12 +114,12 @@ class ItemsBuilderForMarkLineBoxesDirty {
}
void ClearInlineFragment(LayoutObject* object) {
- NGInlineNode::ClearInlineFragment(object);
+ DCHECK(object->IsInLayoutNGInlineFormattingContext());
}
void ClearNeedsLayout(LayoutObject* object) {
object->ClearNeedsLayout();
- object->ClearNeedsCollectInlines();
+ DCHECK(!object->NeedsCollectInlines());
ClearInlineFragment(object);
}
@@ -178,6 +128,7 @@ class ItemsBuilderForMarkLineBoxesDirty {
}
private:
+ NGDirtyLines* dirty_lines_;
bool has_floating_or_out_of_flow_positioned_ = false;
};
@@ -194,8 +145,7 @@ class ItemsBuilderForMarkLineBoxesDirty {
template <typename ItemsBuilder>
void CollectInlinesInternal(LayoutBlockFlow* block,
ItemsBuilder* builder,
- String* previous_text,
- NGLineBoxMarker* marker) {
+ const NGInlineNodeData* previous_data) {
builder->EnterBlock(block->Style());
LayoutObject* node = GetLayoutObjectForFirstChildNode(block);
@@ -203,28 +153,11 @@ void CollectInlinesInternal(LayoutBlockFlow* block,
LayoutNGListItem::FindSymbolMarkerLayoutText(block);
while (node) {
if (LayoutText* layout_text = ToLayoutTextOrNull(node)) {
- // If the LayoutText element hasn't changed, reuse the existing items.
-
- // if the last ended with space and this starts with space, do not allow
- // reuse. builder->MightCollapseWithPreceding(*previous_text)
- bool item_reused = false;
- if (previous_text && layout_text->HasValidInlineItems())
- item_reused = builder->AppendTextReusing(*previous_text, layout_text);
-
- // If not create a new item as needed.
- if (!item_reused) {
- if (UNLIKELY(layout_text->IsWordBreak()))
- builder->AppendBreakOpportunity(layout_text);
- else
- builder->AppendText(layout_text->GetText(), layout_text);
- }
+ builder->AppendText(layout_text, previous_data);
if (symbol == layout_text)
builder->SetIsSymbolMarker(true);
- if (marker && marker->HandleText(layout_text))
- marker = nullptr;
-
builder->ClearNeedsLayout(layout_text);
} else if (node->IsFloating()) {
@@ -252,9 +185,6 @@ void CollectInlinesInternal(LayoutBlockFlow* block,
// signal the presence of a non-text object to the unicode bidi
// algorithm.
builder->AppendAtomicInline(node);
-
- if (marker && marker->HandleAtomicInline(ToLayoutBox(node)))
- marker = nullptr;
}
builder->ClearInlineFragment(node);
@@ -268,9 +198,6 @@ void CollectInlinesInternal(LayoutBlockFlow* block,
builder->EnterInline(layout_inline);
- if (marker && marker->HandleInlineBox(layout_inline))
- marker = nullptr;
-
// Traverse to children if they exist.
if (LayoutObject* child = layout_inline->FirstChild()) {
node = child;
@@ -302,7 +229,65 @@ void CollectInlinesInternal(LayoutBlockFlow* block,
builder->ExitBlock();
}
-static bool NeedsShaping(const NGInlineItem& item) {
+// Returns whether this text should break shaping. Even within a box, text runs
+// that have different shaping properties need to break shaping.
+inline bool ShouldBreakShapingBeforeText(const NGInlineItem& item,
+ const NGInlineItem& start_item,
+ const ComputedStyle& start_style,
+ const Font& start_font,
+ TextDirection start_direction) {
+ DCHECK_EQ(item.Type(), NGInlineItem::kText);
+ DCHECK(item.Style());
+ const ComputedStyle& style = *item.Style();
+ if (&style != &start_style) {
+ const Font& font = style.GetFont();
+ if (&font != &start_font && font != start_font)
+ return true;
+ }
+
+ // The resolved direction and run segment properties must match to shape
+ // across for HarfBuzzShaper.
+ return item.Direction() != start_direction ||
+ !item.EqualsRunSegment(start_item);
+}
+
+// Returns whether the start of this box should break shaping.
+inline bool ShouldBreakShapingBeforeBox(const NGInlineItem& item,
+ const Font& start_font) {
+ DCHECK_EQ(item.Type(), NGInlineItem::kOpenTag);
+ DCHECK(item.Style());
+ const ComputedStyle& style = *item.Style();
+
+ // These properties values must break shaping.
+ // https://drafts.csswg.org/css-text-3/#boundary-shaping
+ if ((style.MayHavePadding() && !style.PaddingStart().IsZero()) ||
+ (style.MayHaveMargin() && !style.MarginStart().IsZero()) ||
+ style.BorderStartWidth() ||
+ style.VerticalAlign() != EVerticalAlign::kBaseline)
+ return true;
+
+ return false;
+}
+
+// Returns whether the end of this box should break shaping.
+inline bool ShouldBreakShapingAfterBox(const NGInlineItem& item,
+ const Font& start_font) {
+ DCHECK_EQ(item.Type(), NGInlineItem::kCloseTag);
+ DCHECK(item.Style());
+ const ComputedStyle& style = *item.Style();
+
+ // These properties values must break shaping.
+ // https://drafts.csswg.org/css-text-3/#boundary-shaping
+ if ((style.MayHavePadding() && !style.PaddingEnd().IsZero()) ||
+ (style.MayHaveMargin() && !style.MarginEnd().IsZero()) ||
+ style.BorderEndWidth() ||
+ style.VerticalAlign() != EVerticalAlign::kBaseline)
+ return true;
+
+ return false;
+}
+
+inline bool NeedsShaping(const NGInlineItem& item) {
return item.Type() == NGInlineItem::kText && !item.TextShapeResult();
}
@@ -363,17 +348,31 @@ void NGInlineNode::PrepareLayoutIfNeeded() {
block_flow->ResetNGInlineNodeData();
}
+ if (RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) {
+ if (const NGPaintFragment* fragment = block_flow->PaintFragment()) {
+ NGDirtyLines dirty_lines(fragment);
+ PrepareLayout(std::move(previous_data), &dirty_lines);
+ return;
+ }
+ }
+ PrepareLayout(std::move(previous_data), /* dirty_lines */ nullptr);
+}
+
+void NGInlineNode::PrepareLayout(
+ std::unique_ptr<NGInlineNodeData> previous_data,
+ NGDirtyLines* dirty_lines) {
// Scan list of siblings collecting all in-flow non-atomic inlines. A single
// NGInlineNode represent a collection of adjacent non-atomic inlines.
NGInlineNodeData* data = MutableData();
DCHECK(data);
- CollectInlines(data, previous_data.get());
+ CollectInlines(data, previous_data.get(), dirty_lines);
SegmentText(data);
ShapeText(data, previous_data.get());
ShapeTextForFirstLineIfNeeded(data);
AssociateItemsWithInlines(data);
DCHECK_EQ(data, MutableData());
+ LayoutBlockFlow* block_flow = GetLayoutBlockFlow();
block_flow->ClearNeedsCollectInlines();
#if DCHECK_IS_ON()
@@ -418,7 +417,7 @@ void NGInlineNode::ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow,
NGInlineItemsBuilderForOffsetMapping builder(&items);
builder.GetOffsetMappingBuilder().ReserveCapacity(
EstimateOffsetMappingItemsCount(*layout_block_flow));
- CollectInlinesInternal(layout_block_flow, &builder, nullptr, nullptr);
+ CollectInlinesInternal(layout_block_flow, &builder, nullptr);
// For non-NG object, we need the text, and also the inline items to resolve
// bidi levels. Otherwise |data| already has the text from the pre-layout
@@ -443,6 +442,14 @@ const NGOffsetMapping* NGInlineNode::GetOffsetMapping(
LayoutBlockFlow* layout_block_flow) {
DCHECK(!layout_block_flow->GetDocument().NeedsLayoutTreeUpdate());
+ if (UNLIKELY(layout_block_flow->NeedsLayout())) {
+ // TODO(kojii): This shouldn't happen, but is not easy to fix all cases.
+ // Return nullptr so that callers can chose to fail gracefully, or
+ // null-deref. crbug.com/946004
+ NOTREACHED();
+ return nullptr;
+ }
+
// If |layout_block_flow| is LayoutNG, compute from |NGInlineNode|.
if (layout_block_flow->IsLayoutNGMixin()) {
NGInlineNode node(layout_block_flow);
@@ -466,23 +473,16 @@ const NGOffsetMapping* NGInlineNode::GetOffsetMapping(
// parent LayoutInline where possible, and joining all text content in a single
// string to allow bidi resolution and shaping of the entire block.
void NGInlineNode::CollectInlines(NGInlineNodeData* data,
- NGInlineNodeData* previous_data) {
+ NGInlineNodeData* previous_data,
+ NGDirtyLines* dirty_lines) {
DCHECK(data->text_content.IsNull());
DCHECK(data->items.IsEmpty());
LayoutBlockFlow* block = GetLayoutBlockFlow();
block->WillCollectInlines();
- // If we have PaintFragment, mark line boxes dirty from |NeedsLayout| flag.
- base::Optional<NGLineBoxMarker> marker;
- if (NGPaintFragment* block_fragment = block->PaintFragment())
- marker.emplace(block_fragment);
-
- String* previous_text =
- previous_data ? &previous_data->text_content : nullptr;
data->items.ReserveCapacity(EstimateInlineItemsCount(*block));
- NGInlineItemsBuilder builder(&data->items);
- CollectInlinesInternal(block, &builder, previous_text,
- marker.has_value() ? &*marker : nullptr);
+ NGInlineItemsBuilder builder(&data->items, dirty_lines);
+ CollectInlinesInternal(block, &builder, previous_data);
data->text_content = builder.ToString();
// Set |is_bidi_enabled_| for all UTF-16 strings for now, because at this
@@ -678,22 +678,27 @@ void NGInlineNode::ShapeText(NGInlineItemsData* data,
if (item.Type() == NGInlineItem::kText) {
if (!item.Length())
continue;
- // Shape adjacent items together if the font and direction matches to
- // allow ligatures and kerning to apply.
- // Also run segment properties must match because NGInlineItem gives
- // pre-segmented range to HarfBuzzShaper.
- // TODO(kojii): Figure out the exact conditions under which this
- // behavior is desirable.
- if (font != item.Style()->GetFont() || direction != item.Direction() ||
- !item.EqualsRunSegment(start_item))
+ if (ShouldBreakShapingBeforeText(item, start_item, start_style, font,
+ direction)) {
+ break;
+ }
+ // Break shaping at ZWNJ so that it prevents kerning. ZWNJ is always at
+ // the beginning of an item for this purpose; see NGInlineItemsBuilder.
+ if (text_content[item.StartOffset()] == kZeroWidthNonJoinerCharacter)
break;
end_offset = item.EndOffset();
num_text_items++;
- } else if (item.Type() == NGInlineItem::kOpenTag ||
- item.Type() == NGInlineItem::kCloseTag) {
- // These items are opaque to shaping.
- // Opaque items cannot have text, such as Object Replacement Characters,
- // since such characters can affect shaping.
+ } else if (item.Type() == NGInlineItem::kOpenTag) {
+ if (ShouldBreakShapingBeforeBox(item, font)) {
+ break;
+ }
+ // Should not have any characters to be opaque to shaping.
+ DCHECK_EQ(0u, item.Length());
+ } else if (item.Type() == NGInlineItem::kCloseTag) {
+ if (ShouldBreakShapingAfterBox(item, font)) {
+ break;
+ }
+ // Should not have any characters to be opaque to shaping.
DCHECK_EQ(0u, item.Length());
} else {
break;
@@ -841,22 +846,6 @@ void NGInlineNode::ShapeTextForFirstLineIfNeeded(NGInlineNodeData* data) {
item.SetStyleVariant(NGStyleVariant::kFirstLine);
}
- // Check if we have a first-line anonymous inline box. It is the first
- // open-tag if we have.
- for (auto& item : first_line_items->items) {
- if (item.Type() == NGInlineItem::kOpenTag) {
- if (item.layout_object_->IsAnonymous() &&
- item.layout_object_->IsLayoutInline() &&
- item.layout_object_->Parent() == GetLayoutBox() &&
- ToLayoutInline(item.layout_object_)->IsFirstLineAnonymous()) {
- item.SetShouldCreateBoxFragment();
- }
- break;
- }
- if (item.Type() != NGInlineItem::kBidiControl)
- break;
- }
-
// Re-shape if the font is different.
if (needs_reshape || FirstLineNeedsReshape(*first_line_style, *block_style))
ShapeText(first_line_items.get());
@@ -876,12 +865,18 @@ void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) {
// Items split from a LayoutObject should be consecutive.
DCHECK(associated_objects.insert(object).is_new_entry);
#endif
+ layout_text->ClearHasBidiControlInlineItems();
+ bool has_bidi_control = false;
NGInlineItem* begin = item;
for (++item; item != items.end(); ++item) {
if (item->GetLayoutObject() != object)
break;
+ if (item->Type() == NGInlineItem::kBidiControl)
+ has_bidi_control = true;
}
layout_text->SetInlineItems(begin, item);
+ if (has_bidi_control)
+ layout_text->SetHasBidiControlInlineItems();
continue;
}
++item;
@@ -891,7 +886,7 @@ void NGInlineNode::AssociateItemsWithInlines(NGInlineNodeData* data) {
void NGInlineNode::ClearAssociatedFragments(
const NGPhysicalFragment& fragment,
const NGBlockBreakToken* block_break_token) {
- auto* block_flow = To<LayoutBlockFlow>(fragment.GetLayoutObject());
+ auto* block_flow = To<LayoutBlockFlow>(fragment.GetMutableLayoutObject());
if (!block_flow->ChildrenInline())
return;
NGInlineNode node = NGInlineNode(block_flow);
@@ -949,11 +944,28 @@ scoped_refptr<const NGLayoutResult> NGInlineNode::Layout(
const auto* inline_break_token = To<NGInlineBreakToken>(break_token);
NGInlineLayoutAlgorithm algorithm(*this, constraint_space, inline_break_token,
context);
- return algorithm.Layout();
+ auto layout_result = algorithm.Layout();
+
+#if defined(OS_ANDROID)
+ // Cached position data is crucial for line breaking performance and is
+ // preserved across layouts to speed up subsequent layout passes due to
+ // reflow, page zoom, window resize, etc. On Android though reflows are less
+ // common, page zoom isn't used (instead uses pinch-zoom), and the window
+ // typically can't be resized (apart from rotation). To reduce memory usage
+ // discard the cached position data after layout.
+ NGInlineNodeData* data = MutableData();
+ for (auto& item : data->items) {
+ if (item.shape_result_)
+ item.shape_result_->DiscardPositionData();
+ }
+#endif // defined(OS_ANDROID)
+
+ return layout_result;
}
const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer(
const NGConstraintSpace& constraint_space) {
+ DCHECK(RuntimeEnabledFeatures::LayoutNGLineCacheEnabled());
// |SelfNeedsLayout()| is the most common reason that we check it earlier.
LayoutBlockFlow* block_flow = GetLayoutBlockFlow();
DCHECK(!block_flow->SelfNeedsLayout());
@@ -987,7 +999,8 @@ const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer(
return nullptr;
// Propagating OOF needs re-layout.
- if (!cached_layout_result->OutOfFlowPositionedDescendants().IsEmpty())
+ if (cached_layout_result->PhysicalFragment()
+ .HasOutOfFlowPositionedDescendants())
return nullptr;
// Cached fragments are not for intermediate layout.
@@ -1002,11 +1015,9 @@ const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer(
if (!paint_fragment)
return nullptr;
- if (!MarkLineBoxesDirty(block_flow))
+ if (!MarkLineBoxesDirty(block_flow, paint_fragment))
return nullptr;
- PrepareLayoutIfNeeded();
-
if (Data().changes_may_affect_earlier_lines_)
return nullptr;
@@ -1018,10 +1029,19 @@ const NGPaintFragment* NGInlineNode::ReusableLineBoxContainer(
// Removals of LayoutObject already marks relevant line boxes dirty by calling
// |DirtyLinesFromChangedChild()|, but insertions and style changes are not
// marked yet.
-bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow) {
- NGLineBoxMarker marker(block_flow->PaintFragment());
- ItemsBuilderForMarkLineBoxesDirty builder;
- CollectInlinesInternal(block_flow, &builder, nullptr, &marker);
+bool NGInlineNode::MarkLineBoxesDirty(LayoutBlockFlow* block_flow,
+ const NGPaintFragment* paint_fragment) {
+ DCHECK(RuntimeEnabledFeatures::LayoutNGLineCacheEnabled());
+ NGDirtyLines dirty_lines(paint_fragment);
+ if (block_flow->NeedsCollectInlines()) {
+ std::unique_ptr<NGInlineNodeData> previous_data;
+ previous_data.reset(block_flow->TakeNGInlineNodeData());
+ block_flow->ResetNGInlineNodeData();
+ PrepareLayout(std::move(previous_data), &dirty_lines);
+ return true;
+ }
+ ItemsBuilderForMarkLineBoxesDirty builder(&dirty_lines);
+ CollectInlinesInternal(block_flow, &builder, nullptr);
return !builder.ShouldAbort();
}
@@ -1042,7 +1062,7 @@ static LayoutUnit ComputeContentSize(
/* out_writing_mode */ writing_mode,
/* is_new_fc */ false)
.SetTextDirection(style.Direction())
- .SetAvailableSize({available_inline_size, NGSizeIndefinite})
+ .SetAvailableSize({available_inline_size, kIndefiniteSize})
.SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
.SetReplacedPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
.SetIsIntermediateLayout(true)
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
index 94052b21d64..a3bcb07a50b 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
@@ -9,6 +9,7 @@
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h"
+#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -16,10 +17,11 @@ namespace blink {
class NGBlockBreakToken;
class NGConstraintSpace;
+class NGDirtyLines;
class NGInlineChildLayoutContext;
+class NGInlineNodeLegacy;
class NGLayoutResult;
class NGOffsetMapping;
-class NGInlineNodeLegacy;
struct MinMaxSize;
struct NGInlineItemsData;
@@ -59,8 +61,11 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
// Instruct to re-compute |PrepareLayout| on the next layout.
void InvalidatePrepareLayoutForTest() {
- GetLayoutBlockFlow()->ResetNGInlineNodeData();
+ LayoutBlockFlow* block_flow = GetLayoutBlockFlow();
+ block_flow->ResetNGInlineNodeData();
DCHECK(!IsPrepareLayoutFinished());
+ // There shouldn't be paint fragment if NGInlineNodeData does not exist.
+ block_flow->SetPaintFragment(nullptr, nullptr);
}
const NGInlineItemsData& ItemsData(bool is_first_line) const {
@@ -100,21 +105,18 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
String ToString() const;
- // A helper function for NGInlineItemsBuilder.
- static void ClearInlineFragment(LayoutObject* object) {
- object->SetIsInLayoutNGInlineFormattingContext(true);
- object->SetFirstInlineFragment(nullptr);
- }
-
protected:
bool IsPrepareLayoutFinished() const;
// Prepare inline and text content for layout. Must be called before
// calling the Layout method.
void PrepareLayoutIfNeeded();
+ void PrepareLayout(std::unique_ptr<NGInlineNodeData> previous_data,
+ NGDirtyLines* dirty_lines);
void CollectInlines(NGInlineNodeData*,
- NGInlineNodeData* previous_data = nullptr);
+ NGInlineNodeData* previous_data = nullptr,
+ NGDirtyLines* dirty_lines = nullptr);
void SegmentText(NGInlineNodeData*);
void SegmentScriptRuns(NGInlineNodeData*);
void SegmentFontOrientation(NGInlineNodeData*);
@@ -124,7 +126,7 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
void ShapeTextForFirstLineIfNeeded(NGInlineNodeData*);
void AssociateItemsWithInlines(NGInlineNodeData*);
- bool MarkLineBoxesDirty(LayoutBlockFlow*);
+ bool MarkLineBoxesDirty(LayoutBlockFlow*, const NGPaintFragment*);
NGInlineNodeData* MutableData() {
return To<LayoutBlockFlow>(box_)->GetNGInlineNodeData();
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
index 64c19d1af7b..833d63944c7 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
@@ -19,6 +19,8 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData {
return static_cast<TextDirection>(base_direction_);
}
+ bool IsEmptyInline() const { return is_empty_inline_; }
+
private:
const NGInlineItemsData& ItemsData(bool is_first_line) const {
return !is_first_line || !first_line_items_
@@ -29,6 +31,7 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData {
base_direction_ = static_cast<unsigned>(direction);
}
+ friend class NGInlineItemsBuilderTest;
friend class NGInlineNode;
friend class NGInlineNodeLegacy;
friend class NGInlineNodeForTest;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
index 2851a3b14ec..4ee6e041cba 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
@@ -21,6 +21,7 @@
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/svg_names.h"
namespace blink {
@@ -77,7 +78,9 @@ class NGInlineNodeForTest : public NGInlineNode {
void ShapeText() { NGInlineNode::ShapeText(MutableData()); }
bool MarkLineBoxesDirty() {
- return NGInlineNode::MarkLineBoxesDirty(GetLayoutBlockFlow());
+ LayoutBlockFlow* block_flow = GetLayoutBlockFlow();
+ return NGInlineNode::MarkLineBoxesDirty(block_flow,
+ block_flow->PaintFragment());
}
};
@@ -132,9 +135,9 @@ class NGInlineNodeTest : public NGLayoutTest {
nullptr /* break_token */, &context)
.Layout();
- const auto* line =
+ const auto& line =
To<NGPhysicalLineBoxFragment>(result->PhysicalFragment());
- for (const auto& child : line->Children()) {
+ for (const auto& child : line.Children()) {
fragments_out->push_back(To<NGPhysicalTextFragment>(child.get()));
}
}
@@ -546,7 +549,7 @@ TEST_F(NGInlineNodeTest, NeedsCollectInlinesOnSetText) {
Element* container = GetElementById("container");
Element* parent = GetElementById("parent");
- Text* text = ToText(parent->firstChild());
+ auto* text = To<Text>(parent->firstChild());
EXPECT_FALSE(text->GetLayoutObject()->NeedsCollectInlines());
EXPECT_FALSE(parent->GetLayoutObject()->NeedsCollectInlines());
EXPECT_FALSE(container->GetLayoutObject()->NeedsCollectInlines());
@@ -579,28 +582,35 @@ struct StyleChangeData {
kAll = kText | kParentAndAbove,
};
unsigned needs_collect_inlines;
+ base::Optional<bool> is_line_dirty;
} style_change_data[] = {
// Changing color, text-decoration, etc. should not re-run
// |CollectInlines()|.
- {"#parent.after { color: red; }", StyleChangeData::kNone},
+ {"#parent.after { color: red; }", StyleChangeData::kNone, false},
{"#parent.after { text-decoration-line: underline; }",
- StyleChangeData::kNone},
+ StyleChangeData::kNone, false},
// Changing fonts should re-run |CollectInlines()|.
- {"#parent.after { font-size: 200%; }", StyleChangeData::kAll},
+ {"#parent.after { font-size: 200%; }", StyleChangeData::kAll, true},
+ // Changing from/to out-of-flow should re-rerun |CollectInlines()|.
+ {"#parent.after { position: absolute; }", StyleChangeData::kContainer,
+ true},
+ {"#parent { position: absolute; }"
+ "#parent.after { position: initial; }",
+ StyleChangeData::kContainer, true},
// List markers are captured in |NGInlineItem|.
{"#parent.after { display: list-item; }", StyleChangeData::kContainer},
{"#parent { display: list-item; list-style-type: none; }"
"#parent.after { list-style-type: disc; }",
- StyleChangeData::kTextAndParent},
+ StyleChangeData::kParent},
{"#parent { display: list-item; }"
"#container.after { list-style-type: none; }",
- StyleChangeData::kTextAndParent},
+ StyleChangeData::kParent},
// Changing properties related with bidi resolution should re-run
// |CollectInlines()|.
{"#parent.after { unicode-bidi: bidi-override; }",
- StyleChangeData::kParentAndAbove},
+ StyleChangeData::kParentAndAbove, true},
{"#container.after { unicode-bidi: bidi-override; }",
- StyleChangeData::kContainer},
+ StyleChangeData::kContainer, false},
};
std::ostream& operator<<(std::ostream& os, const StyleChangeData& data) {
@@ -630,7 +640,7 @@ TEST_P(StyleChangeTest, NeedsCollectInlinesOnStyle) {
Element* container = GetElementById("container");
Element* parent = GetElementById("parent");
- Text* text = ToText(parent->firstChild());
+ auto* text = To<Text>(parent->firstChild());
EXPECT_FALSE(text->GetLayoutObject()->NeedsCollectInlines());
EXPECT_FALSE(parent->GetLayoutObject()->NeedsCollectInlines());
EXPECT_FALSE(container->GetLayoutObject()->NeedsCollectInlines());
@@ -654,6 +664,15 @@ TEST_P(StyleChangeTest, NeedsCollectInlinesOnStyle) {
Element* next = GetElementById("next");
EXPECT_FALSE(previous->GetLayoutObject()->NeedsCollectInlines());
EXPECT_FALSE(next->GetLayoutObject()->NeedsCollectInlines());
+
+ if (data.is_line_dirty &&
+ RuntimeEnabledFeatures::LayoutNGLineCacheEnabled()) {
+ layout_block_flow_ = ToLayoutNGBlockFlow(container->GetLayoutObject());
+ auto lines = MarkLineBoxesDirty();
+ EXPECT_EQ(*data.is_line_dirty, lines[0]->IsDirty());
+ }
+
+ ForceLayout(); // Ensure running layout does not crash.
}
using CreateNode = Node* (*)(Document&);
@@ -964,47 +983,88 @@ TEST_F(NGInlineNodeTest, SpaceRestoredByInsertingWord) {
}
// Test marking line boxes when inserting a span before the first child.
-TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnInsert) {
+TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnInsert) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
+ <style>
+ .abspos { position: absolute; }
+ .float { float: left; }
+ </style>
<div id=container style="font-size: 10px; width: 10ch">
12345678
</div>
)HTML");
- Element* span = GetDocument().CreateElementForBinding("span");
+ Node* insert = (*GetParam())(GetDocument());
Element* container = GetElementById("container");
- container->insertBefore(span, container->firstChild());
+ container->insertBefore(insert, container->firstChild());
auto lines = MarkLineBoxesDirty();
EXPECT_TRUE(lines[0]->IsDirty());
}
// Test marking line boxes when appending a span.
-TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend) {
+TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnAppend) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
+ <style>
+ .abspos { position: absolute; }
+ .float { float: left; }
+ </style>
<div id=container style="font-size: 10px; width: 10ch">
12345678
</div>
)HTML");
- Element* span = GetDocument().CreateElementForBinding("span");
- layout_block_flow_->GetNode()->appendChild(span);
+ Node* insert = (*GetParam())(GetDocument());
+ layout_block_flow_->GetNode()->appendChild(insert);
auto lines = MarkLineBoxesDirty();
EXPECT_TRUE(lines[0]->IsDirty());
}
// Test marking line boxes when appending a span on 2nd line.
-TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend2) {
+TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnAppend2) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
+ <style>
+ .abspos { position: absolute; }
+ .float { float: left; }
+ </style>
<div id=container style="font-size: 10px; width: 10ch">
12345678
2234
</div>
)HTML");
- Element* span = GetDocument().CreateElementForBinding("span");
- layout_block_flow_->GetNode()->appendChild(span);
+ Node* insert = (*GetParam())(GetDocument());
+ layout_block_flow_->GetNode()->appendChild(insert);
+
+ auto lines = MarkLineBoxesDirty();
+ EXPECT_FALSE(lines[0]->IsDirty());
+ EXPECT_TRUE(lines[1]->IsDirty());
+}
+
+// Test marking line boxes when appending a span on 2nd line.
+TEST_P(NodeInsertTest, MarkLineBoxesDirtyOnAppendAfterBR) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
+ SetupHtml("container", R"HTML(
+ <style>
+ .abspos { position: absolute; }
+ .float { float: left; }
+ </style>
+ <div id=container style="font-size: 10px; width: 10ch">
+ <br>
+ <br>
+ </div>
+ )HTML");
+
+ Node* insert = (*GetParam())(GetDocument());
+ layout_block_flow_->GetNode()->appendChild(insert);
auto lines = MarkLineBoxesDirty();
EXPECT_FALSE(lines[0]->IsDirty());
@@ -1013,6 +1073,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnAppend2) {
// Test marking line boxes when removing a span.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
1234<span id=t>5678</span>
@@ -1028,6 +1090,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove) {
// Test marking line boxes when removing a span.
TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveFirst) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", String(R"HTML(
<div id=container style="font-size: 10px; width: 10ch">)HTML") +
GetParam() + R"HTML(<span>after</span>
@@ -1045,6 +1109,8 @@ TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveFirst) {
// Test marking line boxes when removing a span on 2nd line.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove2) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
12345678
@@ -1062,6 +1128,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnRemove2) {
// Test marking line boxes when removing a text node on 2nd line.
TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveAfterBR) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", String(R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
line 1
@@ -1081,9 +1149,45 @@ TEST_P(NodeParameterTest, MarkLineBoxesDirtyOnRemoveAfterBR) {
ForceLayout(); // Ensure running layout does not crash.
}
+TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnEndSpaceCollapsed) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
+ SetupHtml("container", R"HTML(
+ <style>
+ div {
+ font-size: 10px;
+ width: 8ch;
+ }
+ #empty {
+ background: yellow; /* ensure fragment is created */
+ }
+ #target {
+ display: inline-block;
+ }
+ </style>
+ <div id=container>
+ 1234567890
+ 1234567890
+ <span id=empty> </span>
+ <span id=target></span></div>
+ )HTML");
+
+ // Removing #target makes the spaces before it to be collapsed.
+ Element* target = GetElementById("target");
+ target->remove();
+
+ auto lines = MarkLineBoxesDirty();
+ EXPECT_FALSE(lines[0]->IsDirty());
+ EXPECT_TRUE(lines[1]->IsDirty());
+
+ ForceLayout(); // Ensure running layout does not crash.
+}
+
// Test marking line boxes when the first span has NeedsLayout. The span is
// culled.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirst) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
<span id=t>1234</span>5678
@@ -1100,6 +1204,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirst) {
// Test marking line boxes when the first span has NeedsLayout. The span has a
// box fragment.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirstWithBox) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
<span id=t style="background: blue">1234</span>5678
@@ -1115,6 +1221,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutFirstWithBox) {
// Test marking line boxes when a span has NeedsLayout. The span is culled.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayout) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
12345678
@@ -1133,6 +1241,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayout) {
// Test marking line boxes when a span has NeedsLayout. The span has a box
// fragment.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px; width: 10ch">
12345678
@@ -1152,6 +1262,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnNeedsLayoutWithBox) {
// The parent span has a box fragment, and wraps, so that its fragment
// is seen earlier in pre-order DFS.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnChildOfWrappedBox) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="font-size: 10px">
<span style="background: yellow">
@@ -1172,6 +1284,8 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyOnChildOfWrappedBox) {
// Test marking line boxes when a span has NeedsLayout. The span has a box
// fragment.
TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyInInlineBlock) {
+ if (!RuntimeEnabledFeatures::LayoutNGLineCacheEnabled())
+ return;
SetupHtml("container", R"HTML(
<div id=container style="display: inline-block; font-size: 10px">
12345678<br>
@@ -1226,6 +1340,71 @@ TEST_F(NGInlineNodeTest, RemoveInlineNodeDataIfBlockObtainsBlockChild) {
EXPECT_FALSE(layout_block_flow_->HasNGInlineNodeData());
}
+// Test inline objects are initialized when |SplitFlow()| moves them.
+TEST_F(NGInlineNodeTest, ClearFirstInlineFragmentOnSplitFlow) {
+ SetBodyInnerHTML(R"HTML(
+ <div>
+ <span id=outer_span>
+ <span id=inner_span>1234</span>
+ </span>
+ </div>
+ )HTML");
+
+ // Keep the text fragment to compare later.
+ Element* inner_span = GetElementById("inner_span");
+ Node* text = inner_span->firstChild();
+ scoped_refptr<NGPaintFragment> text_fragment_before_split =
+ text->GetLayoutObject()->FirstInlineFragment();
+ EXPECT_NE(text_fragment_before_split.get(), nullptr);
+
+ // Append <div> to <span>. causing SplitFlow().
+ Element* outer_span = GetElementById("outer_span");
+ Element* div = GetDocument().CreateRawElement(html_names::kDivTag);
+ outer_span->appendChild(div);
+
+ // Update tree but do NOT update layout. At this point, there's no guarantee,
+ // but there are some clients (e.g., Schroll Anchor) who try to read
+ // associated fragments.
+ //
+ // NGPaintFragment is owned by LayoutNGBlockFlow. Because the original owner
+ // no longer has an inline formatting context, the NGPaintFragment subtree is
+ // destroyed, and should not be accessible.
+ GetDocument().UpdateStyleAndLayoutTree();
+ scoped_refptr<NGPaintFragment> text_fragment_before_layout =
+ text->GetLayoutObject()->FirstInlineFragment();
+ EXPECT_EQ(text_fragment_before_layout, nullptr);
+
+ // Update layout. There should be a different instance of the text fragment.
+ UpdateAllLifecyclePhasesForTest();
+ scoped_refptr<NGPaintFragment> text_fragment_after_layout =
+ text->GetLayoutObject()->FirstInlineFragment();
+ EXPECT_NE(text_fragment_before_split, text_fragment_after_layout);
+
+ // Check it is the one owned by the new root inline formatting context.
+ LayoutBlock* anonymous_block =
+ inner_span->GetLayoutObject()->ContainingBlock();
+ EXPECT_TRUE(anonymous_block->IsAnonymous());
+ const NGPaintFragment* block_fragment = anonymous_block->PaintFragment();
+ const NGPaintFragment* line_box_fragment = block_fragment->FirstChild();
+ EXPECT_EQ(line_box_fragment->FirstChild(), text_fragment_after_layout);
+}
+
+TEST_F(NGInlineNodeTest, AddChildToSVGRoot) {
+ SetBodyInnerHTML(R"HTML(
+ <div id="container">
+ text
+ <svg id="svg"></svg>
+ </div>
+ )HTML");
+
+ Element* svg = GetElementById("svg");
+ svg->appendChild(GetDocument().CreateRawElement(svg_names::kTextTag));
+ GetDocument().UpdateStyleAndLayoutTree();
+
+ LayoutObject* container = GetLayoutObjectByElementId("container");
+ EXPECT_FALSE(container->NeedsCollectInlines());
+}
+
// https://crbug.com/911220
TEST_F(NGInlineNodeTest, PreservedNewlineWithBidiAndRelayout) {
SetupHtml("container",
@@ -1241,6 +1420,44 @@ TEST_F(NGInlineNodeTest, PreservedNewlineWithBidiAndRelayout) {
EXPECT_EQ(String(u"foo\u2066\u2069\n\u2066\u2069bar\nbaz"), GetText());
}
+TEST_F(NGInlineNodeTest, PreservedNewlineWithRemovedBidiAndRelayout) {
+ SetupHtml("container",
+ "<pre id=container>foo<span dir=rtl>\nbar</span></pre>");
+ EXPECT_EQ(String(u"foo\u2067\u2069\n\u2067bar\u2069"), GetText());
+
+ GetDocument().QuerySelector("span")->removeAttribute(html_names::kDirAttr);
+ UpdateAllLifecyclePhasesForTest();
+
+ // The bidi control characters around '\n' should not preserve
+ EXPECT_EQ("foo\nbar", GetText());
+}
+
+TEST_F(NGInlineNodeTest, PreservedNewlineWithRemovedLtrDirAndRelayout) {
+ SetupHtml("container",
+ "<pre id=container>foo<span dir=ltr>\nbar</span></pre>");
+ EXPECT_EQ(String(u"foo\u2066\u2069\n\u2066bar\u2069"), GetText());
+
+ GetDocument().QuerySelector("span")->removeAttribute(html_names::kDirAttr);
+ UpdateAllLifecyclePhasesForTest();
+
+ // The bidi control characters around '\n' should not preserve
+ EXPECT_EQ("foo\nbar", GetText());
+}
+
+// https://crbug.com/969089
+TEST_F(NGInlineNodeTest, InsertedWBRWithLineBreakInRelayout) {
+ SetupHtml("container", "<div id=container><span>foo</span>\nbar</div>");
+ EXPECT_EQ("foo bar", GetText());
+
+ Element* div = GetElementById("container");
+ Element* wbr = GetDocument().CreateElementForBinding("wbr");
+ div->insertBefore(wbr, div->lastChild());
+ UpdateAllLifecyclePhasesForTest();
+
+ // The '\n' should be collapsed by the inserted <wbr>
+ EXPECT_EQ(String(u"foo\u200Bbar"), GetText());
+}
+
#if SEGMENT_BREAK_TRANSFORMATION_FOR_EAST_ASIAN_WIDTH
// https://crbug.com/879088
TEST_F(NGInlineNodeTest, RemoveSegmentBreakFromJapaneseInRelayout) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
index 8d66c3cc915..13d2b5d6e37 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
@@ -16,7 +16,6 @@ namespace blink {
void NGLineBoxFragmentBuilder::Reset() {
children_.Shrink(0);
- offsets_.Shrink(0);
child_break_tokens_.Shrink(0);
inline_break_tokens_.Shrink(0);
oof_positioned_candidates_.Shrink(0);
@@ -28,7 +27,7 @@ void NGLineBoxFragmentBuilder::Reset() {
has_last_resort_break_ = false;
has_floating_descendants_ = false;
has_orthogonal_flow_roots_ = false;
- has_child_that_depends_on_percentage_block_size_ = false;
+ has_descendant_that_depends_on_percentage_block_size_ = false;
has_block_fragmentation_ = false;
may_have_descendant_above_block_start_ = false;
}
@@ -57,6 +56,12 @@ NGLineBoxFragmentBuilder::ChildList::LastInFlowChild() {
}
void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection(
+ LayoutUnit delta) {
+ for (auto& child : children_)
+ child.offset.inline_offset += delta;
+}
+
+void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection(
LayoutUnit delta,
unsigned start,
unsigned end) {
@@ -78,13 +83,12 @@ void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection(LayoutUnit delta,
}
void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) {
- offsets_.ReserveCapacity(children.size());
children_.ReserveCapacity(children.size());
for (auto& child : children) {
if (child.layout_result) {
DCHECK(!child.fragment);
- AddChild(*child.layout_result, child.offset);
+ AddChild(child.layout_result->PhysicalFragment(), child.offset);
child.layout_result.reset();
} else if (child.fragment) {
AddChild(std::move(child.fragment), child.offset);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
index 573308b00b2..71c7396ed98 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h
@@ -5,13 +5,15 @@
#ifndef NGLineBoxFragmentBuilder_h
#define NGLineBoxFragmentBuilder_h
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_offset.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
@@ -19,11 +21,10 @@ namespace blink {
class ComputedStyle;
class NGInlineBreakToken;
-class NGPhysicalFragment;
class CORE_EXPORT NGLineBoxFragmentBuilder final
: public NGContainerFragmentBuilder {
- STACK_ALLOCATED();
+ DISALLOW_NEW();
public:
NGLineBoxFragmentBuilder(NGInlineNode node,
@@ -45,6 +46,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
return metrics_.LineHeight().ClampNegativeToZero();
}
+ void SetInlineSize(LayoutUnit inline_size) {
+ size_.inline_size = inline_size;
+ }
+
+ void SetHangInlineSize(LayoutUnit hang_inline_size) {
+ hang_inline_size_ = hang_inline_size;
+ }
+
// Mark this line box is an "empty" line box. See NGLineBoxType.
void SetIsEmptyLineBox();
@@ -67,12 +76,12 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
DISALLOW_NEW();
scoped_refptr<const NGLayoutResult> layout_result;
- scoped_refptr<const NGPhysicalFragment> fragment;
+ scoped_refptr<const NGPhysicalTextFragment> fragment;
LayoutObject* out_of_flow_positioned_box = nullptr;
LayoutObject* unpositioned_float = nullptr;
// The offset of the border box, initially in this child coordinate system.
// |ComputeInlinePositions()| converts it to the offset within the line box.
- NGLogicalOffset offset;
+ LogicalOffset offset;
// The offset of a positioned float wrt. the root BFC. This should only be
// set for positioned floats.
NGBfcOffset bfc_offset;
@@ -90,29 +99,29 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
Child() = default;
// Create a placeholder. A placeholder does not have a fragment nor a bidi
// level.
- Child(NGLogicalOffset offset) : offset(offset) {}
+ Child(LogicalOffset offset) : offset(offset) {}
// Crete a bidi control. A bidi control does not have a fragment, but has
// bidi level and affects bidi reordering.
Child(UBiDiLevel bidi_level) : bidi_level(bidi_level) {}
// Create an in-flow |NGLayoutResult|.
Child(scoped_refptr<const NGLayoutResult> layout_result,
- NGLogicalOffset offset,
+ LogicalOffset offset,
LayoutUnit inline_size,
UBiDiLevel bidi_level)
: layout_result(std::move(layout_result)),
offset(offset),
inline_size(inline_size),
bidi_level(bidi_level) {}
- // Create an in-flow |NGPhysicalFragment|.
- Child(scoped_refptr<const NGPhysicalFragment> fragment,
- NGLogicalOffset offset,
+ // Create an in-flow |NGPhysicalTextFragment|.
+ Child(scoped_refptr<const NGPhysicalTextFragment> fragment,
+ LogicalOffset offset,
LayoutUnit inline_size,
UBiDiLevel bidi_level)
: fragment(std::move(fragment)),
offset(offset),
inline_size(inline_size),
bidi_level(bidi_level) {}
- Child(scoped_refptr<const NGPhysicalFragment> fragment,
+ Child(scoped_refptr<const NGPhysicalTextFragment> fragment,
LayoutUnit block_offset,
LayoutUnit inline_size,
UBiDiLevel bidi_level)
@@ -142,7 +151,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
if (fragment)
return true;
- if (layout_result && !layout_result->PhysicalFragment()->IsFloating())
+ if (layout_result && !layout_result->PhysicalFragment().IsFloating())
return true;
return false;
@@ -154,7 +163,9 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
bool HasBidiLevel() const { return bidi_level != 0xff; }
bool IsPlaceholder() const { return !HasFragment() && !HasBidiLevel(); }
const NGPhysicalFragment* PhysicalFragment() const {
- return layout_result ? layout_result->PhysicalFragment() : fragment.get();
+ if (layout_result)
+ return &layout_result->PhysicalFragment();
+ return fragment.get();
}
};
@@ -162,7 +173,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
// Unlike the fragment builder, chlidren are mutable.
// Callers can add to the fragment builder in a batch once finalized.
class ChildList {
- STACK_ALLOCATED();
+ DISALLOW_NEW();
public:
ChildList() = default;
@@ -204,13 +215,14 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
}
void InsertChild(unsigned index,
scoped_refptr<const NGLayoutResult> layout_result,
- const NGLogicalOffset& offset,
+ const LogicalOffset& offset,
LayoutUnit inline_size,
UBiDiLevel bidi_level) {
children_.insert(index, Child{std::move(layout_result), offset,
inline_size, bidi_level});
}
+ void MoveInInlineDirection(LayoutUnit);
void MoveInInlineDirection(LayoutUnit, unsigned start, unsigned end);
void MoveInBlockDirection(LayoutUnit);
void MoveInBlockDirection(LayoutUnit, unsigned start, unsigned end);
@@ -227,6 +239,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
private:
NGLineHeightMetrics metrics_;
+ LayoutUnit hang_inline_size_;
NGPhysicalLineBoxFragment::NGLineBoxType line_box_type_;
TextDirection base_direction_;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index d36e73f5419..8f60a66ffb1 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -157,6 +157,14 @@ inline void ClearNeedsLayout(const NGInlineItem& item) {
} // namespace
+LayoutUnit NGLineBreaker::ComputeAvailableWidth() const {
+ LayoutUnit available_width = line_opportunity_.AvailableInlineSize();
+ // Available width must be smaller than |LayoutUnit::Max()| so that the
+ // position can be larger.
+ available_width = std::min(available_width, LayoutUnit::NearlyMax());
+ return available_width;
+}
+
NGLineBreaker::NGLineBreaker(NGInlineNode node,
NGLineBreakerMode mode,
const NGConstraintSpace& space,
@@ -184,6 +192,7 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node,
leading_floats_(leading_floats),
handled_leading_floats_index_(handled_leading_floats_index),
base_direction_(node_.BaseDirection()) {
+ available_width_ = ComputeAvailableWidth();
break_iterator_.SetBreakSpace(BreakSpaceType::kBeforeSpaceRun);
if (break_token) {
@@ -235,20 +244,23 @@ void NGLineBreaker::SetMaxSizeCache(MaxSizeCache* max_size_cache) {
max_size_cache_ = max_size_cache;
}
-void NGLineBreaker::SetLineEndFragment(
+LayoutUnit NGLineBreaker::SetLineEndFragment(
scoped_refptr<const NGPhysicalTextFragment> fragment,
NGLineInfo* line_info) {
+ LayoutUnit inline_size;
bool is_horizontal =
IsHorizontalWritingMode(constraint_space_.GetWritingMode());
if (line_info->LineEndFragment()) {
- const NGPhysicalSize& size = line_info->LineEndFragment()->Size();
- position_ -= is_horizontal ? size.width : size.height;
+ const PhysicalSize& size = line_info->LineEndFragment()->Size();
+ inline_size = is_horizontal ? -size.width : -size.height;
}
if (fragment) {
- const NGPhysicalSize& size = fragment->Size();
- position_ += is_horizontal ? size.width : size.height;
+ const PhysicalSize& size = fragment->Size();
+ inline_size = is_horizontal ? size.width : size.height;
}
line_info->SetLineEndFragment(std::move(fragment));
+ position_ += inline_size;
+ return inline_size;
}
// Compute the base direction for bidi algorithm for this line.
@@ -273,8 +285,8 @@ void NGLineBreaker::ComputeBaseDirection() {
void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) {
// NGLineInfo is not supposed to be re-used becase it's not much gain and to
// avoid rare code path.
- NGInlineItemResults* item_results = line_info->MutableResults();
- DCHECK(item_results->IsEmpty());
+ const NGInlineItemResults& item_results = line_info->Results();
+ DCHECK(item_results.IsEmpty());
if (item_index_) {
// We're past the first line
@@ -310,6 +322,8 @@ void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) {
// Use 'text-indent' as the initial position. This lets tab positions to align
// regardless of 'text-indent'.
position_ = line_info->TextIndent();
+
+ overflow_item_index_ = 0;
}
void NGLineBreaker::NextLine(
@@ -326,9 +340,9 @@ void NGLineBreaker::NextLine(
out_floats_for_min_max, line_info);
RemoveTrailingCollapsibleSpace(line_info);
- NGInlineItemResults* item_results = line_info->MutableResults();
+ const NGInlineItemResults& item_results = line_info->Results();
#if DCHECK_IS_ON()
- for (const auto& result : *item_results)
+ for (const auto& result : item_results)
result.CheckConsistency(mode_ == NGLineBreakerMode::kMinContent);
#endif
@@ -340,7 +354,7 @@ void NGLineBreaker::NextLine(
//
// TODO(kojii): There are cases where we need to PlaceItems() without creating
// line boxes. These cases need to be reviewed.
- bool should_create_line_box = ShouldCreateLineBox(*item_results) ||
+ bool should_create_line_box = ShouldCreateLineBox(item_results) ||
(has_list_marker_ && line_info->IsLastLine()) ||
mode_ != NGLineBreakerMode::kContent;
@@ -377,7 +391,7 @@ void NGLineBreaker::BreakLine(
return;
}
- NGInlineItemResults* item_results = line_info->MutableResults();
+ const NGInlineItemResults& item_results = line_info->Results();
// Handle trailable items first. These items may not be break before.
// They (or part of them) may also overhang the available width.
@@ -388,11 +402,16 @@ void NGLineBreaker::BreakLine(
else
HandleEmptyText(item, line_info);
#if DCHECK_IS_ON()
- if (!item_results->IsEmpty())
- item_results->back().CheckConsistency(true);
+ if (!item_results.IsEmpty())
+ item_results.back().CheckConsistency(true);
#endif
continue;
}
+ if (item.Type() == NGInlineItem::kOpenTag) {
+ if (HandleOpenTag(item, line_info))
+ continue;
+ return;
+ }
if (item.Type() == NGInlineItem::kCloseTag) {
HandleCloseTag(item, line_info);
continue;
@@ -413,12 +432,12 @@ void NGLineBreaker::BreakLine(
// Items after this point are not trailable. Break at the earliest break
// opportunity if we're trailing.
if (state_ == LineBreakState::kTrailing &&
- CanBreakAfterLast(*item_results)) {
+ CanBreakAfterLast(item_results)) {
// If the sticky images quirk is enabled, and this is an image that
// follows text that doesn't end with something breakable, we cannot break
// between the two items.
if (sticky_images_quirk_ &&
- IsStickyImage(item, *item_results, trailing_whitespace_, Text())) {
+ IsStickyImage(item, item_results, trailing_whitespace_, Text())) {
HandleAtomicInline(item, percentage_resolution_block_size_for_min_max,
line_info);
continue;
@@ -431,8 +450,6 @@ void NGLineBreaker::BreakLine(
if (item.Type() == NGInlineItem::kAtomicInline) {
HandleAtomicInline(item, percentage_resolution_block_size_for_min_max,
line_info);
- } else if (item.Type() == NGInlineItem::kOpenTag) {
- HandleOpenTag(item, line_info);
} else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) {
AddItem(item, line_info);
MoveToNextOf(item);
@@ -465,6 +482,8 @@ void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
line_info->SetWidth(available_width, position_);
line_info->SetBfcOffset(
{line_opportunity_.line_left_offset, line_opportunity_.bfc_block_offset});
+ if (mode_ == NGLineBreakerMode::kContent)
+ line_info->UpdateTextAlign();
}
void NGLineBreaker::HandleText(const NGInlineItem& item,
@@ -477,7 +496,7 @@ void NGLineBreaker::HandleText(const NGInlineItem& item,
// If we're trailing, only trailing spaces can be included in this line.
if (state_ == LineBreakState::kTrailing) {
- if (CanBreakAfterLast(*line_info->MutableResults()))
+ if (CanBreakAfterLast(line_info->Results()))
return HandleTrailingSpaces(item, shape_result, line_info);
// When a run of preserved spaces are across items, |CanBreakAfterLast| is
// false for between spaces. But we still need to handle them as trailing
@@ -519,17 +538,18 @@ void NGLineBreaker::HandleText(const NGInlineItem& item,
// Try to break inside of this text item.
LayoutUnit available_width = AvailableWidthToFit();
- BreakText(item_result, item, shape_result, available_width - position_,
- line_info);
-
- LayoutUnit next_position = position_ + item_result->inline_size;
- bool is_overflow = next_position > available_width;
- DCHECK(is_overflow || item_result->shape_result);
- position_ = next_position;
- item_result->may_break_inside = !is_overflow;
+ BreakResult break_result =
+ BreakText(item_result, item, shape_result, available_width - position_,
+ line_info);
+ DCHECK(item_result->shape_result ||
+ (break_result == kOverflow && break_anywhere_if_overflow_ &&
+ !override_break_anywhere_));
+ position_ += item_result->inline_size;
+ DCHECK_EQ(break_result == kSuccess, position_ <= available_width);
+ item_result->may_break_inside = break_result == kSuccess;
MoveToNextOf(*item_result);
- if (!is_overflow ||
+ if (break_result == kSuccess ||
(state_ == LineBreakState::kTrailing && item_result->shape_result)) {
if (item_result->end_offset < item.EndOffset()) {
// The break point found, and text follows. Break here, after trailing
@@ -542,15 +562,27 @@ void NGLineBreaker::HandleText(const NGInlineItem& item,
return;
}
+ DCHECK_EQ(break_result, kOverflow);
return HandleOverflow(line_info);
}
- // Add the whole item if !auto_wrap. The previous line should not have wrapped
- // in the middle of nowrap item.
- DCHECK_EQ(item_result->start_offset, item.StartOffset());
+ // Add until the end of the item if !auto_wrap. In most cases, it's the whole
+ // item.
DCHECK_EQ(item_result->end_offset, item.EndOffset());
- item_result->inline_size = shape_result.SnappedWidth().ClampNegativeToZero();
- item_result->shape_result = ShapeResultView::Create(&shape_result);
+ if (item_result->start_offset == item.StartOffset()) {
+ item_result->inline_size =
+ shape_result.SnappedWidth().ClampNegativeToZero();
+ item_result->shape_result = ShapeResultView::Create(&shape_result);
+ } else {
+ // <wbr> can wrap even if !auto_wrap. Spaces after that will be leading
+ // spaces and thus be collapsed.
+ DCHECK(trailing_whitespace_ == WhitespaceState::kLeading &&
+ item_result->start_offset >= item.StartOffset());
+ item_result->shape_result = ShapeResultView::Create(
+ &shape_result, item_result->start_offset, item_result->end_offset);
+ item_result->inline_size =
+ item_result->shape_result->SnappedWidth().ClampNegativeToZero();
+ }
DCHECK(!item_result->may_break_inside);
DCHECK(!item_result->can_break_after);
@@ -559,21 +591,18 @@ void NGLineBreaker::HandleText(const NGInlineItem& item,
MoveToNextOf(item);
}
-void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
- const NGInlineItem& item,
- const ShapeResult& item_shape_result,
- LayoutUnit available_width,
- NGLineInfo* line_info) {
+NGLineBreaker::BreakResult NGLineBreaker::BreakText(
+ NGInlineItemResult* item_result,
+ const NGInlineItem& item,
+ const ShapeResult& item_shape_result,
+ LayoutUnit available_width,
+ NGLineInfo* line_info) {
DCHECK(item.Type() == NGInlineItem::kText ||
(item.Type() == NGInlineItem::kControl &&
Text()[item.StartOffset()] == kTabulationCharacter));
DCHECK(&item_shape_result);
item.AssertOffset(item_result->start_offset);
- // TODO(kojii): We need to instantiate ShapingLineBreaker here because it
- // has item-specific info as context. Should they be part of ShapeLine() to
- // instantiate once, or is this just fine since instatiation is not
- // expensive?
DCHECK_EQ(item_shape_result.StartIndex(), item.StartOffset());
DCHECK_EQ(item_shape_result.EndIndex(), item.EndOffset());
struct ShapeCallbackContext {
@@ -593,7 +622,6 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
shape_callback, &shape_callback_context);
if (!enable_soft_hyphen_)
breaker.DisableSoftHyphen();
- available_width = std::max(LayoutUnit(0), available_width);
// Use kStartShouldBeSafe if at the beginning of a line.
unsigned options = ShapingLineBreaker::kDefaultOptions;
@@ -612,36 +640,58 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
if (break_anywhere_if_overflow_ && !override_break_anywhere_)
options |= ShapingLineBreaker::kNoResultIfOverflow;
- ShapingLineBreaker::Result result;
- scoped_refptr<const ShapeResultView> shape_result = breaker.ShapeLine(
- item_result->start_offset, available_width, options, &result);
-
- // If this item overflows and 'break-word' is set, this line will be
- // rewinded. Making this item long enough to overflow is enough.
- if (!shape_result) {
- DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow);
- item_result->inline_size = available_width + 1;
- item_result->end_offset = item.EndOffset();
- return;
- }
- DCHECK_EQ(shape_result->NumCharacters(),
- result.break_offset - item_result->start_offset);
-
- if (result.is_hyphenated) {
- SetLineEndFragment(
- CreateHyphenFragment(node_, constraint_space_.GetWritingMode(), item),
- line_info);
-
- // TODO(kojii): Implement when adding a hyphen caused overflow.
- // crbug.com/714962: Should be removed when switched to NGPaint.
- item_result->text_end_effect = NGTextEndEffect::kHyphen;
+#if DCHECK_IS_ON()
+ unsigned try_count = 0;
+#endif
+ LayoutUnit inline_size;
+ while (true) {
+#if DCHECK_IS_ON()
+ ++try_count;
+ DCHECK_LE(try_count, 2u);
+#endif
+ ShapingLineBreaker::Result result;
+ scoped_refptr<const ShapeResultView> shape_result = breaker.ShapeLine(
+ item_result->start_offset, available_width.ClampNegativeToZero(),
+ options, &result);
+
+ // If this item overflows and 'break-word' is set, this line will be
+ // rewinded. Making this item long enough to overflow is enough.
+ if (!shape_result) {
+ DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow);
+ item_result->inline_size = available_width + 1;
+ item_result->end_offset = item.EndOffset();
+ return kOverflow;
+ }
+ DCHECK_EQ(shape_result->NumCharacters(),
+ result.break_offset - item_result->start_offset);
+ // It is critical to move the offset forward, or NGLineBreaker may keep
+ // adding NGInlineItemResult until all the memory is consumed.
+ CHECK_GT(result.break_offset, item_result->start_offset);
+
+ inline_size = shape_result->SnappedWidth().ClampNegativeToZero();
+ item_result->inline_size = inline_size;
+ if (UNLIKELY(result.is_hyphenated)) {
+ const WritingMode writing_mode = constraint_space_.GetWritingMode();
+ scoped_refptr<const NGPhysicalTextFragment> hyphen_fragment =
+ CreateHyphenFragment(node_, writing_mode, item);
+ LayoutUnit space_for_hyphen = available_width - inline_size;
+ LayoutUnit hyphen_inline_size = IsHorizontalWritingMode(writing_mode)
+ ? hyphen_fragment->Size().width
+ : hyphen_fragment->Size().height;
+ // If the hyphen overflows, retry with the reduced available width.
+ if (space_for_hyphen >= 0 && hyphen_inline_size > space_for_hyphen) {
+ available_width -= hyphen_inline_size;
+ continue;
+ }
+ inline_size += SetLineEndFragment(std::move(hyphen_fragment), line_info);
+ item_result->text_end_effect = NGTextEndEffect::kHyphen;
+ }
+ item_result->inline_size =
+ shape_result->SnappedWidth().ClampNegativeToZero();
+ item_result->end_offset = result.break_offset;
+ item_result->shape_result = std::move(shape_result);
+ break;
}
- item_result->inline_size = shape_result->SnappedWidth().ClampNegativeToZero();
- item_result->end_offset = result.break_offset;
- item_result->shape_result = std::move(shape_result);
- // It is critical to move offset forward, or NGLineBreaker may keep adding
- // NGInlineItemResult until all the memory is consumed.
- CHECK_GT(item_result->end_offset, item_result->start_offset) << Text();
// * If width <= available_width:
// * If offset < item.EndOffset(): the break opportunity to fit is found.
@@ -666,6 +716,7 @@ void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
break_iterator_.IsBreakable(item_result->end_offset);
trailing_whitespace_ = WhitespaceState::kUnknown;
}
+ return inline_size <= available_width ? kSuccess : kOverflow;
}
// This function handles text item for min-content. The specialized logic is
@@ -1121,10 +1172,9 @@ void NGLineBreaker::HandleAtomicInline(
.LayoutAtomicInline(constraint_space_, node_.Style(),
line_info->LineStyle().GetFontBaseline(),
line_info->UseFirstLineStyle());
- DCHECK(item_result->layout_result->PhysicalFragment());
item_result->inline_size =
NGFragment(constraint_space_.GetWritingMode(),
- *item_result->layout_result->PhysicalFragment())
+ item_result->layout_result->PhysicalFragment())
.InlineSize();
item_result->margins =
ComputeLineMarginsForVisualContainer(constraint_space_, style);
@@ -1199,7 +1249,7 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
// fragmentainer break. In that case the floats associated with this line will
// already have been processed.
NGInlineItemResult* item_result = AddItem(item, line_info);
- ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
+ item_result->can_break_after = auto_wrap_;
MoveToNextOf(item);
// If we are currently computing our min/max-content size simply append to
@@ -1261,7 +1311,7 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
// Check if we already have a pending float. That's because a float cannot be
// higher than any block or floated box generated before.
- if (HasUnpositionedFloats(*line_info->MutableResults()) || float_after_line) {
+ if (HasUnpositionedFloats(line_info->Results()) || float_after_line) {
item_result->has_unpositioned_floats = true;
} else {
NGPositionedFloat positioned_float = PositionFloat(
@@ -1276,12 +1326,13 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
NGLayoutOpportunity opportunity = exclusion_space_->FindLayoutOpportunity(
{constraint_space_.BfcOffset().line_offset, bfc_block_offset},
- constraint_space_.AvailableSize().inline_size, NGLogicalSize());
+ constraint_space_.AvailableSize().inline_size, LogicalSize());
DCHECK_EQ(bfc_block_offset, opportunity.rect.BlockStartOffset());
line_opportunity_ = opportunity.ComputeLineLayoutOpportunity(
constraint_space_, line_opportunity_.line_block_size, LayoutUnit());
+ available_width_ = ComputeAvailableWidth();
DCHECK_GE(AvailableWidth(), LayoutUnit());
}
@@ -1290,7 +1341,8 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item,
bool NGLineBreaker::ComputeOpenTagResult(
const NGInlineItem& item,
const NGConstraintSpace& constraint_space,
- NGInlineItemResult* item_result) {
+ NGInlineItemResult* item_result,
+ base::Optional<NGLineBoxStrut> margins) {
DCHECK_EQ(item.Type(), NGInlineItem::kOpenTag);
DCHECK(item.Style());
const ComputedStyle& style = *item.Style();
@@ -1301,7 +1353,9 @@ bool NGLineBreaker::ComputeOpenTagResult(
item_result->borders = ComputeLineBorders(style);
item_result->padding = ComputeLinePadding(constraint_space, style);
if (item_result->has_edge) {
- item_result->margins = ComputeLineMarginsForSelf(constraint_space, style);
+ item_result->margins =
+ margins ? *margins
+ : ComputeLineMarginsForSelf(constraint_space, style);
item_result->inline_size = item_result->margins.inline_start +
item_result->borders.inline_start +
item_result->padding.inline_start;
@@ -1311,11 +1365,39 @@ bool NGLineBreaker::ComputeOpenTagResult(
return false;
}
-void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
+bool NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
NGLineInfo* line_info) {
+ DCHECK_EQ(item.Type(), NGInlineItem::kOpenTag);
+ DCHECK(item.Style());
+ const ComputedStyle& style = *item.Style();
+
+ // OpenTag is not trailable, except when it has negative inline-start margin,
+ // which can bring the position back to inside of the available width.
+ base::Optional<NGLineBoxStrut> margins;
+ if (UNLIKELY(state_ == LineBreakState::kTrailing &&
+ CanBreakAfterLast(line_info->Results()))) {
+ bool can_continue = false;
+ if (UNLIKELY(item_index_ >= overflow_item_index_ &&
+ item.ShouldCreateBoxFragment() && item.HasStartEdge() &&
+ style.MayHaveMargin())) {
+ margins = ComputeLineMarginsForSelf(constraint_space_, style);
+ LayoutUnit inline_start_margin = margins->inline_start;
+ can_continue = inline_start_margin < 0 &&
+ position_ + inline_start_margin < AvailableWidthToFit();
+ }
+ if (!can_continue) {
+ // Not that case. Break the line before this OpenTag.
+ line_info->SetIsLastLine(false);
+ return false;
+ }
+ // The state is back to normal because the position is back to inside of the
+ // available width.
+ state_ = LineBreakState::kContinue;
+ }
+
NGInlineItemResult* item_result = AddItem(item, line_info);
- if (ComputeOpenTagResult(item, constraint_space_, item_result)) {
+ if (ComputeOpenTagResult(item, constraint_space_, item_result, margins)) {
position_ += item_result->inline_size;
// While the spec defines "non-zero margins, padding, or borders" prevents
@@ -1327,16 +1409,15 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
}
bool was_auto_wrap = auto_wrap_;
- DCHECK(item.Style());
- const ComputedStyle& style = *item.Style();
SetCurrentStyle(style);
MoveToNextOf(item);
DCHECK(!item_result->can_break_after);
- NGInlineItemResults* item_results = line_info->MutableResults();
- if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results->size() >= 2)) {
+ const NGInlineItemResults& item_results = line_info->Results();
+ if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results.size() >= 2)) {
ComputeCanBreakAfter(std::prev(item_result), auto_wrap_, break_iterator_);
}
+ return true;
}
void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
@@ -1360,8 +1441,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
// If the line can break after the previous item, prohibit it and allow break
// after this close tag instead.
if (was_auto_wrap) {
- NGInlineItemResults* item_results = line_info->MutableResults();
- if (item_results->size() >= 2) {
+ const NGInlineItemResults& item_results = line_info->Results();
+ if (item_results.size() >= 2) {
NGInlineItemResult* last = std::prev(item_result);
item_result->can_break_after = last->can_break_after;
last->can_break_after = false;
@@ -1390,6 +1471,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
// At this point, item_results does not fit into the current line, and there
// are no break opportunities in item_results.back().
void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) {
+ overflow_item_index_ = std::max(overflow_item_index_, item_index_);
+
// Compute the width needing to rewind. When |width_to_rewind| goes negative,
// items can fit within the line.
LayoutUnit available_width = AvailableWidthToFit();
@@ -1435,14 +1518,18 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) {
LayoutUnit item_available_width =
std::min(-width_to_rewind, item_result->inline_size - 1);
SetCurrentStyle(*item.Style());
- BreakText(item_result, item, *item.TextShapeResult(),
- item_available_width, line_info);
+ BreakResult break_result =
+ BreakText(item_result, item, *item.TextShapeResult(),
+ item_available_width, line_info);
#if DCHECK_IS_ON()
item_result->CheckConsistency(true);
#endif
// If BreakText() changed this item small enough to fit, break here.
- if (item_result->inline_size <= item_available_width) {
- DCHECK(item_result->end_offset < item.EndOffset());
+ DCHECK_EQ(break_result == kSuccess,
+ item_result->inline_size <= item_available_width);
+ if (break_result == kSuccess) {
+ DCHECK_LE(item_result->inline_size, item_available_width);
+ DCHECK_LT(item_result->end_offset, item.EndOffset());
DCHECK(item_result->can_break_after);
DCHECK_LE(i + 1, item_results->size());
if (i + 1 == item_results->size()) {
@@ -1478,6 +1565,7 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) {
if (!item_results->IsEmpty())
Rewind(0, line_info);
state_ = LineBreakState::kContinue;
+ overflow_item_index_ = 0;
return;
}
@@ -1508,6 +1596,8 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) {
// most cases where our support for rewinding positioned floats is not great
// yet (see below.)
while (item_results[new_end].item->Type() == NGInlineItem::kFloating) {
+ // We assume floats can break after, or this may cause an infinite loop.
+ DCHECK(item_results[new_end].can_break_after);
++new_end;
if (new_end == item_results.size()) {
position_ = line_info->ComputeWidth();
@@ -1520,6 +1610,8 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) {
for (unsigned i = item_results.size(); i > new_end;) {
NGInlineItemResult& rewind = item_results[--i];
if (rewind.positioned_float) {
+ // We assume floats can break after, or this may cause an infinite loop.
+ DCHECK(rewind.can_break_after);
// TODO(kojii): We do not have mechanism to remove once positioned floats
// yet, and that rewinding them may lay it out twice. For now, prohibit
// rewinding positioned floats. This may results in incorrect layout, but
@@ -1560,7 +1652,7 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) {
const ComputedStyle& NGLineBreaker::ComputeCurrentStyle(
unsigned item_result_index,
NGLineInfo* line_info) const {
- NGInlineItemResults& item_results = *line_info->MutableResults();
+ const NGInlineItemResults& item_results = line_info->Results();
// Use the current item if it can compute the current style.
const NGInlineItem* item = item_results[item_result_index].item;
@@ -1616,8 +1708,10 @@ void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
line_break_type = LineBreakType::kKeepAll;
break;
}
- if (UNLIKELY(override_break_anywhere_ && break_anywhere_if_overflow_))
+ if (UNLIKELY((override_break_anywhere_ && break_anywhere_if_overflow_) ||
+ style.GetLineBreak() == LineBreak::kAnywhere)) {
line_break_type = LineBreakType::kBreakCharacter;
+ }
break_iterator_.SetBreakType(line_break_type);
enable_soft_hyphen_ = style.GetHyphens() != Hyphens::kNone;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
index 1e9baf8885b..17c3d0545a0 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
@@ -47,7 +47,7 @@ class CORE_EXPORT NGLineBreaker {
// Compute the next line break point and produces NGInlineItemResults for
// the line.
inline void NextLine(NGLineInfo* line_info) {
- NextLine(NGSizeIndefinite, nullptr, line_info);
+ NextLine(kIndefiniteSize, nullptr, line_info);
}
// During the min/max size calculation we need a special percentage
@@ -72,9 +72,11 @@ class CORE_EXPORT NGLineBreaker {
// Compute NGInlineItemResult for an open tag item.
// Returns true if this item has edge and may have non-zero inline size.
- static bool ComputeOpenTagResult(const NGInlineItem&,
- const NGConstraintSpace&,
- NGInlineItemResult*);
+ static bool ComputeOpenTagResult(
+ const NGInlineItem&,
+ const NGConstraintSpace&,
+ NGInlineItemResult*,
+ base::Optional<NGLineBoxStrut> margins = base::nullopt);
// This enum is private, except for |WhitespaceStateForTesting()|. See
// |whitespace_| member.
@@ -98,8 +100,8 @@ class CORE_EXPORT NGLineBreaker {
unsigned end_offset,
NGLineInfo*);
NGInlineItemResult* AddItem(const NGInlineItem&, NGLineInfo*);
- void SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>,
- NGLineInfo*);
+ LayoutUnit SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>,
+ NGLineInfo*);
void BreakLine(LayoutUnit percentage_resolution_block_size_for_min_max,
Vector<LayoutObject*>* out_floats_for_min_max,
@@ -126,11 +128,12 @@ class CORE_EXPORT NGLineBreaker {
HandleText(item, *item.TextShapeResult(), line_info);
}
void HandleText(const NGInlineItem& item, const ShapeResult&, NGLineInfo*);
- void BreakText(NGInlineItemResult*,
- const NGInlineItem&,
- const ShapeResult&,
- LayoutUnit available_width,
- NGLineInfo*);
+ enum BreakResult { kSuccess, kOverflow };
+ BreakResult BreakText(NGInlineItemResult*,
+ const NGInlineItem&,
+ const ShapeResult&,
+ LayoutUnit available_width,
+ NGLineInfo*);
bool HandleTextForFastMinContent(NGInlineItemResult*,
const NGInlineItem&,
const ShapeResult&,
@@ -163,7 +166,7 @@ class CORE_EXPORT NGLineBreaker {
Vector<LayoutObject*>* out_floats_for_min_max,
NGLineInfo*);
- void HandleOpenTag(const NGInlineItem&, NGLineInfo*);
+ bool HandleOpenTag(const NGInlineItem&, NGLineInfo*);
void HandleCloseTag(const NGInlineItem&, NGLineInfo*);
void HandleOverflow(NGLineInfo*);
@@ -179,11 +182,13 @@ class CORE_EXPORT NGLineBreaker {
void ComputeBaseDirection();
LayoutUnit AvailableWidth() const {
- return line_opportunity_.AvailableInlineSize();
+ DCHECK_EQ(available_width_, ComputeAvailableWidth());
+ return available_width_;
}
LayoutUnit AvailableWidthToFit() const {
return AvailableWidth().AddEpsilon();
}
+ LayoutUnit ComputeAvailableWidth() const;
// Represents the current offset of the input.
LineBreakState state_;
@@ -197,6 +202,7 @@ class CORE_EXPORT NGLineBreaker {
// The current position from inline_start. Unlike NGInlineLayoutAlgorithm
// that computes position in visual order, this position in logical order.
LayoutUnit position_;
+ LayoutUnit available_width_;
NGLineLayoutOpportunity line_opportunity_;
NGInlineNode node_;
@@ -261,6 +267,9 @@ class CORE_EXPORT NGLineBreaker {
};
base::Optional<TrailingCollapsibleSpace> trailing_collapsible_space_;
+ // Keep track of item index where overflow occurrred.
+ unsigned overflow_item_index_;
+
// Keep track of handled float items. See HandleFloat().
const NGPositionedFloatVector& leading_floats_;
unsigned leading_floats_index_ = 0u;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc
index 861fd965e43..778bb892eec 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc
@@ -35,10 +35,10 @@ class NGLineBreakerTest : public NGLayoutTest {
node.PrepareLayoutIfNeeded();
NGConstraintSpace space =
- NGConstraintSpaceBuilder(
- WritingMode::kHorizontalTb, WritingMode::kHorizontalTb,
- /* is_new_fc */ false)
- .SetAvailableSize({available_width, NGSizeIndefinite})
+ NGConstraintSpaceBuilder(WritingMode::kHorizontalTb,
+ WritingMode::kHorizontalTb,
+ /* is_new_fc */ false)
+ .SetAvailableSize({available_width, kIndefiniteSize})
.ToConstraintSpace();
scoped_refptr<NGInlineBreakToken> break_token;
@@ -494,8 +494,13 @@ TEST_P(NGTrailingSpaceWidthTest, TrailingSpaceWidth) {
)HTML");
Vector<NGLineInfo> line_infos = BreakToLineInfo(node, LayoutUnit(50));
- EXPECT_EQ(line_infos[0].ComputeTrailingSpaceWidth(),
- LayoutUnit(10) * data.trailing_space_width);
+ const NGLineInfo& line_info = line_infos[0];
+ if (line_info.ShouldHangTrailingSpaces()) {
+ EXPECT_EQ(line_info.HangWidth(),
+ LayoutUnit(10) * data.trailing_space_width);
+ } else {
+ EXPECT_EQ(line_info.HangWidth(), LayoutUnit());
+ }
}
TEST_F(NGLineBreakerTest, MinMaxWithTrailingSpaces) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
index 97a947e04a5..747f3f13a07 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
@@ -13,29 +13,6 @@
namespace blink {
-namespace {
-
-// Create the style to use for the ellipsis characters.
-//
-// The ellipsis is styled according to the line style.
-// https://drafts.csswg.org/css-ui/#ellipsing-details
-scoped_refptr<const ComputedStyle> CreateEllipsisStyle(
- scoped_refptr<const ComputedStyle> line_style) {
- if (line_style->TextDecorationsInEffect() == TextDecoration::kNone)
- return line_style;
-
- // Ellipsis should not have text decorations. Reset if it's set.
- // This is not defined, but 4 impls do this.
- scoped_refptr<ComputedStyle> ellipsis_style =
- ComputedStyle::CreateAnonymousStyleWithDisplay(*line_style,
- EDisplay::kInline);
- ellipsis_style->ResetTextDecoration();
- ellipsis_style->ClearAppliedTextDecorations();
- return ellipsis_style;
-}
-
-} // namespace
-
NGLineTruncator::NGLineTruncator(NGInlineNode& node,
const NGLineInfo& line_info)
: node_(node),
@@ -47,8 +24,9 @@ LayoutUnit NGLineTruncator::TruncateLine(
LayoutUnit line_width,
NGLineBoxFragmentBuilder::ChildList* line_box) {
// Shape the ellipsis and compute its inline size.
- scoped_refptr<const ComputedStyle> ellipsis_style =
- CreateEllipsisStyle(line_style_);
+ // The ellipsis is styled according to the line style.
+ // https://drafts.csswg.org/css-ui/#ellipsing-details
+ const ComputedStyle* ellipsis_style = line_style_.get();
const Font& font = ellipsis_style->GetFont();
const SimpleFontData* font_data = font.PrimaryFont();
DCHECK(font_data);
@@ -101,7 +79,7 @@ LayoutUnit NGLineTruncator::TruncateLine(
NGTextFragmentBuilder builder(node_, line_style_->GetWritingMode());
DCHECK(ellipsized_fragment->GetLayoutObject() &&
ellipsized_fragment->GetLayoutObject()->IsInline());
- builder.SetText(ellipsized_fragment->GetLayoutObject(), ellipsis_text,
+ builder.SetText(ellipsized_fragment->GetMutableLayoutObject(), ellipsis_text,
ellipsis_style, true /* is_ellipsis_style */,
std::move(ellipsis_shape_result));
FontBaseline baseline_type = line_style_->GetFontBaseline();
@@ -109,7 +87,7 @@ LayoutUnit NGLineTruncator::TruncateLine(
baseline_type);
line_box->AddChild(
builder.ToTextFragment(),
- NGLogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent},
+ LogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent},
ellipsis_width, 0);
return std::max(ellipsis_inline_offset + ellipsis_width, line_width);
}
@@ -121,10 +99,9 @@ void NGLineTruncator::HideChild(NGLineBoxFragmentBuilder::Child* child) {
const NGPhysicalFragment* fragment = nullptr;
if (const NGLayoutResult* layout_result = child->layout_result.get()) {
// Need to propagate OOF descendants in this inline-block child.
- if (!layout_result->OutOfFlowPositionedDescendants().IsEmpty()) {
+ if (layout_result->PhysicalFragment().HasOutOfFlowPositionedDescendants())
return;
- }
- fragment = layout_result->PhysicalFragment();
+ fragment = &layout_result->PhysicalFragment();
} else {
fragment = child->fragment.get();
}
@@ -211,6 +188,11 @@ bool NGLineTruncator::TruncateChild(LayoutUnit space_for_child,
if (!child->fragment)
return is_first_child;
auto& fragment = To<NGPhysicalTextFragment>(*child->fragment);
+
+ // No need to truncate empty results.
+ if (!fragment.TextShapeResult())
+ return is_first_child;
+
// TODO(layout-dev): Add support for OffsetToFit to ShapeResultView to avoid
// this copy.
scoped_refptr<blink::ShapeResult> shape_result =
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
index fab78c10ead..1e7fb8e2a92 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
@@ -12,7 +12,6 @@
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/position.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
#include "third_party/blink/renderer/platform/text/character.h"
@@ -27,16 +26,16 @@ bool CanUseNGOffsetMapping(const LayoutObject& object) {
}
Position CreatePositionForOffsetMapping(const Node& node, unsigned dom_offset) {
- if (node.IsTextNode()) {
+ if (auto* text_node = DynamicTo<Text>(node)) {
// 'text-transform' may make the rendered text length longer than the
// original text node, in which case we clamp the offset to avoid crashing.
// TODO(crbug.com/750990): Support 'text-transform' to remove this hack.
#if DCHECK_IS_ON()
// Ensures that the clamping hack kicks in only with text-transform.
if (node.ComputedStyleRef().TextTransform() == ETextTransform::kNone)
- DCHECK_LE(dom_offset, ToText(node).length());
+ DCHECK_LE(dom_offset, text_node->length());
#endif
- const unsigned clamped_offset = std::min(dom_offset, ToText(node).length());
+ const unsigned clamped_offset = std::min(dom_offset, text_node->length());
return Position(&node, clamped_offset);
}
// For non-text-anchored position, the offset must be either 0 or 1.
@@ -46,13 +45,13 @@ Position CreatePositionForOffsetMapping(const Node& node, unsigned dom_offset) {
std::pair<const Node&, unsigned> ToNodeOffsetPair(const Position& position) {
DCHECK(NGOffsetMapping::AcceptsPosition(position)) << position;
- if (position.AnchorNode()->IsTextNode()) {
+ if (auto* text_node = DynamicTo<Text>(position.AnchorNode())) {
if (position.IsOffsetInAnchor())
return {*position.AnchorNode(), position.OffsetInContainerNode()};
if (position.IsBeforeAnchor())
return {*position.AnchorNode(), 0};
DCHECK(position.IsAfterAnchor());
- return {*position.AnchorNode(), ToText(position.AnchorNode())->length()};
+ return {*position.AnchorNode(), text_node->length()};
}
if (position.IsBeforeAnchor())
return {*position.AnchorNode(), 0};
@@ -99,7 +98,29 @@ NGOffsetMappingUnit::NGOffsetMappingUnit(NGOffsetMappingUnitType type,
dom_start_(dom_start),
dom_end_(dom_end),
text_content_start_(text_content_start),
- text_content_end_(text_content_end) {}
+ text_content_end_(text_content_end) {
+ AssertValid();
+}
+
+void NGOffsetMappingUnit::AssertValid() const {
+#if ENABLE_SECURITY_ASSERT
+ SECURITY_DCHECK(dom_start_ <= dom_end_) << dom_start_ << " vs. " << dom_end_;
+ SECURITY_DCHECK(text_content_start_ <= text_content_end_)
+ << text_content_start_ << " vs. " << text_content_end_;
+ if (layout_object_->IsText()) {
+ const LayoutText& layout_text = ToLayoutText(*layout_object_);
+ const unsigned text_start =
+ AssociatedNode() ? layout_text.TextStartOffset() : 0;
+ const unsigned text_end = text_start + layout_text.TextLength();
+ SECURITY_DCHECK(dom_end_ >= text_start)
+ << dom_end_ << " vs. " << text_start;
+ SECURITY_DCHECK(dom_end_ <= text_end) << dom_end_ << " vs. " << text_end;
+ } else {
+ SECURITY_DCHECK(dom_start_ == 0) << dom_start_;
+ SECURITY_DCHECK(dom_end_ == 1) << dom_end_;
+ }
+#endif
+}
NGOffsetMappingUnit::~NGOffsetMappingUnit() = default;
@@ -238,7 +259,23 @@ LayoutBlockFlow* NGOffsetMapping::GetInlineFormattingContextOf(
NGOffsetMapping::NGOffsetMapping(UnitVector&& units,
RangeMap&& ranges,
String text)
- : units_(std::move(units)), ranges_(std::move(ranges)), text_(text) {}
+ : units_(std::move(units)), ranges_(std::move(ranges)), text_(text) {
+#if ENABLE_SECURITY_ASSERT
+ for (const auto& unit : units_) {
+ SECURITY_DCHECK(unit.TextContentStart() <= text.length())
+ << unit.TextContentStart() << "<=" << text.length();
+ SECURITY_DCHECK(unit.TextContentEnd() <= text.length())
+ << unit.TextContentEnd() << "<=" << text.length();
+ unit.AssertValid();
+ }
+ for (const auto& pair : ranges) {
+ SECURITY_DCHECK(pair.value.first < units_.size())
+ << pair.value.first << "<" << units_.size();
+ SECURITY_DCHECK(pair.value.second < units_.size())
+ << pair.value.second << "<" << units_.size();
+ }
+#endif
+}
NGOffsetMapping::~NGOffsetMapping() = default;
@@ -385,7 +422,7 @@ Position NGOffsetMapping::StartOfNextNonCollapsedContent(
const auto node_and_offset = ToNodeOffsetPair(position);
const Node& node = node_and_offset.first;
const unsigned offset = node_and_offset.second;
- while (unit != units_.end() && unit->GetOwner() == node) {
+ while (unit != units_.end() && unit->AssociatedNode() == node) {
if (unit->DOMEnd() > offset &&
unit->GetType() != NGOffsetMappingUnitType::kCollapsed) {
const unsigned result = std::max(offset, unit->DOMStart());
@@ -406,7 +443,7 @@ Position NGOffsetMapping::EndOfLastNonCollapsedContent(
const auto node_and_offset = ToNodeOffsetPair(position);
const Node& node = node_and_offset.first;
const unsigned offset = node_and_offset.second;
- while (unit->GetOwner() == node) {
+ while (unit->AssociatedNode() == node) {
if (unit->DOMStart() < offset &&
unit->GetType() != NGOffsetMappingUnitType::kCollapsed) {
const unsigned result = std::min(offset, unit->DOMEnd());
@@ -509,16 +546,6 @@ Position NGOffsetMapping::GetLastPosition(unsigned offset) const {
return CreatePositionForOffsetMapping(node, dom_offset);
}
-PositionWithAffinity NGOffsetMapping::GetPositionWithAffinity(
- const NGCaretNavigator::Position& position) const {
- if (position.IsBeforeCharacter()) {
- return PositionWithAffinity(GetLastPosition(position.index),
- TextAffinity::kDownstream);
- }
- return PositionWithAffinity(GetFirstPosition(position.index + 1),
- TextAffinity::kUpstream);
-}
-
bool NGOffsetMapping::HasBidiControlCharactersOnly(unsigned start,
unsigned end) const {
DCHECK_LE(start, end);
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h
index a8b59f75f1e..743dd9d8757 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h
@@ -9,7 +9,6 @@
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/editing/forward.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
@@ -73,6 +72,8 @@ class CORE_EXPORT NGOffsetMappingUnit {
unsigned ConvertTextContentToFirstDOMOffset(unsigned) const;
unsigned ConvertTextContentToLastDOMOffset(unsigned) const;
+ void AssertValid() const;
+
private:
NGOffsetMappingUnitType type_ = NGOffsetMappingUnitType::kIdentity;
@@ -82,6 +83,10 @@ class CORE_EXPORT NGOffsetMappingUnit {
// offset in |LayoutText::text_| instead of DOM node.
unsigned dom_start_;
unsigned dom_end_;
+
+ // |text_content_start_| and |text_content_end_| are offsets in
+ // |NGOffsetMapping::text_|. These values are in [0, |text_.length()] to
+ // represent collapsed spaces at the end of block.
unsigned text_content_start_;
unsigned text_content_end_;
@@ -219,13 +224,6 @@ class CORE_EXPORT NGOffsetMapping {
Position GetFirstPosition(unsigned) const;
Position GetLastPosition(unsigned) const;
- // Converts the given caret position on text content to a PositionWithAffinity
- // in DOM. If |position| is before a character, the function creates a
- // downstream position before |GetLastPosition()| of the character; otherwise,
- // it returns an upstream position after |GetFirstPosition()| of the character
- PositionWithAffinity GetPositionWithAffinity(
- const NGCaretNavigator::Position& position) const;
-
// Returns all NGOffsetMappingUnits whose text content ranges has non-empty
// (but possibly collapsed) intersection with (start, end). Note that units
// that only "touch" |start| or |end| are excluded.
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc
index 8969d67f8f5..9524bfb97ac 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc
@@ -7,7 +7,6 @@
#include <utility>
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
namespace blink {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
index f41b357b915..78b74c7b685 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
@@ -1225,6 +1225,33 @@ TEST_F(NGOffsetMappingTest, SoftHyphen) {
TEST_RANGE(mapping.GetRanges(), text, 0u, 1u);
}
+// For http://crbug.com/965353
+TEST_F(NGOffsetMappingTest, PreWrapAndReusing) {
+ // Note: "white-space: break-space" yields same result.
+ SetupHtml("t", "<p id='t' style='white-space: pre-wrap'>abc</p>");
+ Element& target = *GetDocument().getElementById("t");
+
+ // Change to <p id=t>abc xyz</p>
+ Text& text = *Text::Create(GetDocument(), " xyz");
+ target.appendChild(&text);
+ UpdateAllLifecyclePhasesForTest();
+
+ // Change to <p id=t> xyz</p>. We attempt to reuse " xyz".
+ target.firstChild()->remove();
+ UpdateAllLifecyclePhasesForTest();
+
+ const NGOffsetMapping& mapping = GetOffsetMapping();
+ EXPECT_EQ(String(u" \u200Bxyz"), mapping.GetText())
+ << "We have ZWS after leading preserved space.";
+ EXPECT_EQ((Vector<NGOffsetMappingUnit>{
+ NGOffsetMappingUnit(kIdentity, *text.GetLayoutObject(), 0u, 1u,
+ 0u, 1u),
+ NGOffsetMappingUnit(kIdentity, *text.GetLayoutObject(), 1u, 4u,
+ 2u, 5u),
+ }),
+ mapping.GetUnits());
+}
+
TEST_F(NGOffsetMappingTest, TextOverflowEllipsis) {
LoadAhem();
SetupHtml("t",
@@ -1241,6 +1268,41 @@ TEST_F(NGOffsetMappingTest, TextOverflowEllipsis) {
TEST_RANGE(mapping.GetRanges(), text, 0u, 1u);
}
+// https://crbug.com/967106
+TEST_F(NGOffsetMappingTest, StartOfNextNonCollapsedContentWithPseudo) {
+ // The white spaces are necessary for bug repro. Do not remove them.
+ SetupHtml("t", R"HTML(
+ <style>span#quote::before { content: '"'}</style>
+ <div id=t>
+ <span>foo </span>
+ <span id=quote>bar</span>
+ </div>)HTML");
+
+ const Element* quote = GetElementById("quote");
+ const Node* text = quote->previousSibling();
+ const Position position = Position::FirstPositionInNode(*text);
+
+ EXPECT_EQ(Position(),
+ GetOffsetMapping().StartOfNextNonCollapsedContent(position));
+}
+
+// https://crbug.com/967106
+TEST_F(NGOffsetMappingTest, EndOfLastNonCollapsedContentWithPseudo) {
+ // The white spaces are necessary for bug repro. Do not remove them.
+ SetupHtml("t", R"HTML(
+ <style>span#quote::after { content: '" '}</style>
+ <div id=t>
+ <span id=quote>foo</span>
+ <span>bar</span>
+ </div>)HTML");
+
+ const Element* quote = GetElementById("quote");
+ const Node* text = quote->nextSibling();
+ const Position position = Position::LastPositionInNode(*text);
+
+ EXPECT_EQ(Position(),
+ GetOffsetMapping().EndOfLastNonCollapsedContent(position));
+}
// Test |GetOffsetMapping| which is available both for LayoutNG and for legacy.
class NGOffsetMappingGetterTest : public RenderingTest,
public testing::WithParamInterface<bool>,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
index bcbbeb51ba0..08681856ab1 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
@@ -4,7 +4,9 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
+#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h"
@@ -15,7 +17,6 @@ namespace blink {
namespace {
struct SameSizeAsNGPhysicalLineBoxFragment : NGPhysicalContainerFragment {
- void* pointer;
NGLineHeightMetrics metrics;
};
@@ -23,6 +24,18 @@ static_assert(sizeof(NGPhysicalLineBoxFragment) ==
sizeof(SameSizeAsNGPhysicalLineBoxFragment),
"NGPhysicalLineBoxFragment should stay small");
+bool IsInlineLeaf(const NGPhysicalFragment& fragment) {
+ if (fragment.IsText())
+ return true;
+ return fragment.IsBox() && fragment.IsAtomicInline();
+}
+
+bool IsEditableFragment(const NGPhysicalFragment& fragment) {
+ if (!fragment.GetNode())
+ return false;
+ return HasEditableStyle(*fragment.GetNode());
+}
+
} // namespace
scoped_refptr<const NGPhysicalLineBoxFragment>
@@ -34,7 +47,7 @@ NGPhysicalLineBoxFragment::Create(NGLineBoxFragmentBuilder* builder) {
// we pass the buffer as a constructor argument.
void* data = ::WTF::Partitions::FastMalloc(
sizeof(NGPhysicalLineBoxFragment) +
- builder->children_.size() * sizeof(NGLinkStorage),
+ builder->children_.size() * sizeof(NGLink),
::WTF::GetStringWithTypeName<NGPhysicalLineBoxFragment>());
new (data) NGPhysicalLineBoxFragment(builder);
return base::AdoptRef(static_cast<NGPhysicalLineBoxFragment*>(data));
@@ -48,8 +61,13 @@ NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment(
kFragmentLineBox,
builder->line_box_type_),
metrics_(builder->metrics_) {
- style_ = std::move(builder->style_);
+ // A line box must have a metrics unless it's an empty line box.
+ DCHECK(!metrics_.IsEmpty() || IsEmptyLineBox());
base_direction_ = static_cast<unsigned>(builder->base_direction_);
+ has_hanging_ = builder->hang_inline_size_ != 0;
+ has_propagated_descendants_ = has_floating_descendants_ ||
+ HasOutOfFlowPositionedDescendants() ||
+ builder->unpositioned_list_marker_;
}
NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics(
@@ -60,17 +78,35 @@ NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics(
return metrics_;
}
-NGPhysicalOffsetRect NGPhysicalLineBoxFragment::ScrollableOverflow(
+PhysicalRect NGPhysicalLineBoxFragment::ScrollableOverflow(
const LayoutObject* container,
const ComputedStyle* container_style,
- NGPhysicalSize container_physical_size) const {
+ PhysicalSize container_physical_size) const {
WritingMode container_writing_mode = container_style->GetWritingMode();
TextDirection container_direction = container_style->Direction();
- NGPhysicalOffsetRect overflow({}, Size());
+ PhysicalRect overflow({}, Size());
for (const auto& child : Children()) {
- NGPhysicalOffsetRect child_scroll_overflow =
+ PhysicalRect child_scroll_overflow =
child->ScrollableOverflowForPropagation(container);
child_scroll_overflow.offset += child.Offset();
+
+ // Chop the hanging part from scrollable overflow. Children overflow in
+ // inline direction should hang, which should not cause scroll.
+ // TODO(kojii): Should move to text fragment to make this more accurate.
+ if (UNLIKELY(has_hanging_ && !child->IsFloatingOrOutOfFlowPositioned())) {
+ if (IsHorizontalWritingMode(container_writing_mode)) {
+ if (child_scroll_overflow.offset.left < 0)
+ child_scroll_overflow.offset.left = LayoutUnit();
+ if (child_scroll_overflow.Right() > Size().width)
+ child_scroll_overflow.ShiftRightEdgeTo(Size().width);
+ } else {
+ if (child_scroll_overflow.offset.top < 0)
+ child_scroll_overflow.offset.top = LayoutUnit();
+ if (child_scroll_overflow.Bottom() > Size().height)
+ child_scroll_overflow.ShiftBottomEdgeTo(Size().height);
+ }
+ }
+
// If child has the same style as parent, parent will compute relative
// offset.
if (&child->Style() != container_style) {
@@ -130,20 +166,63 @@ bool NGPhysicalLineBoxFragment::HasSoftWrapToNextLine() const {
return !break_token.IsFinished() && !break_token.IsForcedBreak();
}
-NGPhysicalOffset NGPhysicalLineBoxFragment::LineStartPoint() const {
- const NGLogicalOffset logical_start; // (0, 0)
- const NGPhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1));
+PhysicalOffset NGPhysicalLineBoxFragment::LineStartPoint() const {
+ const LogicalOffset logical_start; // (0, 0)
+ const PhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1));
return logical_start.ConvertToPhysical(Style().GetWritingMode(),
BaseDirection(), Size(), pixel_size);
}
-NGPhysicalOffset NGPhysicalLineBoxFragment::LineEndPoint() const {
+PhysicalOffset NGPhysicalLineBoxFragment::LineEndPoint() const {
const LayoutUnit inline_size =
NGFragment(Style().GetWritingMode(), *this).InlineSize();
- const NGLogicalOffset logical_end(inline_size, LayoutUnit());
- const NGPhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1));
+ const LogicalOffset logical_end(inline_size, LayoutUnit());
+ const PhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1));
return logical_end.ConvertToPhysical(Style().GetWritingMode(),
BaseDirection(), Size(), pixel_size);
}
+const LayoutObject* NGPhysicalLineBoxFragment::ClosestLeafChildForPoint(
+ const PhysicalOffset& point,
+ bool only_editable_leaves) const {
+ const PhysicalSize unit_square(LayoutUnit(1), LayoutUnit(1));
+ const LogicalOffset logical_point = point.ConvertToLogical(
+ Style().GetWritingMode(), BaseDirection(), Size(), unit_square);
+ const LayoutUnit inline_offset = logical_point.inline_offset;
+ const NGPhysicalFragment* closest_leaf_child = nullptr;
+ LayoutUnit closest_leaf_distance;
+ for (const auto& descendant :
+ NGInlineFragmentTraversal::DescendantsOf(*this)) {
+ const NGPhysicalFragment& fragment = *descendant.fragment;
+ if (!fragment.GetLayoutObject())
+ continue;
+ if (!IsInlineLeaf(fragment) || fragment.IsListMarker())
+ continue;
+ if (only_editable_leaves && !IsEditableFragment(fragment))
+ continue;
+
+ const LogicalSize fragment_logical_size =
+ fragment.Size().ConvertToLogical(Style().GetWritingMode());
+ const LogicalOffset fragment_logical_offset =
+ descendant.offset_to_container_box.ConvertToLogical(
+ Style().GetWritingMode(), BaseDirection(), Size(), fragment.Size());
+ const LayoutUnit inline_min = fragment_logical_offset.inline_offset;
+ const LayoutUnit inline_max = fragment_logical_offset.inline_offset +
+ fragment_logical_size.inline_size;
+ if (inline_offset >= inline_min && inline_offset < inline_max)
+ return fragment.GetLayoutObject();
+
+ const LayoutUnit distance =
+ inline_offset < inline_min ? inline_min - inline_offset
+ : inline_offset - inline_max + LayoutUnit(1);
+ if (!closest_leaf_child || distance < closest_leaf_distance) {
+ closest_leaf_child = &fragment;
+ closest_leaf_distance = distance;
+ }
+ }
+ if (!closest_leaf_child)
+ return nullptr;
+ return closest_leaf_child->GetLayoutObject();
+}
+
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
index a295aacc7a3..7a453e31e5d 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
@@ -32,7 +32,7 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
NGLineBoxFragmentBuilder* builder);
~NGPhysicalLineBoxFragment() {
- for (const NGLinkStorage& child : Children())
+ for (const NGLink& child : Children())
child.fragment->Release();
}
@@ -41,8 +41,9 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
}
bool IsEmptyLineBox() const { return LineBoxType() == kEmptyLineBox; }
+ // True if descendants were propagated to outside of this fragment.
+ bool HasPropagatedDescendants() const { return has_propagated_descendants_; }
- const ComputedStyle& Style() const { return *style_; }
const NGLineHeightMetrics& Metrics() const { return metrics_; }
// The base direction of this line. Also known as the paragraph direction.
@@ -59,20 +60,22 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
// ScrollableOverflow is not precomputed/cached because it cannot be computed
// when LineBox is generated because it needs container dimensions to
// resolve relative position of its children.
- NGPhysicalOffsetRect ScrollableOverflow(
- const LayoutObject* container,
- const ComputedStyle* container_style,
- NGPhysicalSize container_physical_size) const;
+ PhysicalRect ScrollableOverflow(const LayoutObject* container,
+ const ComputedStyle* container_style,
+ PhysicalSize container_physical_size) const;
// Returns the first/last leaf fragment in the line in logical order. Returns
// nullptr if the line box is empty.
const NGPhysicalFragment* FirstLogicalLeaf() const;
const NGPhysicalFragment* LastLogicalLeaf() const;
+ const LayoutObject* ClosestLeafChildForPoint(const PhysicalOffset&,
+ bool only_editable_leaves) const;
+
// Returns a point at the visual start/end of the line.
// Encapsulates the handling of text direction and writing mode.
- NGPhysicalOffset LineStartPoint() const;
- NGPhysicalOffset LineEndPoint() const;
+ PhysicalOffset LineStartPoint() const;
+ PhysicalOffset LineEndPoint() const;
// Whether the content soft-wraps to the next line.
bool HasSoftWrapToNextLine() const;
@@ -80,9 +83,8 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final
private:
NGPhysicalLineBoxFragment(NGLineBoxFragmentBuilder* builder);
- scoped_refptr<const ComputedStyle> style_;
NGLineHeightMetrics metrics_;
- NGLinkStorage children_[];
+ NGLink children_[];
};
template <>
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
index 26ca7c236bf..53167624896 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment_test.cc
@@ -16,7 +16,7 @@ class NGPhysicalLineBoxFragmentTest : public NGLayoutTest {
NGPhysicalLineBoxFragmentTest() : NGLayoutTest() {}
protected:
- const NGPhysicalLineBoxFragment* GetLineBox() const {
+ Vector<const NGPhysicalLineBoxFragment*> GetLineBoxes() const {
const Element* container = GetElementById("root");
DCHECK(container);
const LayoutObject* layout_object = container->GetLayoutObject();
@@ -26,12 +26,21 @@ class NGPhysicalLineBoxFragmentTest : public NGLayoutTest {
To<LayoutBlockFlow>(layout_object)->CurrentFragment();
DCHECK(root_fragment) << container;
+ Vector<const NGPhysicalLineBoxFragment*> lines;
for (const auto& child :
NGInlineFragmentTraversal::DescendantsOf(*root_fragment)) {
- if (child.fragment->IsLineBox())
- return To<NGPhysicalLineBoxFragment>(child.fragment.get());
+ if (const NGPhysicalLineBoxFragment* line =
+ DynamicTo<NGPhysicalLineBoxFragment>(child.fragment.get())) {
+ lines.push_back(line);
+ }
}
- NOTREACHED();
+ return lines;
+ }
+
+ const NGPhysicalLineBoxFragment* GetLineBox() const {
+ Vector<const NGPhysicalLineBoxFragment*> lines = GetLineBoxes();
+ if (!lines.IsEmpty())
+ return lines.front();
return nullptr;
}
};
@@ -47,6 +56,42 @@ class NGPhysicalLineBoxFragmentTest : public NGLayoutTest {
EXPECT_EQ(GetElementById(id), fragment->GetNode()); \
}
+TEST_F(NGPhysicalLineBoxFragmentTest, HasPropagatedDescendantsFloat) {
+ SetBodyInnerHTML(R"HTML(
+ <!DOCTYPE html>
+ <style>
+ div {
+ font-size: 10px;
+ width: 10ch;
+ }
+ .float { float: left; }
+ </style>
+ <div id=root>12345678 12345<div class=float>float</div></div>
+ )HTML");
+ Vector<const NGPhysicalLineBoxFragment*> lines = GetLineBoxes();
+ EXPECT_EQ(lines.size(), 2u);
+ EXPECT_FALSE(lines[0]->HasPropagatedDescendants());
+ EXPECT_TRUE(lines[1]->HasPropagatedDescendants());
+}
+
+TEST_F(NGPhysicalLineBoxFragmentTest, HasPropagatedDescendantsOOF) {
+ SetBodyInnerHTML(R"HTML(
+ <!DOCTYPE html>
+ <style>
+ div {
+ font-size: 10px;
+ width: 10ch;
+ }
+ .abspos { position: absolute; }
+ </style>
+ <div id=root>12345678 12345<div class=abspos>abspos</div></div>
+ )HTML");
+ Vector<const NGPhysicalLineBoxFragment*> lines = GetLineBoxes();
+ EXPECT_EQ(lines.size(), 2u);
+ EXPECT_FALSE(lines[0]->HasPropagatedDescendants());
+ EXPECT_TRUE(lines[1]->HasPropagatedDescendants());
+}
+
TEST_F(NGPhysicalLineBoxFragmentTest, FirstLastLogicalLeafInSimpleText) {
SetBodyInnerHTML(
"<div id=root>"
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
index 6db5844ca0f..384bb007a72 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
@@ -5,12 +5,13 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/dom/node.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
+#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
+#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
@@ -19,24 +20,15 @@ namespace blink {
namespace {
struct SameSizeAsNGPhysicalTextFragment : NGPhysicalFragment {
- void* pointers[3];
+ void* pointers[2];
unsigned offsets[2];
+ PhysicalRect rect;
};
static_assert(sizeof(NGPhysicalTextFragment) ==
sizeof(SameSizeAsNGPhysicalTextFragment),
"NGPhysicalTextFragment should stay small");
-inline bool IsPhysicalTextFragmentAnonymousText(
- const LayoutObject* layout_object) {
- if (!layout_object)
- return false;
- if (layout_object->IsText() && ToLayoutText(layout_object)->IsTextFragment())
- return !ToLayoutTextFragment(layout_object)->AssociatedTextNode();
- const Node* node = layout_object->GetNode();
- return !node || node->IsPseudoElement();
-}
-
NGLineOrientation ToLineOrientation(WritingMode writing_mode) {
switch (writing_mode) {
case WritingMode::kHorizontalTb:
@@ -59,15 +51,14 @@ NGPhysicalTextFragment::NGPhysicalTextFragment(
unsigned start_offset,
unsigned end_offset,
scoped_refptr<const ShapeResultView> shape_result)
- : NGPhysicalFragment(source.GetLayoutObject(),
- source.StyleVariant(),
- source.IsHorizontal()
- ? NGPhysicalSize{shape_result->SnappedWidth(),
- source.Size().height}
- : NGPhysicalSize{source.Size().width,
- shape_result->SnappedWidth()},
- kFragmentText,
- source.TextType()),
+ : NGPhysicalFragment(
+ source.GetMutableLayoutObject(),
+ source.StyleVariant(),
+ source.IsHorizontal()
+ ? PhysicalSize{shape_result->SnappedWidth(), source.Size().height}
+ : PhysicalSize{source.Size().width, shape_result->SnappedWidth()},
+ kFragmentText,
+ source.TextType()),
text_(source.text_),
start_offset_(start_offset),
end_offset_(end_offset),
@@ -75,11 +66,9 @@ NGPhysicalTextFragment::NGPhysicalTextFragment(
DCHECK_GE(start_offset_, source.StartOffset());
DCHECK_LE(end_offset_, source.EndOffset());
DCHECK(shape_result_ || IsFlowControl()) << ToString();
- DCHECK(!source.rare_data_ || !source.rare_data_->style_);
line_orientation_ = source.line_orientation_;
- is_anonymous_text_ = source.is_anonymous_text_;
-
- UpdateSelfInkOverflow();
+ is_generated_text_ = source.is_generated_text_;
+ ink_overflow_computed_ = false;
}
NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder)
@@ -91,53 +80,25 @@ NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder)
DCHECK(shape_result_ || IsFlowControl()) << ToString();
line_orientation_ =
static_cast<unsigned>(ToLineOrientation(builder->GetWritingMode()));
-
- if (UNLIKELY(StyleVariant() == NGStyleVariant::kEllipsis)) {
- EnsureRareData()->style_ = std::move(builder->style_);
- is_anonymous_text_ = true;
- } else {
- is_anonymous_text_ =
- builder->text_type_ == kGeneratedText ||
- IsPhysicalTextFragmentAnonymousText(builder->layout_object_);
- }
-
- UpdateSelfInkOverflow();
-}
-
-NGPhysicalTextFragment::RareData* NGPhysicalTextFragment::EnsureRareData() {
- if (!rare_data_)
- rare_data_ = std::make_unique<RareData>();
- return rare_data_.get();
-}
-
-const ComputedStyle& NGPhysicalTextFragment::Style() const {
- switch (StyleVariant()) {
- case NGStyleVariant::kStandard:
- case NGStyleVariant::kFirstLine:
- return NGPhysicalFragment::Style();
- case NGStyleVariant::kEllipsis:
- DCHECK(rare_data_ && rare_data_->style_);
- return *rare_data_->style_;
- }
- NOTREACHED();
- return NGPhysicalFragment::Style();
+ is_generated_text_ = builder->IsGeneratedText();
+ ink_overflow_computed_ = false;
}
// Convert logical cooridnate to local physical coordinate.
-NGPhysicalOffsetRect NGPhysicalTextFragment::ConvertToLocal(
+PhysicalRect NGPhysicalTextFragment::ConvertToLocal(
const LayoutRect& logical_rect) const {
switch (LineOrientation()) {
case NGLineOrientation::kHorizontal:
- return NGPhysicalOffsetRect(logical_rect);
+ return PhysicalRect(logical_rect);
case NGLineOrientation::kClockWiseVertical:
- return {{size_.width - logical_rect.MaxY(), logical_rect.X()},
- {logical_rect.Height(), logical_rect.Width()}};
+ return {size_.width - logical_rect.MaxY(), logical_rect.X(),
+ logical_rect.Height(), logical_rect.Width()};
case NGLineOrientation::kCounterClockWiseVertical:
- return {{logical_rect.Y(), size_.height - logical_rect.MaxX()},
- {logical_rect.Height(), logical_rect.Width()}};
+ return {logical_rect.Y(), size_.height - logical_rect.MaxX(),
+ logical_rect.Height(), logical_rect.Width()};
}
NOTREACHED();
- return NGPhysicalOffsetRect(logical_rect);
+ return PhysicalRect(logical_rect);
}
// Compute the inline position from text offset, in logical coordinate relative
@@ -190,9 +151,8 @@ NGPhysicalTextFragment::LineLeftAndRightForOffsets(unsigned start_offset,
: std::make_pair(start_position, end_position);
}
-NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
- unsigned start_offset,
- unsigned end_offset) const {
+PhysicalRect NGPhysicalTextFragment::LocalRect(unsigned start_offset,
+ unsigned end_offset) const {
if (start_offset == start_offset_ && end_offset == end_offset_)
return LocalRect();
LayoutUnit start_position, end_position;
@@ -201,34 +161,38 @@ NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
const LayoutUnit inline_size = end_position - start_position;
switch (LineOrientation()) {
case NGLineOrientation::kHorizontal:
- return {{start_position, LayoutUnit()}, {inline_size, Size().height}};
+ return {start_position, LayoutUnit(), inline_size, Size().height};
case NGLineOrientation::kClockWiseVertical:
- return {{LayoutUnit(), start_position}, {Size().width, inline_size}};
+ return {LayoutUnit(), start_position, Size().width, inline_size};
case NGLineOrientation::kCounterClockWiseVertical:
- return {{LayoutUnit(), Size().height - end_position},
- {Size().width, inline_size}};
+ return {LayoutUnit(), Size().height - end_position, Size().width,
+ inline_size};
}
NOTREACHED();
return {};
}
-NGPhysicalOffsetRect NGPhysicalTextFragment::SelfInkOverflow() const {
- return UNLIKELY(rare_data_) ? rare_data_->self_ink_overflow_ : LocalRect();
+PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const {
+ if (!ink_overflow_computed_)
+ ComputeSelfInkOverflow();
+ return self_ink_overflow_;
}
-void NGPhysicalTextFragment::ClearSelfInkOverflow() {
- if (UNLIKELY(rare_data_))
- rare_data_->self_ink_overflow_ = LocalRect();
+void NGPhysicalTextFragment::ClearSelfInkOverflow() const {
+ self_ink_overflow_ = LocalRect();
}
-void NGPhysicalTextFragment::UpdateSelfInkOverflow() {
+void NGPhysicalTextFragment::ComputeSelfInkOverflow() const {
+ ink_overflow_computed_ = true;
+
if (UNLIKELY(!shape_result_)) {
ClearSelfInkOverflow();
return;
}
// Glyph bounds is in logical coordinate, origin at the alphabetic baseline.
- LayoutRect ink_overflow = EnclosingLayoutRect(shape_result_->Bounds());
+ FloatRect text_ink_bounds = Style().GetFont().TextInkBounds(PaintInfo());
+ LayoutRect ink_overflow = EnclosingLayoutRect(text_ink_bounds);
// Make the origin at the logical top of this fragment.
const ComputedStyle& style = Style();
@@ -269,18 +233,18 @@ void NGPhysicalTextFragment::UpdateSelfInkOverflow() {
// Uniting the frame rect ensures that non-ink spaces such side bearings, or
// even space characters, are included in the visual rect for decorations.
- NGPhysicalOffsetRect local_ink_overflow = ConvertToLocal(ink_overflow);
- NGPhysicalOffsetRect local_rect = LocalRect();
+ PhysicalRect local_ink_overflow = ConvertToLocal(ink_overflow);
+ PhysicalRect local_rect = LocalRect();
if (local_rect.Contains(local_ink_overflow)) {
- ClearSelfInkOverflow();
+ self_ink_overflow_ = local_rect;
return;
}
local_ink_overflow.Unite(local_rect);
local_ink_overflow.ExpandEdgesToPixelBoundaries();
- EnsureRareData()->self_ink_overflow_ = local_ink_overflow;
+ self_ink_overflow_ = local_ink_overflow;
}
-scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText(
+scoped_refptr<const NGPhysicalTextFragment> NGPhysicalTextFragment::TrimText(
unsigned new_start_offset,
unsigned new_end_offset) const {
DCHECK(shape_result_);
@@ -294,7 +258,7 @@ scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText(
}
unsigned NGPhysicalTextFragment::TextOffsetForPoint(
- const NGPhysicalOffset& point) const {
+ const PhysicalOffset& point) const {
const ComputedStyle& style = Style();
const LayoutUnit& point_in_line_direction =
style.IsHorizontalWritingMode() ? point.left : point.top;
@@ -311,7 +275,7 @@ unsigned NGPhysicalTextFragment::TextOffsetForPoint(
DCHECK(IsFlowControl());
// Zero-inline-size objects such as newline always return the start offset.
- NGLogicalSize size = Size().ConvertToLogical(style.GetWritingMode());
+ LogicalSize size = Size().ConvertToLogical(style.GetWritingMode());
if (!size.inline_size)
return StartOffset();
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
index c3f7669da89..c46013f2bc8 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
@@ -17,8 +17,9 @@
namespace blink {
-struct NGPhysicalOffsetRect;
+struct PhysicalRect;
class NGTextFragmentBuilder;
+class NGPhysicalTextFragment;
enum class AdjustMidCluster;
@@ -54,7 +55,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
// |text_| holds generated contents instead of |text_content_| in
// |NGNodeInlineData|, e.g. hyphen, and ellipsis.
// Note: Contents generated by CSS pseudo element, e.g. ::before, ::after,
- // are not classified to this. See IsAnonymousText() for them.
+ // are not classified to this. See IsGeneratedText() for them.
kGeneratedText,
// When adding new values, make sure the bit size of |sub_type_| is large
// enough to store.
@@ -63,8 +64,9 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
NGPhysicalTextFragment(NGTextFragmentBuilder*);
NGTextType TextType() const { return static_cast<NGTextType>(sub_type_); }
- // True if this is a generated text.
- bool IsGeneratedText() const { return TextType() == kGeneratedText; }
+ // Returns true if the text is generated (from, e.g., list marker,
+ // pseudo-element, ...) instead of from a DOM text node.
+ bool IsGeneratedText() const { return is_generated_text_; }
// True if this is a forced line break.
bool IsLineBreak() const { return TextType() == kForcedLineBreak; }
// True if this is not for painting; i.e., a forced line break, a tabulation,
@@ -72,8 +74,10 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
bool IsFlowControl() const {
return IsLineBreak() || TextType() == kFlowControl;
}
-
- const ComputedStyle& Style() const;
+ // True if this is an ellpisis generated by `text-overflow: ellipsis`.
+ bool IsEllipsis() const {
+ return StyleVariant() == NGStyleVariant::kEllipsis;
+ }
unsigned Length() const { return end_offset_ - start_offset_; }
StringView Text() const { return StringView(text_, start_offset_, Length()); }
@@ -99,18 +103,18 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
// The layout box of text in (start, end) range in local coordinate.
// Start and end offsets must be between StartOffset() and EndOffset().
- NGPhysicalOffsetRect LocalRect(unsigned start_offset,
- unsigned end_offset) const;
+ PhysicalRect LocalRect(unsigned start_offset, unsigned end_offset) const;
using NGPhysicalFragment::LocalRect;
// The visual bounding box that includes glpyh bounding box and CSS
// properties, in local coordinates.
- NGPhysicalOffsetRect SelfInkOverflow() const;
+ PhysicalRect SelfInkOverflow() const;
// Create a new fragment that has part of the text of this fragment.
// All other properties are the same as this fragment.
- scoped_refptr<const NGPhysicalFragment> TrimText(unsigned start_offset,
- unsigned end_offset) const;
+ scoped_refptr<const NGPhysicalTextFragment> TrimText(
+ unsigned start_offset,
+ unsigned end_offset) const;
scoped_refptr<const NGPhysicalFragment> CloneWithoutOffset() const;
@@ -119,12 +123,8 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
TextShapeResult()};
}
- // Returns true if the text is generated (from, e.g., list marker,
- // pseudo-element, ...) instead of from a DOM text node.
- bool IsAnonymousText() const { return is_anonymous_text_; }
-
// Returns the text offset in the fragment placed closest to the given point.
- unsigned TextOffsetForPoint(const NGPhysicalOffset&) const;
+ unsigned TextOffsetForPoint(const PhysicalOffset&) const;
UBiDiLevel BidiLevel() const;
TextDirection ResolvedDirection() const;
@@ -143,23 +143,14 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
unsigned end_offset,
scoped_refptr<const ShapeResultView> shape_result);
- struct RareData {
- USING_FAST_MALLOC(RareData);
-
- public:
- NGPhysicalOffsetRect self_ink_overflow_;
- scoped_refptr<const ComputedStyle> style_; // Used only for ellipsis.
- };
- RareData* EnsureRareData();
-
LayoutUnit InlinePositionForOffset(unsigned offset,
LayoutUnit (*round)(float),
AdjustMidCluster) const;
- NGPhysicalOffsetRect ConvertToLocal(const LayoutRect&) const;
+ PhysicalRect ConvertToLocal(const LayoutRect&) const;
- void UpdateSelfInkOverflow();
- void ClearSelfInkOverflow();
+ void ComputeSelfInkOverflow() const;
+ void ClearSelfInkOverflow() const;
// The text of NGInlineNode; i.e., of a parent block. The text for this
// fragment is a substring(start_offset_, end_offset_) of this string.
@@ -170,7 +161,10 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment {
const unsigned end_offset_;
const scoped_refptr<const ShapeResultView> shape_result_;
- std::unique_ptr<RareData> rare_data_;
+ // Fragments are immutable but allow certain expensive data, specifically ink
+ // overflow, to be cached as long as it is guaranteed to always recompute to
+ // the same value.
+ mutable PhysicalRect self_ink_overflow_;
};
template <>
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
index ce4035dd595..1867897f0d7 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
@@ -4,7 +4,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_rect.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
@@ -55,9 +55,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRect) {
)HTML");
auto text_fragments = CollectTextFragmentsInContainer("container");
ASSERT_EQ(2u, text_fragments.size());
- EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(20), LayoutUnit(0)},
- {LayoutUnit(20), LayoutUnit(10)}),
- text_fragments[1]->LocalRect(8, 10));
+ EXPECT_EQ(PhysicalRect(20, 0, 20, 10), text_fragments[1]->LocalRect(8, 10));
}
TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) {
@@ -78,8 +76,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) {
// The 2nd line starts at 12, because the div has a bidi-control.
EXPECT_EQ(12u, text_fragments[1]->StartOffset());
// TODO(layout-dev): Investigate whether this is correct.
- // EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(50), LayoutUnit(0)},
- // {LayoutUnit(20), LayoutUnit(10)}),
+ // EXPECT_EQ(PhysicalRect(50, 0, 20, 10),
// text_fragments[1]->LocalRect(14, 16));
}
@@ -97,9 +94,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectVLR) {
)HTML");
auto text_fragments = CollectTextFragmentsInContainer("container");
ASSERT_EQ(2u, text_fragments.size());
- EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(0), LayoutUnit(20)},
- {LayoutUnit(10), LayoutUnit(20)}),
- text_fragments[1]->LocalRect(8, 10));
+ EXPECT_EQ(PhysicalRect(0, 20, 10, 20), text_fragments[1]->LocalRect(8, 10));
}
TEST_F(NGPhysicalTextFragmentTest, LocalRectVRL) {
@@ -116,9 +111,7 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectVRL) {
)HTML");
auto text_fragments = CollectTextFragmentsInContainer("container");
ASSERT_EQ(2u, text_fragments.size());
- EXPECT_EQ(NGPhysicalOffsetRect({LayoutUnit(0), LayoutUnit(20)},
- {LayoutUnit(10), LayoutUnit(20)}),
- text_fragments[1]->LocalRect(8, 10));
+ EXPECT_EQ(PhysicalRect(0, 20, 10, 20), text_fragments[1]->LocalRect(8, 10));
}
TEST_F(NGPhysicalTextFragmentTest, NormalTextIsNotAnonymousText) {
@@ -128,7 +121,7 @@ TEST_F(NGPhysicalTextFragmentTest, NormalTextIsNotAnonymousText) {
ASSERT_EQ(1u, text_fragments.size());
const NGPhysicalTextFragment& text = *text_fragments[0];
- EXPECT_FALSE(text.IsAnonymousText());
+ EXPECT_FALSE(text.IsGeneratedText());
}
TEST_F(NGPhysicalTextFragmentTest, FirstLetterIsNotAnonymousText) {
@@ -141,8 +134,8 @@ TEST_F(NGPhysicalTextFragmentTest, FirstLetterIsNotAnonymousText) {
const NGPhysicalTextFragment& first_letter = *text_fragments[0];
const NGPhysicalTextFragment& remaining_text = *text_fragments[1];
- EXPECT_FALSE(first_letter.IsAnonymousText());
- EXPECT_FALSE(remaining_text.IsAnonymousText());
+ EXPECT_FALSE(first_letter.IsGeneratedText());
+ EXPECT_FALSE(remaining_text.IsGeneratedText());
}
TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) {
@@ -156,9 +149,9 @@ TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) {
const NGPhysicalTextFragment& before = *text_fragments[0];
const NGPhysicalTextFragment& text = *text_fragments[1];
const NGPhysicalTextFragment& after = *text_fragments[2];
- EXPECT_TRUE(before.IsAnonymousText());
- EXPECT_FALSE(text.IsAnonymousText());
- EXPECT_TRUE(after.IsAnonymousText());
+ EXPECT_TRUE(before.IsGeneratedText());
+ EXPECT_FALSE(text.IsGeneratedText());
+ EXPECT_TRUE(after.IsGeneratedText());
}
TEST_F(NGPhysicalTextFragmentTest, Ellipsis) {
@@ -181,15 +174,13 @@ TEST_F(NGPhysicalTextFragmentTest, Ellipsis) {
const NGPhysicalTextFragment& ellipsis = *text_fragments[1];
EXPECT_EQ(NGPhysicalTextFragment::kNormalText, abcdef.TextType());
EXPECT_FALSE(abcdef.IsGeneratedText());
- EXPECT_FALSE(abcdef.IsAnonymousText());
EXPECT_EQ(u8"abc", GetText(abcdef));
EXPECT_EQ(NGPhysicalTextFragment::kGeneratedText, ellipsis.TextType());
EXPECT_TRUE(ellipsis.IsGeneratedText());
- EXPECT_TRUE(ellipsis.IsAnonymousText());
EXPECT_EQ(u8"\u2026", GetText(ellipsis));
}
-TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsAnonymousText) {
+TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsGeneratedText) {
SetBodyInnerHTML(
"<ol style='list-style-position:inside'>"
"<li id=list>text</li>"
@@ -200,8 +191,8 @@ TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsAnonymousText) {
const NGPhysicalTextFragment& marker = *text_fragments[0];
const NGPhysicalTextFragment& text = *text_fragments[1];
- EXPECT_TRUE(marker.IsAnonymousText());
- EXPECT_FALSE(text.IsAnonymousText());
+ EXPECT_TRUE(marker.IsGeneratedText());
+ EXPECT_FALSE(text.IsGeneratedText());
}
TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) {
@@ -222,17 +213,14 @@ TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) {
const NGPhysicalTextFragment& shy = *text_fragments[1];
const NGPhysicalTextFragment& def = *text_fragments[2];
EXPECT_EQ(NGPhysicalTextFragment::kNormalText, abc.TextType());
- EXPECT_FALSE(abc.IsGeneratedText());
// Note: ShapeResult::RunInfo.width_ == 0 for U+00AD
EXPECT_EQ(u8"abc\u00AD", GetText(abc));
EXPECT_EQ(NGPhysicalTextFragment::kGeneratedText, shy.TextType());
- EXPECT_TRUE(shy.IsGeneratedText());
// Note: |ComputedStyle::HypenString()| returns "-" or U+2010 based on
// glyph availability.
if (GetText(shy) != "-")
EXPECT_EQ(u8"\u2010", GetText(shy));
EXPECT_EQ(NGPhysicalTextFragment::kNormalText, def.TextType());
- EXPECT_FALSE(def.IsGeneratedText());
}
TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) {
@@ -244,9 +232,9 @@ TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) {
const NGPhysicalTextFragment& open_quote = *text_fragments[0];
const NGPhysicalTextFragment& text = *text_fragments[1];
const NGPhysicalTextFragment& closed_quote = *text_fragments[2];
- EXPECT_TRUE(open_quote.IsAnonymousText());
- EXPECT_FALSE(text.IsAnonymousText());
- EXPECT_TRUE(closed_quote.IsAnonymousText());
+ EXPECT_TRUE(open_quote.IsGeneratedText());
+ EXPECT_FALSE(text.IsGeneratedText());
+ EXPECT_TRUE(closed_quote.IsGeneratedText());
}
TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulation) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
index 07ee69f28b1..65b78d5bd1b 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc
@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
+#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h"
@@ -58,6 +59,21 @@ void NGTextFragmentBuilder::SetText(
end_effect_ = NGTextEndEffect::kNone;
}
+bool NGTextFragmentBuilder::IsGeneratedText() const {
+ if (UNLIKELY(style_variant_ == NGStyleVariant::kEllipsis ||
+ text_type_ == NGPhysicalTextFragment::kGeneratedText))
+ return true;
+
+ DCHECK(layout_object_);
+ if (const auto* layout_text_fragment =
+ ToLayoutTextFragmentOrNull(layout_object_)) {
+ return !layout_text_fragment->AssociatedTextNode();
+ }
+
+ const Node* node = layout_object_->GetNode();
+ return !node || node->IsPseudoElement();
+}
+
scoped_refptr<const NGPhysicalTextFragment>
NGTextFragmentBuilder::ToTextFragment() {
scoped_refptr<const NGPhysicalTextFragment> fragment =
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
index bf000858f53..b219736b393 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h
@@ -5,7 +5,7 @@
#ifndef NGTextFragmentBuilder_h
#define NGTextFragmentBuilder_h
-#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h"
@@ -43,6 +43,10 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder {
scoped_refptr<const NGPhysicalTextFragment> ToTextFragment();
private:
+ // Returns true if the text is generated (from, e.g., list marker,
+ // pseudo-element, ...) instead of from a DOM text node.
+ bool IsGeneratedText() const;
+
NGInlineNode inline_node_;
String text_;
unsigned item_index_;