Commit 63d12e3a authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

[LayoutNG] Add NG alternatives of LayoutText::ContainsCaretOffset

This patch adds LayoutNG alternatives of LayoutText::ContainsCaretOffset for
both "common" LayoutText and ::first-letter LayoutTextFragments, with an
extensive set of unit tests.

Bug: 771398
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_layout_tests_layout_ng
Change-Id: I59b17046c271bf8c5c1fb4fdf7421b37a3183073
Reviewed-on: https://chromium-review.googlesource.com/724262Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#510130}
parent 9d17a277
......@@ -1992,7 +1992,21 @@ bool LayoutText::HasNonCollapsedText() const {
}
bool LayoutText::ContainsCaretOffset(int text_offset) const {
// TODO(crbug.com/771398): Add LayoutNG alternative.
DCHECK_GE(text_offset, 0);
if (ShouldUseNGAlternatives()) {
// ::first-letter handling should be done by LayoutTextFragment override.
DCHECK(!IsTextFragment());
if (!GetNode())
return false;
const NGOffsetMappingResult& mapping = GetNGOffsetMapping();
if (mapping.IsNonCollapsedCharacter(*GetNode(), text_offset))
return true;
if (!mapping.IsAfterNonCollapsedCharacter(*GetNode(), text_offset))
return false;
return *mapping.GetCharacterBefore(*GetNode(), text_offset) !=
kNewlineCharacter;
}
for (InlineTextBox* box : InlineTextBoxesOf(*this)) {
if (text_offset < static_cast<int>(box->Start()) &&
!ContainsReversedText()) {
......
......@@ -206,7 +206,7 @@ class CORE_EXPORT LayoutText : public LayoutObject {
// or segment break in node with style white-space: pre/pre-line/pre-wrap).
// TODO(editing-dev): The behavior is introduced by crrev.com/e3eb4e in
// InlineTextBox::ContainsCaretOffset(). Try to understand it.
bool ContainsCaretOffset(int) const;
virtual bool ContainsCaretOffset(int) const;
int CaretMinOffset() const override;
int CaretMaxOffset() const override;
......
......@@ -222,4 +222,25 @@ unsigned LayoutTextFragment::ResolvedTextLength() const {
return end - start;
}
bool LayoutTextFragment::ContainsCaretOffset(int text_offset) const {
if (!ShouldUseNGAlternatives())
return LayoutText::ContainsCaretOffset(text_offset);
DCHECK_GE(text_offset, 0);
if (text_offset > static_cast<int>(FragmentLength()))
return false;
const Node* node = AssociatedTextNode();
if (!node)
return false;
const unsigned dom_offset = text_offset + Start();
const NGOffsetMappingResult& mapping = GetNGOffsetMapping();
if (mapping.IsNonCollapsedCharacter(*node, dom_offset))
return true;
if (text_offset == 0)
return false;
if (!mapping.IsAfterNonCollapsedCharacter(*node, dom_offset))
return false;
return *mapping.GetCharacterBefore(*node, dom_offset) != kNewlineCharacter;
}
} // namespace blink
......@@ -51,6 +51,7 @@ class CORE_EXPORT LayoutTextFragment final : public LayoutText {
bool IsTextFragment() const override { return true; }
bool ContainsCaretOffset(int) const override;
int CaretMinOffset() const override;
int CaretMaxOffset() const override;
unsigned ResolvedTextLength() const override;
......
......@@ -41,10 +41,12 @@ TEST_F(LayoutTextFragmentTest, LegacyBasics) {
EXPECT_EQ(0, GetFirstLetter()->CaretMinOffset());
EXPECT_EQ(1, GetFirstLetter()->CaretMaxOffset());
EXPECT_EQ(1u, GetFirstLetter()->ResolvedTextLength());
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(0));
EXPECT_EQ(0, GetRemainingText()->CaretMinOffset());
EXPECT_EQ(2, GetRemainingText()->CaretMaxOffset());
EXPECT_EQ(2u, GetRemainingText()->ResolvedTextLength());
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0));
}
TEST_F(LayoutTextFragmentTest, CaretMinMaxOffsetNG) {
......@@ -181,4 +183,105 @@ TEST_F(LayoutTextFragmentTest, ResolvedTextLengthCollapsedRemainingTextNG) {
EXPECT_EQ(0u, GetRemainingText()->ResolvedTextLength());
}
TEST_F(LayoutTextFragmentTest, ContainsCaretOffsetNG) {
ScopedLayoutNGForTest layout_ng(true);
ScopedLayoutNGPaintFragmentsForTest layout_ng_paint_fragments(true);
SetBasicBody("(f)oo");
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(0)); // "|(f)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(1)); // "(|f)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(2)); // "(f|)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(3)); // "(f)|oo"
EXPECT_FALSE(GetFirstLetter()->ContainsCaretOffset(4)); // out of range
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0)); // "(f)|oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // "(f)o|o"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(2)); // "(f)oo|"
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(3)); // out of range
SetBasicBody(" (f)oo");
EXPECT_FALSE(GetFirstLetter()->ContainsCaretOffset(0)); // "| (f)oo"
EXPECT_FALSE(GetFirstLetter()->ContainsCaretOffset(1)); // " | (f)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(2)); // " |(f)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(3)); // " (|f)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(4)); // " (f|)oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(5)); // " (f)|oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0)); // " (f)|oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // " (f)o|o"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(2)); // " (f)oo|"
SetBasicBody("(f)oo ");
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(0)); // "|(f)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(1)); // "(|f)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(2)); // "(f|)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(3)); // "(f)|oo "
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0)); // "(f)|oo "
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // "(f)o|o "
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(2)); // "(f)oo| "
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(3)); // "(f)oo | "
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(4)); // "(f)oo |"
SetBasicBody(" (f)oo ");
EXPECT_FALSE(GetFirstLetter()->ContainsCaretOffset(0)); // "| (f)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(1)); // " |(f)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(2)); // " (|f)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(3)); // " (f|)oo "
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(4)); // " (f)|oo "
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0)); // " (f)|oo "
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // " (f)o|o "
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(2)); // " (f)oo| "
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(3)); // " (f)oo | "
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(4)); // " (f)oo |"
}
TEST_F(LayoutTextFragmentTest, ContainsCaretOffsetSpacesInBetweenNG) {
ScopedLayoutNGForTest layout_ng(true);
ScopedLayoutNGPaintFragmentsForTest layout_ng_paint_fragments(true);
SetBasicBody("(f) oo");
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(0)); // "|(f) oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(1)); // "(|f) oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(2)); // "(f|) oo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(3)); // "(f)| oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0)); // "(f)| oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // "(f) | oo"
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(2)); // "(f) | oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(3)); // "(f) |oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(4)); // "(f) o|o"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(5)); // "(f) oo|"
}
TEST_F(LayoutTextFragmentTest, ContainsCaretOffsetPreNG) {
ScopedLayoutNGForTest layout_ng(true);
ScopedLayoutNGPaintFragmentsForTest layout_ng_paint_fragments(true);
SetBodyInnerHTML("<pre id='target'>(f) oo\n</pre>");
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(0)); // "|(f) oo\n"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(1)); // "(|f) oo\n"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(2)); // "(f|) oo\n"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(3)); // "(f)| oo\n"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(0)); // "(f)| oo\n"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // "(f) | oo\n"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(2)); // "(f) | oo\n"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(3)); // "(f) |oo\n"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(4)); // "(f) o|o\n"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(5)); // "(f) oo|\n"
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(6)); // "(f) oo\n|"
}
TEST_F(LayoutTextFragmentTest, ContainsCaretOffsetPreLineNG) {
ScopedLayoutNGForTest layout_ng(true);
ScopedLayoutNGPaintFragmentsForTest layout_ng_paint_fragments(true);
SetBodyInnerHTML("<div id='target' style='white-space: pre-line'>F \n \noo");
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(0)); // "|F \n \noo"
EXPECT_TRUE(GetFirstLetter()->ContainsCaretOffset(1)); // "F| \n \noo"
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(0)); // "F| \n \noo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(1)); // "F |\n \noo"
EXPECT_FALSE(GetRemainingText()->ContainsCaretOffset(2)); // "F \n| \noo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(3)); // "F \n |\noo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(4)); // "F \n \n|oo"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(5)); // "F \n \no|o"
EXPECT_TRUE(GetRemainingText()->ContainsCaretOffset(6)); // "F \n \noo|"
}
} // namespace blink
......@@ -184,4 +184,61 @@ TEST_F(LayoutTextTest, ContainsCaretOffsetInPre) {
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // "foo\nbar|"
}
TEST_F(LayoutTextTest, ContainsCaretOffsetNG) {
ScopedLayoutNGForTest layout_ng(true);
ScopedLayoutNGPaintFragmentsForTest layout_ng_paint_fragments(true);
// This test records the behavior introduced in crrev.com/e3eb4e
SetBasicBody(" foo bar ");
EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(0)); // "| foo bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // " |foo bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // " f|oo bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // " fo|o bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(4)); // " foo| bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(5)); // " foo | bar "
EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(6)); // " foo | bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // " foo |bar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(8)); // " foo b|ar "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(9)); // " foo ba|r "
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(10)); // " foo bar| "
EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(11)); // " foo bar |"
EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(12)); // out of range
}
TEST_F(LayoutTextTest, ContainsCaretOffsetNGInPre) {
ScopedLayoutNGForTest layout_ng(true);
ScopedLayoutNGPaintFragmentsForTest layout_ng_paint_fragments(true);
// These tests record the behavior introduced in crrev.com/e3eb4e
SetBodyInnerHTML("<pre id='target'>foo bar</pre>");
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(0)); // "|foo bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // "f|oo bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // "fo|o bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // "foo| bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(4)); // "foo | bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(5)); // "foo | bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(6)); // "foo |bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // "foo b|ar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(8)); // "foo ba|r"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(9)); // "foo bar|"
SetBodyInnerHTML("<pre id='target'>foo\n</pre>");
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(0)); // "|foo\n"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // "f|oo\n"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // "fo|o\n"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // "foo|\n"
EXPECT_FALSE(GetBasicText()->ContainsCaretOffset(4)); // "foo\n|"
SetBodyInnerHTML("<pre id='target'>foo\nbar</pre>");
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(0)); // "|foo\nbar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(1)); // "f|oo\nbar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(2)); // "fo|o\nbar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(3)); // "foo|\nbar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(4)); // "foo\n|bar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(5)); // "foo\nb|ar"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(6)); // "foo\nba|r"
EXPECT_TRUE(GetBasicText()->ContainsCaretOffset(7)); // "foo\nbar|"
}
} // namespace blink
......@@ -185,4 +185,13 @@ bool NGOffsetMappingResult::IsAfterNonCollapsedCharacter(
unit->GetType() != NGOffsetMappingUnitType::kCollapsed;
}
Optional<UChar> NGOffsetMappingResult::GetCharacterBefore(
const Node& node,
unsigned offset) const {
const size_t text_content_offset = GetTextContentOffset(node, offset);
if (text_content_offset == kNotFound || !text_content_offset)
return {};
return text_[text_content_offset - 1];
}
} // namespace blink
......@@ -9,6 +9,7 @@
#include "platform/heap/Handle.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/HashMap.h"
#include "platform/wtf/Optional.h"
#include "platform/wtf/Vector.h"
#include "platform/wtf/text/WTFString.h"
......@@ -110,16 +111,19 @@ class CORE_EXPORT NGOffsetMappingResult {
unsigned) const;
// Returns the text content offset corresponding to the given DOM offset.
// TODO(xiaochengh): Use Optional<> return type to indicate inexistent result.
size_t GetTextContentOffset(const Node&, unsigned) const;
// Starting from the given DOM offset in the node, finds the first
// non-collapsed character and returns its offset; Or returns the last offset
// in the node if such a character does not exist.
// TODO(xiaochengh): Use Optional<> return type to indicate inexistent result.
unsigned StartOfNextNonCollapsedCharacter(const Node&, unsigned offset) const;
// Starting from the given DOM offset in the node, reversely finds the first
// non-collapsed character and returns 1 + its offset; Or returns 0 if such a
// character does not exist.
// TODO(xiaochengh): Use Optional<> return type to indicate inexistent result.
unsigned EndOfLastNonCollapsedCharacter(const Node&, unsigned offset) const;
// Returns true if the character at the position is non-collapsed. If the
......@@ -131,6 +135,10 @@ class CORE_EXPORT NGOffsetMappingResult {
// offset is at the beginning of the node, returns false.
bool IsAfterNonCollapsedCharacter(const Node&, unsigned offset) const;
// Maps the given DOM offset to text content, and then returns the text
// content character before the offset. Returns nullopt if it does not exist.
Optional<UChar> GetCharacterBefore(const Node&, unsigned offset) const;
// TODO(xiaochengh): Add APIs for reverse mapping.
private:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment