blob: 2705012b3d439d4098c848b61a7a8da2c9c47b8e [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
#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_inline_node.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/style/computed_style.h"
namespace blink {
class NGOffsetMappingTest : public NGLayoutTest {
protected:
void SetUp() override {
NGLayoutTest::SetUp();
style_ = ComputedStyle::Create();
style_->GetFont().Update(nullptr);
}
void SetupHtml(const char* id, String html) {
SetBodyInnerHTML(html);
layout_block_flow_ = ToLayoutBlockFlow(GetLayoutObjectByElementId(id));
DCHECK(layout_block_flow_->IsLayoutNGMixin());
layout_object_ = layout_block_flow_->FirstChild();
style_ = layout_object_->Style();
}
const NGOffsetMapping& GetOffsetMapping() const {
const NGOffsetMapping* map =
NGInlineNode(layout_block_flow_).ComputeOffsetMappingIfNeeded();
CHECK(map);
return *map;
}
String GetCollapsedIndexes() const {
const NGOffsetMapping& mapping = GetOffsetMapping();
const EphemeralRange block_range =
EphemeralRange::RangeOfContents(*layout_block_flow_->GetNode());
StringBuilder result;
for (const Node& node : block_range.Nodes()) {
if (!node.IsTextNode())
continue;
Vector<unsigned> collapsed_indexes;
for (const auto& unit : mapping.GetMappingUnitsForDOMRange(
EphemeralRange::RangeOfContents(node))) {
if (unit.GetType() != NGOffsetMappingUnitType::kCollapsed)
continue;
for (unsigned i = unit.DOMStart(); i < unit.DOMEnd(); ++i)
collapsed_indexes.push_back(i);
}
result.Append('{');
bool first = true;
for (unsigned index : collapsed_indexes) {
if (!first)
result.Append(", ");
result.AppendNumber(index);
first = false;
}
result.Append('}');
}
return result.ToString();
}
String TestCollapsingWithCSSWhiteSpace(String text, String whitespace) {
StringBuilder html;
html.Append("<div id=t style=\"white-space:");
html.Append(whitespace);
html.Append("\">");
html.Append(text);
html.Append("</div>");
SetupHtml("t", html.ToString());
return GetCollapsedIndexes();
}
String TestCollapsing(Vector<String> text) {
StringBuilder html;
html.Append("<div id=t>");
for (unsigned i = 0; i < text.size(); ++i) {
if (i)
html.Append("<!---->");
html.Append(text[i]);
}
html.Append("</div>");
SetupHtml("t", html.ToString());
return GetCollapsedIndexes();
}
String TestCollapsing(String text) {
return TestCollapsing(Vector<String>({text}));
}
String TestCollapsing(String text, String text2) {
return TestCollapsing(Vector<String>({text, text2}));
}
String TestCollapsing(String text, String text2, String text3) {
return TestCollapsing(Vector<String>({text, text2, text3}));
}
bool IsOffsetMappingStored() const {
return layout_block_flow_->GetNGInlineNodeData()->offset_mapping.get();
}
const LayoutText* GetLayoutTextUnder(const char* parent_id) {
Element* parent = GetDocument().getElementById(parent_id);
return ToLayoutText(parent->firstChild()->GetLayoutObject());
}
const NGOffsetMappingUnit* GetUnitForPosition(
const Position& position) const {
return GetOffsetMapping().GetMappingUnitForPosition(position);
}
base::Optional<unsigned> GetTextContentOffset(
const Position& position) const {
return GetOffsetMapping().GetTextContentOffset(position);
}
Position StartOfNextNonCollapsedContent(const Position& position) const {
return GetOffsetMapping().StartOfNextNonCollapsedContent(position);
}
Position EndOfLastNonCollapsedContent(const Position& position) const {
return GetOffsetMapping().EndOfLastNonCollapsedContent(position);
}
bool IsBeforeNonCollapsedContent(const Position& position) const {
return GetOffsetMapping().IsBeforeNonCollapsedContent(position);
}
bool IsAfterNonCollapsedContent(const Position& position) const {
return GetOffsetMapping().IsAfterNonCollapsedContent(position);
}
Position GetFirstPosition(unsigned offset) const {
return GetOffsetMapping().GetFirstPosition(offset);
}
Position GetLastPosition(unsigned offset) const {
return GetOffsetMapping().GetLastPosition(offset);
}
scoped_refptr<const ComputedStyle> style_;
LayoutBlockFlow* layout_block_flow_ = nullptr;
LayoutObject* layout_object_ = nullptr;
FontCachePurgePreventer purge_preventer_;
};
TEST_F(NGOffsetMappingTest, CollapseSpaces) {
String input("text text text text");
EXPECT_EQ("{10, 16, 17}", TestCollapsingWithCSSWhiteSpace(input, "normal"));
EXPECT_EQ("{10, 16, 17}", TestCollapsingWithCSSWhiteSpace(input, "nowrap"));
EXPECT_EQ("{10, 16, 17}",
TestCollapsingWithCSSWhiteSpace(input, "-webkit-nowrap"));
EXPECT_EQ("{10, 16, 17}", TestCollapsingWithCSSWhiteSpace(input, "pre-line"));
EXPECT_EQ("{}", TestCollapsingWithCSSWhiteSpace(input, "pre"));
EXPECT_EQ("{}", TestCollapsingWithCSSWhiteSpace(input, "pre-wrap"));
}
TEST_F(NGOffsetMappingTest, CollapseTabs) {
String input("text text \ttext \t\ttext");
EXPECT_EQ("{10, 16, 17}", TestCollapsingWithCSSWhiteSpace(input, "normal"));
EXPECT_EQ("{10, 16, 17}", TestCollapsingWithCSSWhiteSpace(input, "nowrap"));
EXPECT_EQ("{10, 16, 17}",
TestCollapsingWithCSSWhiteSpace(input, "-webkit-nowrap"));
EXPECT_EQ("{10, 16, 17}", TestCollapsingWithCSSWhiteSpace(input, "pre-line"));
EXPECT_EQ("{}", TestCollapsingWithCSSWhiteSpace(input, "pre"));
EXPECT_EQ("{}", TestCollapsingWithCSSWhiteSpace(input, "pre-wrap"));
}
TEST_F(NGOffsetMappingTest, CollapseNewLines) {
String input("text\ntext \n text\n\ntext");
EXPECT_EQ("{10, 11, 17}", TestCollapsingWithCSSWhiteSpace(input, "normal"));
EXPECT_EQ("{10, 11, 17}", TestCollapsingWithCSSWhiteSpace(input, "nowrap"));
EXPECT_EQ("{10, 11, 17}",
TestCollapsingWithCSSWhiteSpace(input, "-webkit-nowrap"));
EXPECT_EQ("{9, 11}", TestCollapsingWithCSSWhiteSpace(input, "pre-line"));
EXPECT_EQ("{}", TestCollapsingWithCSSWhiteSpace(input, "pre"));
EXPECT_EQ("{}", TestCollapsingWithCSSWhiteSpace(input, "pre-wrap"));
}
TEST_F(NGOffsetMappingTest, CollapseNewlinesAsSpaces) {
EXPECT_EQ("{}", TestCollapsing("text\ntext"));
EXPECT_EQ("{5}", TestCollapsing("text\n\ntext"));
EXPECT_EQ("{5, 6, 7}", TestCollapsing("text \n\n text"));
EXPECT_EQ("{5, 6, 7, 8}", TestCollapsing("text \n \n text"));
}
TEST_F(NGOffsetMappingTest, CollapseAcrossElements) {
EXPECT_EQ("{}{0}", TestCollapsing("text ", " text"))
<< "Spaces are collapsed even when across elements.";
}
TEST_F(NGOffsetMappingTest, CollapseLeadingSpaces) {
EXPECT_EQ("{0, 1}", TestCollapsing(" text"));
// TODO(xiaochengh): Currently, LayoutText of trailing whitespace nodes are
// omitted, so we can't verify the following cases. Get around it and make the
// following tests work. EXPECT_EQ("{0}{}", TestCollapsing(" ", "text"));
// EXPECT_EQ("{0}{0}", TestCollapsing(" ", " text"));
}
TEST_F(NGOffsetMappingTest, CollapseTrailingSpaces) {
EXPECT_EQ("{4, 5}", TestCollapsing("text "));
EXPECT_EQ("{}{0}", TestCollapsing("text", " "));
// TODO(xiaochengh): Get around whitespace LayoutText omission, and make the
// following test cases work.
// EXPECT_EQ("{4}{0}", TestCollapsing("text ", " "));
}
// TODO(xiaochengh): Get around whitespace LayoutText omission, and make the
// following test cases work.
TEST_F(NGOffsetMappingTest, DISABLED_CollapseAllSpaces) {
EXPECT_EQ("{0, 1}", TestCollapsing(" "));
EXPECT_EQ("{0, 1}{0, 1}", TestCollapsing(" ", " "));
EXPECT_EQ("{0, 1}{0}", TestCollapsing(" ", "\n"));
EXPECT_EQ("{0}{0, 1}", TestCollapsing("\n", " "));
}
TEST_F(NGOffsetMappingTest, CollapseLeadingNewlines) {
EXPECT_EQ("{0}", TestCollapsing("\ntext"));
EXPECT_EQ("{0, 1}", TestCollapsing("\n\ntext"));
// TODO(xiaochengh): Get around whitespace LayoutText omission, and make the
// following test cases work.
// EXPECT_EQ("{0}{}", TestCollapsing("\n", "text"));
// EXPECT_EQ("{0, 1}{}", TestCollapsing("\n\n", "text"));
// EXPECT_EQ("{0, 1}{}", TestCollapsing(" \n", "text"));
// EXPECT_EQ("{0}{0}", TestCollapsing("\n", " text"));
// EXPECT_EQ("{0, 1}{0}", TestCollapsing("\n\n", " text"));
// EXPECT_EQ("{0, 1}{0}", TestCollapsing(" \n", " text"));
// EXPECT_EQ("{0}{0}", TestCollapsing("\n", "\ntext"));
// EXPECT_EQ("{0, 1}{0}", TestCollapsing("\n\n", "\ntext"));
// EXPECT_EQ("{0, 1}{0}", TestCollapsing(" \n", "\ntext"));
}
TEST_F(NGOffsetMappingTest, CollapseTrailingNewlines) {
EXPECT_EQ("{4}", TestCollapsing("text\n"));
EXPECT_EQ("{}{0}", TestCollapsing("text", "\n"));
// TODO(xiaochengh): Get around whitespace LayoutText omission, and make the
// following test cases work.
// EXPECT_EQ("{4}{0}", TestCollapsing("text\n", "\n"));
// EXPECT_EQ("{4}{0}", TestCollapsing("text\n", " "));
// EXPECT_EQ("{4}{0}", TestCollapsing("text ", "\n"));
}
TEST_F(NGOffsetMappingTest, CollapseNewlineAcrossElements) {
EXPECT_EQ("{}{0}", TestCollapsing("text ", "\ntext"));
EXPECT_EQ("{}{0, 1}", TestCollapsing("text ", "\n text"));
EXPECT_EQ("{}{}{0}", TestCollapsing("text", " ", "\ntext"));
}
TEST_F(NGOffsetMappingTest, CollapseBeforeAndAfterNewline) {
EXPECT_EQ("{4, 5, 7, 8}",
TestCollapsingWithCSSWhiteSpace("text \n text", "pre-line"))
<< "Spaces before and after newline are removed.";
}
TEST_F(NGOffsetMappingTest,
CollapsibleSpaceAfterNonCollapsibleSpaceAcrossElements) {
SetupHtml("t",
"<div id=t>"
"<span style=\"white-space:pre-wrap\">text </span>"
" text"
"</div>");
EXPECT_EQ("{}{}", GetCollapsedIndexes())
<< "The whitespace in constructions like '<span style=\"white-space: "
"pre-wrap\">text <span><span> text</span>' does not collapse.";
}
TEST_F(NGOffsetMappingTest, CollapseZeroWidthSpaces) {
EXPECT_EQ("{5}", TestCollapsing(u"text\u200B\ntext"))
<< "Newline is removed if the character before is ZWS.";
EXPECT_EQ("{4}", TestCollapsing(u"text\n\u200Btext"))
<< "Newline is removed if the character after is ZWS.";
EXPECT_EQ("{5}", TestCollapsing(u"text\u200B\n\u200Btext"))
<< "Newline is removed if the character before/after is ZWS.";
EXPECT_EQ("{4}{}", TestCollapsing(u"text\n", u"\u200Btext"))
<< "Newline is removed if the character after across elements is ZWS.";
EXPECT_EQ("{}{0}", TestCollapsing(u"text\u200B", u"\ntext"))
<< "Newline is removed if the character before is ZWS even across "
"elements.";
EXPECT_EQ("{4, 5}{}", TestCollapsing(u"text \n", u"\u200Btext"))
<< "Collapsible space before newline does not affect the result.";
EXPECT_EQ("{5}{0}", TestCollapsing(u"text\u200B\n", u" text"))
<< "Collapsible space after newline is removed even when the "
"newline was removed.";
EXPECT_EQ("{5}{0}", TestCollapsing(u"text\u200B ", u"\ntext"))
<< "A white space sequence containing a segment break before or after "
"a zero width space is collapsed to a zero width space.";
}
TEST_F(NGOffsetMappingTest, CollapseEastAsianWidth) {
EXPECT_EQ("{1}", TestCollapsing(u"\u4E00\n\u4E00"))
<< "Newline is removed when both sides are Wide.";
EXPECT_EQ("{}", TestCollapsing(u"\u4E00\nA"))
<< "Newline is not removed when after is Narrow.";
EXPECT_EQ("{}", TestCollapsing(u"A\n\u4E00"))
<< "Newline is not removed when before is Narrow.";
EXPECT_EQ("{1}{}", TestCollapsing(u"\u4E00\n", u"\u4E00"))
<< "Newline at the end of elements is removed when both sides are Wide.";
EXPECT_EQ("{}{0}", TestCollapsing(u"\u4E00", u"\n\u4E00"))
<< "Newline at the beginning of elements is removed "
"when both sides are Wide.";
}
#define TEST_UNIT(unit, type, owner, dom_start, dom_end, text_content_start, \
text_content_end) \
EXPECT_EQ(type, unit.GetType()); \
EXPECT_EQ(owner, &unit.GetOwner()); \
EXPECT_EQ(dom_start, unit.DOMStart()); \
EXPECT_EQ(dom_end, unit.DOMEnd()); \
EXPECT_EQ(text_content_start, unit.TextContentStart()); \
EXPECT_EQ(text_content_end, unit.TextContentEnd())
#define TEST_RANGE(ranges, owner, start, end) \
ASSERT_TRUE(ranges.Contains(owner)); \
EXPECT_EQ(start, ranges.at(owner).first); \
EXPECT_EQ(end, ranges.at(owner).second)
TEST_F(NGOffsetMappingTest, StoredResult) {
SetupHtml("t", "<div id=t>foo</div>");
EXPECT_FALSE(IsOffsetMappingStored());
GetOffsetMapping();
EXPECT_TRUE(IsOffsetMappingStored());
}
TEST_F(NGOffsetMappingTest, NGInlineFormattingContextOf) {
SetBodyInnerHTML(
"<div id=container>"
" foo"
" <span id=inline-block style='display:inline-block'>blah</span>"
" <span id=inline-span>bar</span>"
"</div>");
const Element* container = GetElementById("container");
const Element* inline_block = GetElementById("inline-block");
const Element* inline_span = GetElementById("inline-span");
const Node* blah = inline_block->firstChild();
const Node* foo = inline_block->previousSibling();
const Node* bar = inline_span->firstChild();
EXPECT_EQ(nullptr,
NGInlineFormattingContextOf(Position::BeforeNode(*container)));
EXPECT_EQ(nullptr,
NGInlineFormattingContextOf(Position::AfterNode(*container)));
const LayoutObject* container_object = container->GetLayoutObject();
EXPECT_EQ(container_object, NGInlineFormattingContextOf(Position(foo, 0)));
EXPECT_EQ(container_object, NGInlineFormattingContextOf(Position(bar, 0)));
EXPECT_EQ(container_object,
NGInlineFormattingContextOf(Position::BeforeNode(*inline_block)));
EXPECT_EQ(container_object,
NGInlineFormattingContextOf(Position::AfterNode(*inline_block)));
EXPECT_EQ(container_object,
NGInlineFormattingContextOf(Position::BeforeNode(*inline_span)));
EXPECT_EQ(container_object,
NGInlineFormattingContextOf(Position::AfterNode(*inline_span)));
const LayoutObject* inline_block_object = inline_block->GetLayoutObject();
EXPECT_EQ(inline_block_object,
NGInlineFormattingContextOf(Position(blah, 0)));
}
TEST_F(NGOffsetMappingTest, OneTextNode) {
SetupHtml("t", "<div id=t>foo</div>");
const Node* foo_node = layout_object_->GetNode();
const NGOffsetMapping& result = GetOffsetMapping();
EXPECT_EQ("foo", result.GetText());
ASSERT_EQ(1u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo_node,
0u, 3u, 0u, 3u);
ASSERT_EQ(1u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 1u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 3)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(foo_node, 3)));
EXPECT_EQ(Position(foo_node, 0), GetFirstPosition(0));
EXPECT_EQ(Position(foo_node, 1), GetFirstPosition(1));
EXPECT_EQ(Position(foo_node, 2), GetFirstPosition(2));
EXPECT_EQ(Position(foo_node, 3), GetFirstPosition(3));
EXPECT_EQ(Position(foo_node, 0), GetLastPosition(0));
EXPECT_EQ(Position(foo_node, 1), GetLastPosition(1));
EXPECT_EQ(Position(foo_node, 2), GetLastPosition(2));
EXPECT_EQ(Position(foo_node, 3), GetLastPosition(3));
EXPECT_EQ(Position(foo_node, 0),
StartOfNextNonCollapsedContent(Position(foo_node, 0)));
EXPECT_EQ(Position(foo_node, 1),
StartOfNextNonCollapsedContent(Position(foo_node, 1)));
EXPECT_EQ(Position(foo_node, 2),
StartOfNextNonCollapsedContent(Position(foo_node, 2)));
EXPECT_TRUE(StartOfNextNonCollapsedContent(Position(foo_node, 3)).IsNull());
EXPECT_TRUE(EndOfLastNonCollapsedContent(Position(foo_node, 0)).IsNull());
EXPECT_EQ(Position(foo_node, 1),
EndOfLastNonCollapsedContent(Position(foo_node, 1)));
EXPECT_EQ(Position(foo_node, 2),
EndOfLastNonCollapsedContent(Position(foo_node, 2)));
EXPECT_EQ(Position(foo_node, 3),
EndOfLastNonCollapsedContent(Position(foo_node, 3)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(foo_node, 0)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(foo_node, 1)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(foo_node, 2)));
EXPECT_FALSE(
IsBeforeNonCollapsedContent(Position(foo_node, 3))); // false at node end
// false at node start
EXPECT_FALSE(IsAfterNonCollapsedContent(Position(foo_node, 0)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(foo_node, 1)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(foo_node, 2)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(foo_node, 3)));
}
TEST_F(NGOffsetMappingTest, TwoTextNodes) {
SetupHtml("t", "<div id=t>foo<span id=s>bar</span></div>");
const LayoutText* foo = ToLayoutText(layout_object_);
const LayoutText* bar = GetLayoutTextUnder("s");
const Node* foo_node = foo->GetNode();
const Node* bar_node = bar->GetNode();
const Node* span = GetElementById("s");
const NGOffsetMapping& result = GetOffsetMapping();
EXPECT_EQ("foobar", result.GetText());
ASSERT_EQ(2u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo_node,
0u, 3u, 0u, 3u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, bar_node,
0u, 3u, 3u, 6u);
ASSERT_EQ(3u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 1u);
TEST_RANGE(result.GetRanges(), bar_node, 1u, 2u);
TEST_RANGE(result.GetRanges(), span, 1u, 2u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 3)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(bar_node, 0)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(bar_node, 1)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(bar_node, 2)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(bar_node, 3)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(foo_node, 3)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(bar_node, 0)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(bar_node, 1)));
EXPECT_EQ(5u, *GetTextContentOffset(Position(bar_node, 2)));
EXPECT_EQ(6u, *GetTextContentOffset(Position(bar_node, 3)));
EXPECT_EQ(Position(foo_node, 3), GetFirstPosition(3));
EXPECT_EQ(Position(bar_node, 0), GetLastPosition(3));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(foo_node, 0)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(foo_node, 1)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(foo_node, 2)));
EXPECT_FALSE(
IsBeforeNonCollapsedContent(Position(foo_node, 3))); // false at node end
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(bar_node, 0)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(bar_node, 1)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(bar_node, 2)));
EXPECT_FALSE(
IsBeforeNonCollapsedContent(Position(bar_node, 3))); // false at node end
// false at node start
EXPECT_FALSE(IsAfterNonCollapsedContent(Position(foo_node, 0)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(foo_node, 1)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(foo_node, 2)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(foo_node, 3)));
// false at node start
EXPECT_FALSE(IsAfterNonCollapsedContent(Position(bar_node, 0)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(bar_node, 1)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(bar_node, 2)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(bar_node, 3)));
}
TEST_F(NGOffsetMappingTest, BRBetweenTextNodes) {
SetupHtml("t", u"<div id=t>foo<br>bar</div>");
const LayoutText* foo = ToLayoutText(layout_object_);
const LayoutText* br = ToLayoutText(foo->NextSibling());
const LayoutText* bar = ToLayoutText(br->NextSibling());
const Node* foo_node = foo->GetNode();
const Node* br_node = br->GetNode();
const Node* bar_node = bar->GetNode();
const NGOffsetMapping& result = GetOffsetMapping();
EXPECT_EQ("foo\nbar", result.GetText());
ASSERT_EQ(3u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo_node,
0u, 3u, 0u, 3u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, br_node,
0u, 1u, 3u, 4u);
TEST_UNIT(result.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, bar_node,
0u, 3u, 4u, 7u);
ASSERT_EQ(3u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 1u);
TEST_RANGE(result.GetRanges(), br_node, 1u, 2u);
TEST_RANGE(result.GetRanges(), bar_node, 2u, 3u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 3)));
EXPECT_EQ(&result.GetUnits()[1],
GetUnitForPosition(Position::BeforeNode(*br_node)));
EXPECT_EQ(&result.GetUnits()[1],
GetUnitForPosition(Position::AfterNode(*br_node)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 0)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 1)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 2)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 3)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(foo_node, 3)));
EXPECT_EQ(3u, *GetTextContentOffset(Position::BeforeNode(*br_node)));
EXPECT_EQ(4u, *GetTextContentOffset(Position::AfterNode(*br_node)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(bar_node, 0)));
EXPECT_EQ(5u, *GetTextContentOffset(Position(bar_node, 1)));
EXPECT_EQ(6u, *GetTextContentOffset(Position(bar_node, 2)));
EXPECT_EQ(7u, *GetTextContentOffset(Position(bar_node, 3)));
EXPECT_EQ(Position(foo_node, 3), GetFirstPosition(3));
EXPECT_EQ(Position::BeforeNode(*br_node), GetLastPosition(3));
EXPECT_EQ(Position::AfterNode(*br_node), GetFirstPosition(4));
EXPECT_EQ(Position(bar_node, 0), GetLastPosition(4));
}
TEST_F(NGOffsetMappingTest, OneTextNodeWithCollapsedSpace) {
SetupHtml("t", "<div id=t>foo bar</div>");
const Node* node = layout_object_->GetNode();
const NGOffsetMapping& result = GetOffsetMapping();
EXPECT_EQ("foo bar", result.GetText());
ASSERT_EQ(3u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, node, 0u,
4u, 0u, 4u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kCollapsed, node, 4u,
5u, 4u, 4u);
TEST_UNIT(result.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, node, 5u,
8u, 4u, 7u);
ASSERT_EQ(1u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), node, 0u, 3u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(node, 1)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(node, 2)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(node, 3)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(node, 4)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(node, 5)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(node, 6)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(node, 7)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(node, 8)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(node, 2)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(node, 3)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(node, 4)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(node, 5)));
EXPECT_EQ(5u, *GetTextContentOffset(Position(node, 6)));
EXPECT_EQ(6u, *GetTextContentOffset(Position(node, 7)));
EXPECT_EQ(7u, *GetTextContentOffset(Position(node, 8)));
EXPECT_EQ(Position(node, 4), GetFirstPosition(4));
EXPECT_EQ(Position(node, 5), GetLastPosition(4));
EXPECT_EQ(Position(node, 3),
StartOfNextNonCollapsedContent(Position(node, 3)));
EXPECT_EQ(Position(node, 5),
StartOfNextNonCollapsedContent(Position(node, 4)));
EXPECT_EQ(Position(node, 5),
StartOfNextNonCollapsedContent(Position(node, 5)));
EXPECT_EQ(Position(node, 3), EndOfLastNonCollapsedContent(Position(node, 3)));
EXPECT_EQ(Position(node, 4), EndOfLastNonCollapsedContent(Position(node, 4)));
EXPECT_EQ(Position(node, 4), EndOfLastNonCollapsedContent(Position(node, 5)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 0)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 1)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 2)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 3)));
EXPECT_FALSE(IsBeforeNonCollapsedContent(Position(node, 4)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 5)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 6)));
EXPECT_TRUE(IsBeforeNonCollapsedContent(Position(node, 7)));
EXPECT_FALSE(IsBeforeNonCollapsedContent(Position(node, 8)));
EXPECT_FALSE(IsAfterNonCollapsedContent(Position(node, 0)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 1)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 2)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 3)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 4)));
EXPECT_FALSE(IsAfterNonCollapsedContent(Position(node, 5)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 6)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 7)));
EXPECT_TRUE(IsAfterNonCollapsedContent(Position(node, 8)));
}
TEST_F(NGOffsetMappingTest, FullyCollapsedWhiteSpaceNode) {
SetupHtml("t",
"<div id=t>"
"<span id=s1>foo </span>"
" "
"<span id=s2>bar</span>"
"</div>");
const LayoutText* foo = GetLayoutTextUnder("s1");
const LayoutText* bar = GetLayoutTextUnder("s2");
const LayoutText* space = ToLayoutText(layout_object_->NextSibling());
const Node* foo_node = foo->GetNode();
const Node* bar_node = bar->GetNode();
const Node* space_node = space->GetNode();
const Node* span1 = GetElementById("s1");
const Node* span2 = GetElementById("s2");
const NGOffsetMapping& result = GetOffsetMapping();
EXPECT_EQ("foo bar", result.GetText());
ASSERT_EQ(3u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo_node,
0u, 4u, 0u, 4u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kCollapsed,
space_node, 0u, 1u, 4u, 4u);
TEST_UNIT(result.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, bar_node,
0u, 3u, 4u, 7u);
ASSERT_EQ(5u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 1u);
TEST_RANGE(result.GetRanges(), span1, 0u, 1u);
TEST_RANGE(result.GetRanges(), space_node, 1u, 2u);
TEST_RANGE(result.GetRanges(), bar_node, 2u, 3u);
TEST_RANGE(result.GetRanges(), span2, 2u, 3u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 3)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 4)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(space_node, 0)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(space_node, 1)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 0)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 1)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 2)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 3)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(foo_node, 3)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(foo_node, 4)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(space_node, 0)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(space_node, 1)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(bar_node, 0)));
EXPECT_EQ(5u, *GetTextContentOffset(Position(bar_node, 1)));
EXPECT_EQ(6u, *GetTextContentOffset(Position(bar_node, 2)));
EXPECT_EQ(7u, *GetTextContentOffset(Position(bar_node, 3)));
EXPECT_EQ(Position(foo_node, 4), GetFirstPosition(4));
EXPECT_EQ(Position(bar_node, 0), GetLastPosition(4));
EXPECT_TRUE(EndOfLastNonCollapsedContent(Position(space_node, 1u)).IsNull());
EXPECT_TRUE(
StartOfNextNonCollapsedContent(Position(space_node, 0u)).IsNull());
}
TEST_F(NGOffsetMappingTest, ReplacedElement) {
SetupHtml("t", "<div id=t>foo <img> bar</div>");
const LayoutText* foo = ToLayoutText(layout_object_);
const LayoutObject* img = foo->NextSibling();
const LayoutText* bar = ToLayoutText(img->NextSibling());
const Node* foo_node = foo->GetNode();
const Node* img_node = img->GetNode();
const Node* bar_node = bar->GetNode();
const NGOffsetMapping& result = GetOffsetMapping();
ASSERT_EQ(3u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo_node,
0u, 4u, 0u, 4u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, img_node,
0u, 1u, 4u, 5u);
TEST_UNIT(result.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, bar_node,
0u, 4u, 5u, 9u);
ASSERT_EQ(3u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 1u);
TEST_RANGE(result.GetRanges(), img_node, 1u, 2u);
TEST_RANGE(result.GetRanges(), bar_node, 2u, 3u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 3)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 4)));
EXPECT_EQ(&result.GetUnits()[1],
GetUnitForPosition(Position::BeforeNode(*img_node)));
EXPECT_EQ(&result.GetUnits()[1],
GetUnitForPosition(Position::AfterNode(*img_node)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 0)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 1)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 2)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 3)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(bar_node, 4)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(3u, *GetTextContentOffset(Position(foo_node, 3)));
EXPECT_EQ(4u, *GetTextContentOffset(Position(foo_node, 4)));
EXPECT_EQ(4u, *GetTextContentOffset(Position::BeforeNode(*img_node)));
EXPECT_EQ(5u, *GetTextContentOffset(Position::AfterNode(*img_node)));
EXPECT_EQ(5u, *GetTextContentOffset(Position(bar_node, 0)));
EXPECT_EQ(6u, *GetTextContentOffset(Position(bar_node, 1)));
EXPECT_EQ(7u, *GetTextContentOffset(Position(bar_node, 2)));
EXPECT_EQ(8u, *GetTextContentOffset(Position(bar_node, 3)));
EXPECT_EQ(9u, *GetTextContentOffset(Position(bar_node, 4)));
EXPECT_EQ(Position(foo_node, 4), GetFirstPosition(4));
EXPECT_EQ(Position::BeforeNode(*img_node), GetLastPosition(4));
EXPECT_EQ(Position::AfterNode(*img_node), GetFirstPosition(5));
EXPECT_EQ(Position(bar_node, 0), GetLastPosition(5));
}
TEST_F(NGOffsetMappingTest, FirstLetter) {
SetupHtml("t",
"<style>div:first-letter{color:red}</style>"
"<div id=t>foo</div>");
Element* div = GetDocument().getElementById("t");
const Node* foo_node = div->firstChild();
const NGOffsetMapping& result = GetOffsetMapping();
ASSERT_EQ(2u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo_node,
0u, 1u, 0u, 1u);
// first leter and remaining text are always in different mapping units.
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, foo_node,
1u, 3u, 1u, 3u);
ASSERT_EQ(1u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 2u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(Position(foo_node, 1), GetFirstPosition(1));
EXPECT_EQ(Position(foo_node, 1), GetLastPosition(1));
}
TEST_F(NGOffsetMappingTest, FirstLetterWithLeadingSpace) {
SetupHtml("t",
"<style>div:first-letter{color:red}</style>"
"<div id=t> foo</div>");
Element* div = GetDocument().getElementById("t");
const Node* foo_node = div->firstChild();
const NGOffsetMapping& result = GetOffsetMapping();
ASSERT_EQ(3u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kCollapsed, foo_node,
0u, 2u, 0u, 0u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, foo_node,
2u, 3u, 0u, 1u);
// first leter and remaining text are always in different mapping units.
TEST_UNIT(result.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, foo_node,
3u, 5u, 1u, 3u);
ASSERT_EQ(1u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 3u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(foo_node, 1)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(foo_node, 2)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(foo_node, 3)));
EXPECT_EQ(&result.GetUnits()[2], GetUnitForPosition(Position(foo_node, 4)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 0)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 1)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(foo_node, 2)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(foo_node, 3)));
EXPECT_EQ(2u, *GetTextContentOffset(Position(foo_node, 4)));
EXPECT_EQ(Position(foo_node, 0), GetFirstPosition(0));
EXPECT_EQ(Position(foo_node, 2), GetLastPosition(0));
}
TEST_F(NGOffsetMappingTest, FirstLetterWithoutRemainingText) {
SetupHtml("t",
"<style>div:first-letter{color:red}</style>"
"<div id=t> f</div>");
Element* div = GetDocument().getElementById("t");
const Node* text_node = div->firstChild();
const NGOffsetMapping& result = GetOffsetMapping();
ASSERT_EQ(2u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kCollapsed,
text_node, 0u, 2u, 0u, 0u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, text_node,
2u, 3u, 0u, 1u);
ASSERT_EQ(1u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), text_node, 0u, 2u);
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(text_node, 0)));
EXPECT_EQ(&result.GetUnits()[0], GetUnitForPosition(Position(text_node, 1)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(text_node, 2)));
EXPECT_EQ(&result.GetUnits()[1], GetUnitForPosition(Position(text_node, 3)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(text_node, 0)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(text_node, 1)));
EXPECT_EQ(0u, *GetTextContentOffset(Position(text_node, 2)));
EXPECT_EQ(1u, *GetTextContentOffset(Position(text_node, 3)));
EXPECT_EQ(Position(text_node, 0), GetFirstPosition(0));
EXPECT_EQ(Position(text_node, 2), GetLastPosition(0));
}
TEST_F(NGOffsetMappingTest, FirstLetterInDifferentBlock) {
SetupHtml("t",
"<style>:first-letter{float:right}</style><div id=t>foo</div>");
Element* div = GetDocument().getElementById("t");
const Node* text_node = div->firstChild();
auto* mapping0 = NGOffsetMapping::GetFor(Position(text_node, 0));
auto* mapping1 = NGOffsetMapping::GetFor(Position(text_node, 1));
auto* mapping2 = NGOffsetMapping::GetFor(Position(text_node, 2));
auto* mapping3 = NGOffsetMapping::GetFor(Position(text_node, 3));
ASSERT_TRUE(mapping0);
ASSERT_TRUE(mapping1);
ASSERT_TRUE(mapping2);
ASSERT_TRUE(mapping3);
// GetNGOffsetmappingFor() returns different mappings for offset 0 and other
// offsets, because first-letter is laid out in a different block.
EXPECT_NE(mapping0, mapping1);
EXPECT_EQ(mapping1, mapping2);
EXPECT_EQ(mapping2, mapping3);
const NGOffsetMapping& first_letter_result = *mapping0;
ASSERT_EQ(1u, first_letter_result.GetUnits().size());
TEST_UNIT(first_letter_result.GetUnits()[0],
NGOffsetMappingUnitType::kIdentity, text_node, 0u, 1u, 0u, 1u);
ASSERT_EQ(1u, first_letter_result.GetRanges().size());
TEST_RANGE(first_letter_result.GetRanges(), text_node, 0u, 1u);
const NGOffsetMapping& remaining_text_result = *mapping1;
ASSERT_EQ(1u, remaining_text_result.GetUnits().size());
TEST_UNIT(remaining_text_result.GetUnits()[0],
NGOffsetMappingUnitType::kIdentity, text_node, 1u, 3u, 1u, 3u);
ASSERT_EQ(1u, remaining_text_result.GetRanges().size());
TEST_RANGE(remaining_text_result.GetRanges(), text_node, 0u, 1u);
EXPECT_EQ(
&first_letter_result.GetUnits()[0],
first_letter_result.GetMappingUnitForPosition(Position(text_node, 0)));
EXPECT_EQ(
&remaining_text_result.GetUnits()[0],
remaining_text_result.GetMappingUnitForPosition(Position(text_node, 1)));
EXPECT_EQ(
&remaining_text_result.GetUnits()[0],
remaining_text_result.GetMappingUnitForPosition(Position(text_node, 2)));
EXPECT_EQ(
&remaining_text_result.GetUnits()[0],
remaining_text_result.GetMappingUnitForPosition(Position(text_node, 3)));
EXPECT_EQ(0u,
*first_letter_result.GetTextContentOffset(Position(text_node, 0)));
EXPECT_EQ(
1u, *remaining_text_result.GetTextContentOffset(Position(text_node, 1)));
EXPECT_EQ(
2u, *remaining_text_result.GetTextContentOffset(Position(text_node, 2)));
EXPECT_EQ(
3u, *remaining_text_result.GetTextContentOffset(Position(text_node, 3)));
EXPECT_EQ(Position(text_node, 1), first_letter_result.GetFirstPosition(1));
EXPECT_EQ(Position(text_node, 1), first_letter_result.GetLastPosition(1));
EXPECT_EQ(Position(text_node, 1), remaining_text_result.GetFirstPosition(1));
EXPECT_EQ(Position(text_node, 1), remaining_text_result.GetLastPosition(1));
}
TEST_F(NGOffsetMappingTest, WhiteSpaceTextNodeWithoutLayoutText) {
SetupHtml("t", "<div id=t> <span>foo</span></div>");
Element* div = GetDocument().getElementById("t");
const Node* text_node = div->firstChild();
EXPECT_TRUE(EndOfLastNonCollapsedContent(Position(text_node, 1u)).IsNull());
EXPECT_TRUE(StartOfNextNonCollapsedContent(Position(text_node, 0u)).IsNull());
}
TEST_F(NGOffsetMappingTest, OneContainerWithLeadingAndTrailingSpaces) {
SetupHtml("t", "<div id=t><span id=s> foo </span></div>");
const Node* span = GetElementById("s");
const Node* text = span->firstChild();
const NGOffsetMapping& result = GetOffsetMapping();
// 3 units in total:
// - collapsed unit for leading spaces
// - identity unit for "foo"
// - collapsed unit for trailing spaces
ASSERT_EQ(2u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), span, 0u, 3u);
TEST_RANGE(result.GetRanges(), text, 0u, 3u);
auto unit_range = result.GetMappingUnitsForDOMRange(
EphemeralRange(Position::BeforeNode(*span), Position::AfterNode(*span)));
EXPECT_EQ(result.GetUnits().begin(), unit_range.begin());
EXPECT_EQ(result.GetUnits().end(), unit_range.end());
EXPECT_EQ(0u, *GetTextContentOffset(Position::BeforeNode(*span)));
EXPECT_EQ(3u, *GetTextContentOffset(Position::AfterNode(*span)));
}
TEST_F(NGOffsetMappingTest, ContainerWithGeneratedContent) {
SetupHtml("t",
"<style>#s::before{content:'bar'} #s::after{content:'baz'}</style>"
"<div id=t><span id=s>foo</span></div>");
const Node* span = GetElementById("s");
const Node* text = span->firstChild();
const NGOffsetMapping& result = GetOffsetMapping();
ASSERT_EQ(2u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), span, 0u, 1u);
TEST_RANGE(result.GetRanges(), text, 0u, 1u);
auto unit_range = result.GetMappingUnitsForDOMRange(
EphemeralRange(Position::BeforeNode(*span), Position::AfterNode(*span)));
EXPECT_EQ(result.GetUnits().begin(), unit_range.begin());
EXPECT_EQ(result.GetUnits().end(), unit_range.end());
// Offset mapping for inline containers skips generated content.
EXPECT_EQ(3u, *GetTextContentOffset(Position::BeforeNode(*span)));
EXPECT_EQ(6u, *GetTextContentOffset(Position::AfterNode(*span)));
}
TEST_F(NGOffsetMappingTest, Table) {
SetupHtml("t", "<table><tr><td id=t> foo </td></tr></table>");
const Node* foo_node = layout_object_->GetNode();
const NGOffsetMapping& result = GetOffsetMapping();
EXPECT_EQ("foo", result.GetText());
ASSERT_EQ(3u, result.GetUnits().size());
TEST_UNIT(result.GetUnits()[0], NGOffsetMappingUnitType::kCollapsed, foo_node,
0u, 2u, 0u, 0u);
TEST_UNIT(result.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, foo_node,
2u, 5u, 0u, 3u);
TEST_UNIT(result.GetUnits()[2], NGOffsetMappingUnitType::kCollapsed, foo_node,
5u, 7u, 3u, 3u);
ASSERT_EQ(1u, result.GetRanges().size());
TEST_RANGE(result.GetRanges(), foo_node, 0u, 3u);
}
TEST_F(NGOffsetMappingTest, GetMappingForInlineBlock) {
SetupHtml("t",
"<div id=t>foo"
"<span style='display: inline-block' id=span> bar </span>"
"baz</div>");
const Element* div = GetElementById("t");
const Element* span = GetElementById("span");
const NGOffsetMapping* div_mapping =
NGOffsetMapping::GetFor(Position(div->firstChild(), 0));
const NGOffsetMapping* span_mapping =
NGOffsetMapping::GetFor(Position(span->firstChild(), 0));
// NGOffsetMapping::GetFor for Before/AfterAnchor of an inline block should
// return the mapping of the containing block, not of the inline block itself.
const NGOffsetMapping* span_before_mapping =
NGOffsetMapping::GetFor(Position::BeforeNode(*span));
EXPECT_EQ(div_mapping, span_before_mapping);
EXPECT_NE(span_mapping, span_before_mapping);
const NGOffsetMapping* span_after_mapping =
NGOffsetMapping::GetFor(Position::AfterNode(*span));
EXPECT_EQ(div_mapping, span_after_mapping);
EXPECT_NE(span_mapping, span_after_mapping);
}
TEST_F(NGOffsetMappingTest, NoWrapSpaceAndCollapsibleSpace) {
SetupHtml("t",
"<div id=t>"
"<span style='white-space: nowrap' id=span>foo </span>"
" bar"
"</div>");
const Element* span = GetElementById("span");
const Node* foo = span->firstChild();
const Node* bar = span->nextSibling();
const NGOffsetMapping& mapping = GetOffsetMapping();
// NGInlineItemsBuilder inserts a ZWS to indicate break opportunity.
EXPECT_EQ(String(u"foo \u200Bbar"), mapping.GetText());
// Should't map any character in DOM to the generated ZWS.
ASSERT_EQ(3u, mapping.GetUnits().size());
TEST_UNIT(mapping.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, foo, 0u,
4u, 0u, 4u);
TEST_UNIT(mapping.GetUnits()[1], NGOffsetMappingUnitType::kCollapsed, bar, 0u,
1u, 5u, 5u);
TEST_UNIT(mapping.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, bar, 1u,
4u, 5u, 8u);
}
TEST_F(NGOffsetMappingTest, BiDiAroundForcedBreakInPreLine) {
SetupHtml("t",
"<div id=t style='white-space: pre-line'>"
"<bdo dir=rtl id=bdo>foo\nbar</bdo></div>");
const Node* text = GetElementById("bdo")->firstChild();
const NGOffsetMapping& mapping = GetOffsetMapping();
EXPECT_EQ(String(u"\u202Efoo\u202C"
u"\n"
u"\u202Ebar\u202C"),
mapping.GetText());
// Offset mapping should skip generated BiDi control characters.
ASSERT_EQ(3u, mapping.GetUnits().size());
TEST_UNIT(mapping.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, text, 0u,
3u, 1u, 4u); // "foo"
TEST_UNIT(mapping.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, text, 3u,
4u, 5u, 6u); // "\n"
TEST_UNIT(mapping.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, text, 4u,
7u, 7u, 10u); // "bar"
TEST_RANGE(mapping.GetRanges(), text, 0u, 3u);
}
TEST_F(NGOffsetMappingTest, BiDiAroundForcedBreakInPreWrap) {
SetupHtml("t",
"<div id=t style='white-space: pre-wrap'>"
"<bdo dir=rtl id=bdo>foo\nbar</bdo></div>");
const Node* text = GetElementById("bdo")->firstChild();
const NGOffsetMapping& mapping = GetOffsetMapping();
EXPECT_EQ(String(u"\u202Efoo\u202C"
u"\n"
u"\u202Ebar\u202C"),
mapping.GetText());
// Offset mapping should skip generated BiDi control characters.
ASSERT_EQ(3u, mapping.GetUnits().size());
TEST_UNIT(mapping.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, text, 0u,
3u, 1u, 4u); // "foo"
TEST_UNIT(mapping.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, text, 3u,
4u, 5u, 6u); // "\n"
TEST_UNIT(mapping.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, text, 4u,
7u, 7u, 10u); // "bar"
TEST_RANGE(mapping.GetRanges(), text, 0u, 3u);
}
TEST_F(NGOffsetMappingTest, BiDiAroundForcedBreakInPre) {
SetupHtml("t",
"<div id=t style='white-space: pre'>"
"<bdo dir=rtl id=bdo>foo\nbar</bdo></div>");
const Node* text = GetElementById("bdo")->firstChild();
const NGOffsetMapping& mapping = GetOffsetMapping();
EXPECT_EQ(String(u"\u202Efoo\u202C"
u"\n"
u"\u202Ebar\u202C"),
mapping.GetText());
// Offset mapping should skip generated BiDi control characters.
ASSERT_EQ(3u, mapping.GetUnits().size());
TEST_UNIT(mapping.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, text, 0u,
3u, 1u, 4u); // "foo"
TEST_UNIT(mapping.GetUnits()[1], NGOffsetMappingUnitType::kIdentity, text, 3u,
4u, 5u, 6u); // "\n"
TEST_UNIT(mapping.GetUnits()[2], NGOffsetMappingUnitType::kIdentity, text, 4u,
7u, 7u, 10u); // "bar"
TEST_RANGE(mapping.GetRanges(), text, 0u, 3u);
}
TEST_F(NGOffsetMappingTest, SoftHyphen) {
LoadAhem();
SetupHtml(
"t",
"<div id=t style='font: 10px/10px Ahem; width: 40px'>abc&shy;def</div>");
const Node* text = GetElementById("t")->firstChild();
const NGOffsetMapping& mapping = GetOffsetMapping();
// Line wrapping and hyphenation are oblivious to offset mapping.
ASSERT_EQ(1u, mapping.GetUnits().size());
TEST_UNIT(mapping.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, text, 0u,
7u, 0u, 7u);
TEST_RANGE(mapping.GetRanges(), text, 0u, 1u);
}
TEST_F(NGOffsetMappingTest, TextOverflowEllipsis) {
LoadAhem();
SetupHtml("t",
"<div id=t style='font: 10px/10px Ahem; width: 30px; overflow: "
"hidden; text-overflow: ellipsis'>123456</div>");
const Node* text = GetElementById("t")->firstChild();
const NGOffsetMapping& mapping = GetOffsetMapping();
// Ellipsis is oblivious to offset mapping.
ASSERT_EQ(1u, mapping.GetUnits().size());
TEST_UNIT(mapping.GetUnits()[0], NGOffsetMappingUnitType::kIdentity, text, 0u,
6u, 0u, 6u);
TEST_RANGE(mapping.GetRanges(), text, 0u, 1u);
}
// Test |GetOffsetMapping| which is available both for LayoutNG and for legacy.
class NGOffsetMappingGetterTest : public RenderingTest,
public testing::WithParamInterface<bool>,
private ScopedLayoutNGForTest {
public:
NGOffsetMappingGetterTest() : ScopedLayoutNGForTest(GetParam()) {}
};
INSTANTIATE_TEST_CASE_P(NGOffsetMappingTest,
NGOffsetMappingGetterTest,
testing::Bool());
TEST_P(NGOffsetMappingGetterTest, Get) {
SetBodyInnerHTML(R"HTML(
<div id=container>
Whitespaces in this text should be collapsed.
</div>
)HTML");
LayoutBlockFlow* layout_block_flow =
ToLayoutBlockFlow(GetLayoutObjectByElementId("container"));
DCHECK(layout_block_flow->ChildrenInline());
// For the purpose of this test, ensure this is laid out by each layout
// engine.
DCHECK_EQ(layout_block_flow->IsLayoutNGMixin(), GetParam());
std::unique_ptr<NGOffsetMapping> storage;
const NGOffsetMapping* mapping =
NGInlineNode::GetOffsetMapping(layout_block_flow, &storage);
EXPECT_TRUE(mapping);
EXPECT_EQ(!storage, GetParam()); // |storage| is used only for legacy.
const String& text_content = mapping->GetText();
EXPECT_EQ(text_content, "Whitespaces in this text should be collapsed.");
}
} // namespace blink