blob: b4bbc0c7d71681630ce7802c09c9241929d4189a [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/physical_box_fragment.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
bool operator==(const LocalCaretRect& rect1, const LocalCaretRect& rect2) {
return rect1.layout_object == rect2.layout_object && rect1.rect == rect2.rect;
}
std::ostream& operator<<(std::ostream& out, const LocalCaretRect& caret_rect) {
return out << "layout_object = " << caret_rect.layout_object
<< ", rect = " << caret_rect.rect;
}
class LocalCaretRectTest : public EditingTestBase {
protected:
LocalCaretRect LocalCaretRectOf(
const Position& position,
EditingBoundaryCrossingRule rule = kCanCrossEditingBoundary) {
return LocalCaretRectOfPosition(PositionWithAffinity(position), rule);
}
};
TEST_F(LocalCaretRectTest, DOMAndFlatTrees) {
const char* body_content =
"<p id='host'><b slot='#one' id='one'>1</b></p><b id='two'>22</b>";
const char* shadow_content =
"<b id='two'>22</b><slot name=#one></slot><b id='three'>333</b>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* one = GetDocument().getElementById(AtomicString("one"));
const LocalCaretRect& caret_rect_from_dom_tree = LocalCaretRectOfPosition(
PositionWithAffinity(Position(one->firstChild(), 0)));
const LocalCaretRect& caret_rect_from_flat_tree = LocalCaretRectOfPosition(
PositionInFlatTreeWithAffinity(PositionInFlatTree(one->firstChild(), 0)));
EXPECT_FALSE(caret_rect_from_dom_tree.IsEmpty());
EXPECT_EQ(caret_rect_from_dom_tree, caret_rect_from_flat_tree);
}
// http://crbug.com/1174101
TEST_F(LocalCaretRectTest, EmptyInlineFlex) {
LoadAhem();
InsertStyleElement(R"CSS(
div { font: 10px/15px Ahem; width: 100px; }
i {
display: inline-flex;
width: 30px; height: 30px;
border: solid 10px red;
})CSS");
// |ComputeInlinePosition(AfterChildren:<div>)=AfterChildren:<b>
// When removing <i>, we have <b>@0
SetBodyContent(
"<div id=target contenteditable>"
"ab<i contenteditable=false><b></b></i></div>");
const auto& target = *GetElementById("target");
const auto& ab = *To<Text>(target.firstChild());
const auto& inline_flex = *ab.nextSibling();
const LocalCaretRect before_ab =
LocalCaretRect(ab.GetLayoutObject(), {0, 32, 1, 10});
const LocalCaretRect before_inline_flex =
LocalCaretRect(ab.GetLayoutObject(), {20, 32, 1, 10});
const LocalCaretRect after_inline_flex =
LocalCaretRect(inline_flex.GetLayoutObject(), {49, 0, 1, 50});
EXPECT_EQ(before_ab, LocalCaretRectOf(Position(target, 0)));
EXPECT_EQ(before_inline_flex, LocalCaretRectOf(Position(target, 1)));
EXPECT_EQ(after_inline_flex, LocalCaretRectOf(Position(target, 2)));
EXPECT_EQ(before_ab, LocalCaretRectOf(Position::BeforeNode(target)));
EXPECT_EQ(after_inline_flex, LocalCaretRectOf(Position::AfterNode(target)));
EXPECT_EQ(after_inline_flex,
LocalCaretRectOf(Position::LastPositionInNode(target)));
}
TEST_F(LocalCaretRectTest, SimpleText) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='font: 10px/10px Ahem; width: 30px'>XXX</div>");
const Node* foo = GetElementById("div")->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, MixedHeightText) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='font: 10px/10px Ahem; width: 30px'>Xpp</div>");
const Node* foo = GetElementById("div")->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, RtlText) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo dir=rtl id=bdo style='display: block; "
"font: 10px/10px Ahem; width: 30px'>XXX</bdo>");
const Node* foo = GetElementById("bdo")->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, ClampingAndRounding) {
// crbug.com/1228620
LoadAhem();
SetBodyContent(R"HTML(
<style>
#root {
margin-left: 0.6px;
width: 150.6px;
text-align: right;
font: 30px/30px Ahem;
}
</style>
<div id=root>def</div>)HTML");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(
LocalCaretRect(text->GetLayoutObject(), PhysicalRect(149, 0, 1, 30)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position(text, 3), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, OverflowTextLtr) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=root style='font: 10px/10px Ahem; width: 30px'>"
"XXXX"
"</div>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(39, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 4), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, UnderflowTextLtr) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=root style='font: 10px/10px Ahem; width: 30px'>"
"XX"
"</div>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, OverflowTextRtl) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo id=root style='display:block; font: 10px/10px Ahem; width: 30px' "
"dir=rtl>"
"XXXX"
"</bdo>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(
LocalCaretRect(text->GetLayoutObject(), PhysicalRect(-10, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position(text, 4), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, UnderflowTextRtl) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo id=root style='display:block; font: 10px/10px Ahem; width: 30px' "
"dir=rtl>"
"XX"
"</bdo>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, VerticalRLText) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='writing-mode: vertical-rl; word-break: break-all; "
"font: 10px/10px Ahem; width: 30px; height: 30px'>XXXYYYZZZ</div>");
const Node* foo = GetElementById("div")->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kUpstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 4), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 5), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 6), TextAffinity::kUpstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 6), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 7), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 8), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 9), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, VerticalLRText) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='writing-mode: vertical-lr; word-break: break-all; "
"font: 10px/10px Ahem; width: 30px; height: 30px'>XXXYYYZZZ</div>");
const Node* foo = GetElementById("div")->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kUpstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 4), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 5), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 6), TextAffinity::kUpstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 6), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 7), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 8), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 9), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, OverflowTextVerticalLtr) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=root style='font: 10px/10px Ahem; height: 30px; writing-mode: "
"vertical-lr'>"
"XXXX"
"</div>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 39, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 4), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, UnderflowTextVerticalLtr) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=root style='font: 10px/10px Ahem; height: 30px; writing-mode: "
"vertical-lr'>"
"XX"
"</div>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 20, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, OverflowTextVerticalRtl) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo id=root style='display:block; font: 10px/10px Ahem; height: 30px; "
"writing-mode: vertical-lr' dir=rtl>"
"XXXX"
"</bdo>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(
LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, -10, 10, 1)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position(text, 4), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, UnderflowTextVerticalRtl) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo id=root style='display:block; font: 10px/10px Ahem; height: 30px; "
"writing-mode: vertical-lr' dir=rtl>"
"XX"
"</bdo>");
const Node* text = GetElementById("root")->firstChild();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 29, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 0), TextAffinity::kDownstream)));
// LocalCaretRect may be outside the containing block.
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(0, 10, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text, 2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, TwoLinesOfTextWithSoftWrap) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='font: 10px/10px Ahem; width: 30px; "
"word-break: break-all'>XXXXXX</div>");
const Node* foo = GetElementById("div")->firstChild();
// First line
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kUpstream)));
// Second line
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(10, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 4), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(20, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 5), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(29, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 6), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, SoftLineWrapBetweenMultipleTextNodes) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div style='font: 10px/10px Ahem; width: 30px; word-break: break-all'>"
"<span>A</span>"
"<span>B</span>"
"<span id=span-c>C</span>"
"<span id=span-d>D</span>"
"<span>E</span>"
"<span>F</span>"
"</div>");
const Node* text_c = GetElementById("span-c")->firstChild();
const Node* text_d = GetElementById("span-d")->firstChild();
const Position after_c(text_c, 1);
EXPECT_EQ(
LocalCaretRect(text_c->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(after_c, TextAffinity::kUpstream)));
EXPECT_EQ(
LocalCaretRect(text_d->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(after_c, TextAffinity::kDownstream)));
const Position before_d(text_d, 0);
EXPECT_EQ(
LocalCaretRect(text_d->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(before_d, TextAffinity::kUpstream)));
EXPECT_EQ(
LocalCaretRect(text_d->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(before_d, TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, SoftLineWrapBetweenMultipleTextNodesRtl) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo dir=rtl style='font: 10px/10px Ahem; width: 30px; "
"word-break: break-all; display: block'>"
"<span>A</span>"
"<span>B</span>"
"<span id=span-c>C</span>"
"<span id=span-d>D</span>"
"<span>E</span>"
"<span>F</span>"
"</bdo>");
const Node* text_c = GetElementById("span-c")->firstChild();
const Node* text_d = GetElementById("span-d")->firstChild();
const Position after_c(text_c, 1);
EXPECT_EQ(
LocalCaretRect(text_c->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(after_c, TextAffinity::kUpstream)));
EXPECT_EQ(
LocalCaretRect(text_d->GetLayoutObject(), PhysicalRect(29, 10, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(after_c, TextAffinity::kDownstream)));
const Position before_d(text_d, 0);
EXPECT_EQ(
LocalCaretRect(text_d->GetLayoutObject(), PhysicalRect(29, 10, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(before_d, TextAffinity::kUpstream)));
EXPECT_EQ(
LocalCaretRect(text_d->GetLayoutObject(), PhysicalRect(29, 10, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(before_d, TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, CaretRectAtBR) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div style='font: 10px/10px Ahem; width: 30px'><br>foo</div>");
const Element& br = *GetDocument().QuerySelector(AtomicString("br"));
EXPECT_EQ(LocalCaretRect(br.GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(br), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, CaretRectAtRtlBR) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo dir=rtl style='display: block; font: 10px/10px Ahem; width: 30px'>"
"<br>foo</bdo>");
const Element& br = *GetDocument().QuerySelector(AtomicString("br"));
EXPECT_EQ(LocalCaretRect(br.GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(br), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, Images) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='font: 10px/10px Ahem; width: 30px'>"
"<img id=img1 width=10px height=10px>"
"<img id=img2 width=10px height=10px>"
"</div>");
const Element& img1 = *GetElementById("img1");
EXPECT_EQ(LocalCaretRect(img1.GetLayoutObject(), PhysicalRect(0, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(img1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img1.GetLayoutObject(), PhysicalRect(9, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(img1), TextAffinity::kDownstream)));
const Element& img2 = *GetElementById("img2");
// Box-anchored LocalCaretRect is local to the box itself, instead of its
// containing block.
EXPECT_EQ(LocalCaretRect(img1.GetLayoutObject(), PhysicalRect(9, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(img2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img2.GetLayoutObject(), PhysicalRect(9, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(img2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, RtlImages) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<bdo dir=rtl style='font: 10px/10px Ahem; width: 30px; display: block'>"
"<img id=img1 width=10px height=10px>"
"<img id=img2 width=10px height=10px>"
"</bdo>");
const Element& img1 = *GetElementById("img1");
const Element& img2 = *GetElementById("img2");
// Box-anchored LocalCaretRect is local to the box itself, instead of its
// containing block.
EXPECT_EQ(LocalCaretRect(img1.GetLayoutObject(), PhysicalRect(9, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(img1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img2.GetLayoutObject(), PhysicalRect(9, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(img1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img2.GetLayoutObject(), PhysicalRect(9, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(img2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img2.GetLayoutObject(), PhysicalRect(0, 0, 1, 12)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(img2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, VerticalImage) {
// This test only records the current behavior. Future changes are allowed.
SetBodyContent(
"<div style='writing-mode: vertical-rl'>"
"<img id=img width=10px height=20px>"
"</div>");
const Element& img = *GetElementById("img");
// Box-anchored LocalCaretRect is local to the box itself, instead of its
// containing block.
EXPECT_EQ(LocalCaretRect(img.GetLayoutObject(), PhysicalRect(0, 0, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(img), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img.GetLayoutObject(), PhysicalRect(0, 19, 10, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(img), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, TextAndImageMixedHeight) {
// This test only records the current behavior. Future changes are allowed.
LoadAhem();
SetBodyContent(
"<div id=div style='font: 10px/10px Ahem; width: 30px'>"
"X"
"<img id=img width=10px height=5px style='vertical-align: text-bottom'>"
"p</div>");
const Element& img = *GetElementById("img");
const Node* text1 = img.previousSibling();
const Node* text2 = img.nextSibling();
EXPECT_EQ(LocalCaretRect(text1->GetLayoutObject(), PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(text1, 0), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(text1->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position(text1, 1), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(text1->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::BeforeNode(img), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(img.GetLayoutObject(), PhysicalRect(9, -5, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(img), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(text2->GetLayoutObject(), PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(text2->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position(text2, 1), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, FloatFirstLetter) {
LoadAhem();
InsertStyleElement("#container::first-letter{float:right}");
SetBodyContent(
"<div id=container style='font: 10px/10px Ahem; width: 40px'>foo</div>");
const Node* foo = GetElementById("container")->firstChild();
const LayoutObject* first_letter = AssociatedLayoutObjectOf(*foo, 0);
const LayoutObject* remaining_text = AssociatedLayoutObjectOf(*foo, 1);
EXPECT_EQ(LocalCaretRect(first_letter, PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(remaining_text, PhysicalRect(0, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(remaining_text, PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 2), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(remaining_text, PhysicalRect(20, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, AfterLineBreak) {
LoadAhem();
SetBodyContent("<div style='font: 10px/10px Ahem;'>foo<br><br></div>");
const Node* div = GetDocument().body()->firstChild();
const Node* foo = div->firstChild();
const Node* first_br = foo->nextSibling();
const Node* second_br = first_br->nextSibling();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(*foo), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(second_br->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(*first_br), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(second_br->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(*second_br), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, AfterLineBreakInPre) {
LoadAhem();
SetBodyContent("<pre style='font: 10px/10px Ahem;'>foo\n\n</pre>");
const Node* pre = GetDocument().body()->firstChild();
const Node* foo = pre->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 4), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 5), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, AfterLineBreakInPre2) {
LoadAhem();
// This test case simulates the rendering of the inner editor of
// <textarea>foo\n</textarea> without using text control element.
SetBodyContent("<pre style='font: 10px/10px Ahem;'>foo\n<br></pre>");
const Node* pre = GetDocument().body()->firstChild();
const Node* foo = pre->firstChild();
const Node* br = foo->nextSibling();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(br->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 4), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(br->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(*br), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, AfterLineBreakTextArea) {
LoadAhem();
SetBodyContent("<textarea style='font: 10px/10px Ahem; '>foo\n\n</textarea>");
const auto* textarea = ToTextControl(GetDocument().body()->firstChild());
const Node* inner_text = textarea->InnerEditorElement()->firstChild();
EXPECT_EQ(
LocalCaretRect(inner_text->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(inner_text, 3), TextAffinity::kDownstream)));
EXPECT_EQ(
LocalCaretRect(inner_text->GetLayoutObject(), PhysicalRect(0, 10, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(inner_text, 4), TextAffinity::kDownstream)));
const Node* hidden_br = inner_text->nextSibling();
EXPECT_EQ(
LocalCaretRect(hidden_br->GetLayoutObject(), PhysicalRect(0, 20, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(inner_text, 5), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, CollapsedSpace) {
LoadAhem();
SetBodyContent(
"<div style='font: 10px/10px Ahem;'>"
"<span>foo</span><span> </span></div>");
const Node* first_span = GetDocument().body()->firstChild()->firstChild();
const Node* foo = first_span->firstChild();
const Node* second_span = first_span->nextSibling();
const Node* white_spaces = second_span->firstChild();
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(foo, 3), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position::AfterNode(*foo), TextAffinity::kDownstream)));
// TODO(yoichio): Following should return valid rect: crbug.com/812535.
EXPECT_EQ(
LocalCaretRect(first_span->GetLayoutObject(), PhysicalRect(0, 0, 0, 0)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position::LastPositionInNode(*first_span),
TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(white_spaces, 0), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(white_spaces, 1), TextAffinity::kDownstream)));
EXPECT_EQ(LocalCaretRect(foo->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(
Position(white_spaces, 2), TextAffinity::kDownstream)));
}
TEST_F(LocalCaretRectTest, AbsoluteCaretBoundsOfWithShadowDOM) {
const char* body_content =
"<p id='host'><b slot='#one' id='one'>11</b><b name='#two' "
"id='two'>22</b></p>";
const char* shadow_content =
"<div><slot name=#two></slot><slot name=#one></slot></div>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* body = GetDocument().body();
Element* one = body->QuerySelector(AtomicString("#one"));
gfx::Rect bounds_in_dom_tree = AbsoluteCaretBoundsOf(
CreateVisiblePosition(Position(one, 0)).ToPositionWithAffinity());
gfx::Rect bounds_in_flat_tree =
AbsoluteCaretBoundsOf(CreateVisiblePosition(PositionInFlatTree(one, 0))
.ToPositionWithAffinity());
EXPECT_FALSE(bounds_in_dom_tree.IsEmpty());
EXPECT_EQ(bounds_in_dom_tree, bounds_in_flat_tree);
}
// Repro case of crbug.com/680428
TEST_F(LocalCaretRectTest, AbsoluteSelectionBoundsOfWithImage) {
SetBodyContent("<div>foo<img></div>");
Node* node = GetDocument().QuerySelector(AtomicString("img"));
gfx::Rect rect = AbsoluteSelectionBoundsOf(VisiblePosition::Create(
PositionWithAffinity(Position::LastPositionInNode(*node))));
EXPECT_FALSE(rect.IsEmpty());
}
static std::pair<PhysicalRect, PhysicalRect> GetPhysicalRects(
const Position& caret) {
const PositionWithAffinity position(caret);
const PhysicalRect& position_rect = LocalCaretRectOfPosition(position).rect;
const PositionWithAffinity visible_position(
CreateVisiblePosition(position).DeepEquivalent());
const PhysicalRect& visible_position_rect =
LocalCaretRectOfPosition(visible_position).rect;
return {position_rect, visible_position_rect};
}
TEST_F(LocalCaretRectTest, AfterLineBreakInPreBlockLTRLineLTR) {
LoadAhem();
InsertStyleElement("pre{ font: 10px/10px Ahem; width: 300px }");
const Position& caret =
SetCaretTextToBody("<pre dir='ltr'>foo\n|<bdo dir='ltr'>abc</bdo></pre>");
auto [position_rect, visible_position_rect] = GetPhysicalRects(caret);
EXPECT_EQ(PhysicalRect(0, 10, 1, 10), position_rect);
EXPECT_EQ(PhysicalRect(0, 10, 1, 10), visible_position_rect);
}
TEST_F(LocalCaretRectTest, AfterLineBreakInPreBlockLTRLineRTL) {
LoadAhem();
InsertStyleElement("pre{ font: 10px/10px Ahem; width: 300px }");
const Position& caret =
SetCaretTextToBody("<pre dir='ltr'>foo\n|<bdo dir='rtl'>abc</bdo></pre>");
auto [position_rect, visible_position_rect] = GetPhysicalRects(caret);
EXPECT_EQ(PhysicalRect(0, 10, 1, 10), position_rect);
EXPECT_EQ(PhysicalRect(0, 10, 1, 10), visible_position_rect);
}
TEST_F(LocalCaretRectTest, AfterLineBreakInPreBlockRTLLineLTR) {
LoadAhem();
InsertStyleElement("pre{ font: 10px/10px Ahem; width: 300px }");
const Position& caret =
SetCaretTextToBody("<pre dir='rtl'>foo\n|<bdo dir='ltr'>abc</bdo></pre>");
auto [position_rect, visible_position_rect] = GetPhysicalRects(caret);
EXPECT_EQ(PhysicalRect(299, 10, 1, 10), position_rect);
EXPECT_EQ(PhysicalRect(299, 10, 1, 10), visible_position_rect);
}
TEST_F(LocalCaretRectTest, AfterLineBreakInPreBlockRTLLineRTL) {
LoadAhem();
InsertStyleElement("pre{ font: 10px/10px Ahem; width: 300px }");
const Position& caret =
SetCaretTextToBody("<pre dir='rtl'>foo\n|<bdo dir='rtl'>abc</bdo></pre>");
auto [position_rect, visible_position_rect] = GetPhysicalRects(caret);
EXPECT_EQ(PhysicalRect(299, 10, 1, 10), position_rect);
EXPECT_EQ(PhysicalRect(299, 10, 1, 10), visible_position_rect);
}
// crbug.com/834686
TEST_F(LocalCaretRectTest, AfterTrimedLineBreak) {
LoadAhem();
InsertStyleElement("body { font: 10px/10px Ahem; width: 300px }");
const Position& caret = SetCaretTextToBody("<div>foo\n|</div>");
auto [position_rect, visible_position_rect] = GetPhysicalRects(caret);
EXPECT_EQ(PhysicalRect(30, 0, 1, 10), position_rect);
EXPECT_EQ(PhysicalRect(30, 0, 1, 10), visible_position_rect);
}
// See also CaretPositionTest.MultiColumnSingleText
TEST_F(LocalCaretRectTest, MultiColumnSingleText) {
LoadAhem();
InsertStyleElement(
"div { font: 10px/15px Ahem; column-count: 3; width: 20ch; }");
SetBodyInnerHTML("<div id=target>abc def ghi jkl mno pqr</div>");
// This HTML is rendered as:
// abc ghi mno
// def jkl
const auto& target = *GetElementById("target");
const Text& text = *To<Text>(target.firstChild());
// Note: Legacy layout caret rect is in stitch coordinate space == as if
// columns are laid out vertically.
// NG caret rect is in relative to containing box fragment.
// "abc " in column 1
EXPECT_EQ(PhysicalRect(0, 2, 1, 10),
LocalCaretRectOf(Position(text, 0)).rect);
EXPECT_EQ(PhysicalRect(10, 2, 1, 10),
LocalCaretRectOf(Position(text, 1)).rect);
EXPECT_EQ(PhysicalRect(20, 2, 1, 10),
LocalCaretRectOf(Position(text, 2)).rect);
EXPECT_EQ(PhysicalRect(30, 2, 1, 10),
LocalCaretRectOf(Position(text, 3)).rect);
// "def " in column 1
EXPECT_EQ(PhysicalRect(0, 17, 1, 10),
LocalCaretRectOf(Position(text, 4)).rect);
EXPECT_EQ(PhysicalRect(10, 17, 1, 10),
LocalCaretRectOf(Position(text, 5)).rect);
EXPECT_EQ(PhysicalRect(20, 17, 1, 10),
LocalCaretRectOf(Position(text, 6)).rect);
EXPECT_EQ(PhysicalRect(30, 17, 1, 10),
LocalCaretRectOf(Position(text, 7)).rect);
// "ghi " in column 2
EXPECT_EQ(PhysicalRect(0, 2, 1, 10),
LocalCaretRectOf(Position(text, 8)).rect);
EXPECT_EQ(PhysicalRect(10, 2, 1, 10),
LocalCaretRectOf(Position(text, 9)).rect);
EXPECT_EQ(PhysicalRect(20, 2, 1, 10),
LocalCaretRectOf(Position(text, 10)).rect);
EXPECT_EQ(PhysicalRect(30, 2, 1, 10),
LocalCaretRectOf(Position(text, 11)).rect);
// "jkl " in column 2
EXPECT_EQ(PhysicalRect(0, 17, 1, 10),
LocalCaretRectOf(Position(text, 12)).rect);
EXPECT_EQ(PhysicalRect(10, 17, 1, 10),
LocalCaretRectOf(Position(text, 13)).rect);
EXPECT_EQ(PhysicalRect(20, 17, 1, 10),
LocalCaretRectOf(Position(text, 14)).rect);
EXPECT_EQ(PhysicalRect(30, 17, 1, 10),
LocalCaretRectOf(Position(text, 15)).rect);
// "mno " in column 3
EXPECT_EQ(PhysicalRect(0, 2, 1, 10),
LocalCaretRectOf(Position(text, 16)).rect);
EXPECT_EQ(PhysicalRect(10, 2, 1, 10),
LocalCaretRectOf(Position(text, 17)).rect);
EXPECT_EQ(PhysicalRect(20, 2, 1, 10),
LocalCaretRectOf(Position(text, 18)).rect);
EXPECT_EQ(PhysicalRect(30, 2, 1, 10),
LocalCaretRectOf(Position(text, 19)).rect);
// "pqr" in column 3
EXPECT_EQ(PhysicalRect(0, 17, 1, 10),
LocalCaretRectOf(Position(text, 20)).rect);
EXPECT_EQ(PhysicalRect(10, 17, 1, 10),
LocalCaretRectOf(Position(text, 21)).rect);
EXPECT_EQ(PhysicalRect(20, 17, 1, 10),
LocalCaretRectOf(Position(text, 22)).rect);
EXPECT_EQ(PhysicalRect(30, 17, 1, 10),
LocalCaretRectOf(Position(text, 23)).rect);
}
TEST_F(LocalCaretRectTest, UnicodeBidiPlaintextWithDifferentBlockDirection) {
LoadAhem();
InsertStyleElement("div { font: 10px/10px Ahem; unicode-bidi: plaintext }");
const Position position = SetCaretTextToBody("<div dir='rtl'>|abc</div>");
const PhysicalRect caret_rect =
LocalCaretRectOfPosition(PositionWithAffinity(position)).rect;
EXPECT_EQ(PhysicalRect(0, 0, 1, 10), caret_rect);
}
// http://crbug.com/835779
TEST_F(LocalCaretRectTest, NextLineWithoutLeafChild) {
LoadAhem();
InsertStyleElement("div { font: 10px/10px Ahem; width: 30px }");
SetBodyContent(
"<div>"
"<br>"
"<span style=\"border-left: 50px solid\"></span>"
"foo"
"</div>");
const Element& br = *GetDocument().QuerySelector(AtomicString("br"));
EXPECT_EQ(
PhysicalRect(50, 10, 1, 10),
LocalCaretRectOfPosition(PositionWithAffinity(Position::AfterNode(br)))
.rect);
}
TEST_F(LocalCaretRectTest, BidiTextWithImage) {
LoadAhem();
InsertStyleElement(
"div { font: 10px/10px Ahem; width: 30px }"
"img { width: 10px; height: 10px; vertical-align: bottom }");
SetBodyContent("<div dir=rtl>X<img id=image>Y</div>");
const Element& image = *GetElementById("image");
const LayoutObject* image_layout = image.GetLayoutObject();
const LayoutObject* text_before = image.previousSibling()->GetLayoutObject();
EXPECT_EQ(LocalCaretRect(text_before, PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position::BeforeNode(image))));
EXPECT_EQ(LocalCaretRect(image_layout, PhysicalRect(9, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(Position::AfterNode(image))));
}
// https://crbug.com/876044
TEST_F(LocalCaretRectTest, RtlMeterNoCrash) {
SetBodyContent("foo<meter dir=rtl></meter>");
const Position position = Position::LastPositionInNode(*GetDocument().body());
// Shouldn't crash inside
const LocalCaretRect local_caret_rect =
LocalCaretRectOfPosition(PositionWithAffinity(position));
EXPECT_EQ(
GetDocument().QuerySelector(AtomicString("meter"))->GetLayoutObject(),
local_caret_rect.layout_object);
}
// https://crbug.com/883044
TEST_F(LocalCaretRectTest, AfterCollapsedWhiteSpaceInRTLText) {
LoadAhem();
InsertStyleElement(
"bdo { display: block; font: 10px/10px Ahem; width: 100px }");
const Position position =
SetCaretTextToBody("<bdo dir=rtl>AAA |BBB<span>CCC</span></bdo>");
const Node* text = position.AnchorNode();
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(60, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionWithAffinity(position, TextAffinity::kDownstream)));
}
// https://crbug.com/936988
TEST_F(LocalCaretRectTest, AfterIneditableInline) {
LoadAhem();
InsertStyleElement("div { font: 10px/10px Ahem }");
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">foo</span></div>");
const Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Node* text = div->firstChild()->firstChild();
const Position position = Position::LastPositionInNode(*div);
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(30, 0, 1, 10)),
LocalCaretRectOfPosition(PositionWithAffinity(position)));
}
// http://crbug.com/688015
TEST_F(LocalCaretRectTest, LocalCaretAtBeginningOfNonEditable) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding-left: 10px; font: 10px/10px Ahem }"
"span { padding-left: 15px }");
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">foo</span></div>");
const Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Element* span = To<Element>(div->firstChild());
const Node* text = span->firstChild();
const Position& position = Position::FirstPositionInNode(*div);
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(25, 0, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(LocalCaretRect(span->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
// http://crbug.com/1456664
TEST_F(LocalCaretRectTest,
LocalCaretAtBeginningOfNonEditableWithCollapsedWhitespace) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding-left: 10px; font: 10px/10px Ahem }"
"span { padding-left: 15px }");
// Note the space before the span!
SetBodyContent(
"<div contenteditable> <span contenteditable=\"false\">foo</span></div>");
Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Element* span = div->firstElementChild();
const Node* text = span->firstChild();
const Position& position = Position::FirstPositionInNode(*div);
EXPECT_EQ(LocalCaretRect(text->GetLayoutObject(), PhysicalRect(25, 0, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(LocalCaretRect(span->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
// http://crbug.com/1384470
TEST_F(LocalCaretRectTest, LocalCaretAtBeginningOfNonEditableWithSvg) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding-left: 10px; font: 10px/10px Ahem }"
"span { padding-left: 15px }"
"svg { vertical-align: text-bottom; }");
// Note the space before the span!
SetBodyContent(
"<div contenteditable> <span contenteditable=\"false\">"
"<svg width=\"30\" height=\"10\"></svg></span></div>");
Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Element* span = div->firstElementChild();
const Position& position = Position::FirstPositionInNode(*div);
// TODO(abotella): Should be (0, 0), with the svg as the layout object.
EXPECT_EQ(LocalCaretRect(div->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(LocalCaretRect(span->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
// http://crbug.com/688015
TEST_F(LocalCaretRectTest, LocalCaretAtBeginningOfNonEditableInFlatTree) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding-left: 10px; font: 10px/10px Ahem }"
"span { padding-left: 15px }");
const char* body_content =
"<div contenteditable><div id='host'>"
"<span slot='#one'>foo1</span><span>foo3</span></div></div>";
const char* shadow_content =
"<slot name=#one></slot>"
"<span contenteditable='false' id='foo2'>foo2</span>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
const Element* target = GetDocument().getElementById(AtomicString("host"));
const Element* foo1_span = To<Element>(target->firstChild());
const Node* foo1 = foo1_span->firstChild();
const Position& position = Position::FirstPositionInNode(*target);
EXPECT_EQ(LocalCaretRect(foo1->GetLayoutObject(), PhysicalRect(25, 0, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
// TODO(abotella): The layout object should probably be |foo1_span|'s.
// TODO(abotella): We should avoid using LayoutBox::LocalCaretRect in
// LayoutNG.
EXPECT_EQ(
LocalCaretRect(target->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
const PositionInFlatTree& position_in_flat_tree =
PositionInFlatTree::FirstPositionInNode(*target);
EXPECT_EQ(LocalCaretRect(foo1->GetLayoutObject(), PhysicalRect(25, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionInFlatTreeWithAffinity(position_in_flat_tree),
kCanCrossEditingBoundary));
// TODO(abotella): The layout object should probably be |foo1_span|'s.
// TODO(abotella): We should avoid using LayoutBox::LocalCaretRect in
// LayoutNG.
EXPECT_EQ(
LocalCaretRect(target->GetLayoutObject(), PhysicalRect(10, 0, 1, 10)),
LocalCaretRectOfPosition(
PositionInFlatTreeWithAffinity(position_in_flat_tree),
kCannotCrossEditingBoundary));
}
// http://crbug.com/688015
TEST_F(LocalCaretRectTest, LocalCaretAtEndOfNonEditable) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding: 10px; font: 10px/10px Ahem }"
"span { padding: 15px }");
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">foo</span></div>");
const Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Element* span = To<Element>(div->firstChild());
const Node* text = span->firstChild();
const Position& position = Position::LastPositionInNode(*div);
EXPECT_EQ(
LocalCaretRect(text->GetLayoutObject(), PhysicalRect(55, 10, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(
LocalCaretRect(span->GetLayoutObject(), PhysicalRect(69, 10, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
// http://crbug.com/1456664
TEST_F(LocalCaretRectTest,
LocalCaretAtEndOfNonEditableWithCollapsedWhitespace) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding: 10px; font: 10px/10px Ahem }"
"span { padding: 15px }");
// Note the space after the span!
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">foo</span> </div>");
Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Element* span = To<Element>(div->firstChild());
const Node* text = span->firstChild();
const Position& position = Position::LastPositionInNode(*div);
EXPECT_EQ(
LocalCaretRect(text->GetLayoutObject(), PhysicalRect(55, 10, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(
LocalCaretRect(span->GetLayoutObject(), PhysicalRect(69, 10, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
// http://crbug.com/1384470
TEST_F(LocalCaretRectTest, LocalCaretAtEndOfNonEditableWithSvg) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding: 10px; font: 10px/10px Ahem }"
"span { padding: 15px }"
"svg { vertical-align: text-bottom; }");
// Note the space after the span!
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">"
"<svg width=\"30\" height=\"10\"></svg></span> </div>");
Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Element* span = To<Element>(div->firstChild());
const Element* svg = To<Element>(span->firstChild());
const Position& position = Position::LastPositionInNode(*div);
EXPECT_EQ(LocalCaretRect(svg->GetLayoutObject(), PhysicalRect(29, 0, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(
LocalCaretRect(span->GetLayoutObject(), PhysicalRect(69, 10, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
// http://crbug.com/688015
TEST_F(LocalCaretRectTest, LocalCaretAtEndOfNonEditableInFlatTree) {
LoadAhem();
InsertStyleElement(
"div { width: 70px; padding: 10px; font: 10px/10px Ahem }"
"span { padding: 15px }");
const char* body_content =
"<div contenteditable><div id='host'>"
"<span slot='#one'>foo1</span><span>foo3</span></div></div>";
const char* shadow_content =
"<slot name=#one></slot>"
"<span contenteditable='false' id='foo2'>foo2</span>";
SetBodyContent(body_content);
auto* shadow_root = SetShadowContent(shadow_content, "host");
const Element* target = GetDocument().getElementById(AtomicString("host"));
const Element* foo2_span = shadow_root->getElementById(AtomicString("foo2"));
const Node* foo2 = foo2_span->firstChild();
const Position& position = Position::LastPositionInNode(*target);
// TODO(abotella): The coordinates should be (50, 20) and the layout object
// should probably be |foo2|'s.
// TODO(abotella): We should avoid using LayoutBox::LocalCaretRect in
// LayoutNG.
EXPECT_EQ(
LocalCaretRect(target->GetLayoutObject(), PhysicalRect(99, 10, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
// TODO(abotella): The coordinates should be (49, 20) and the layout object
// should probably be |foo2_span|'s.
// TODO(abotella): We should avoid using LayoutBox::LocalCaretRect in
// LayoutNG.
EXPECT_EQ(
LocalCaretRect(target->GetLayoutObject(), PhysicalRect(99, 10, 1, 10)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
const PositionInFlatTree& position_in_flat_tree =
PositionInFlatTree::LastPositionInNode(*target);
EXPECT_EQ(
LocalCaretRect(foo2->GetLayoutObject(), PhysicalRect(50, 20, 1, 10)),
LocalCaretRectOfPosition(
PositionInFlatTreeWithAffinity(position_in_flat_tree),
kCanCrossEditingBoundary));
// 50 rather than 49 and a |foo2|'s layout object rather than |foo2_span|'s
// because |foo2_span| is a culled inline. This is probably fine, because
// the 1px difference isn't appreciable in practice.
EXPECT_EQ(
LocalCaretRect(foo2->GetLayoutObject(), PhysicalRect(50, 20, 1, 10)),
LocalCaretRectOfPosition(
PositionInFlatTreeWithAffinity(position_in_flat_tree),
kCannotCrossEditingBoundary));
}
// http://crbug.com/688015
TEST_F(LocalCaretRectTest, AbsoluteCaretAtEndOfNonEditable) {
LoadAhem();
InsertStyleElement(
"body { margin: 5px; }"
"div { width: 70px; padding-left: 10px; font: 10px/10px Ahem }"
"span { padding-left: 15px }");
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">foo</span></div>");
const Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Position& position = Position::LastPositionInNode(*div);
EXPECT_EQ("60,5 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(position), nullptr,
kCanCrossEditingBoundary)
.ToString());
EXPECT_EQ("59,5 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(position), nullptr,
kCannotCrossEditingBoundary)
.ToString());
}
// http://crbug.com/688015
TEST_F(LocalCaretRectTest, AbsoluteCaretAtBeginningOfNonEditable) {
LoadAhem();
InsertStyleElement(
"body { margin: 5px; }"
"div { width: 70px; padding-left: 10px; font: 10px/10px Ahem }"
"span { padding-left: 15px }");
SetBodyContent(
"<div contenteditable><span contenteditable=\"false\">foo</span></div>");
const Element* div = GetDocument().QuerySelector(AtomicString("div"));
const Position& position = Position::FirstPositionInNode(*div);
EXPECT_EQ("30,5 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(position), nullptr,
kCanCrossEditingBoundary)
.ToString());
EXPECT_EQ("15,5 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(position), nullptr,
kCannotCrossEditingBoundary)
.ToString());
}
// https://crbug.com/1155399
TEST_F(LocalCaretRectTest, OptionWithDisplayContents) {
LoadAhem();
InsertStyleElement(
"body { font: 10px/10px Ahem; width: 300px }"
"option { display: contents; }");
SetBodyContent("<option>a</option>");
const Element* body = GetDocument().body();
const Element* option = GetDocument().QuerySelector(AtomicString("option"));
LocalCaretRect empty;
LocalCaretRect start(body->GetLayoutObject(), PhysicalRect(0, 0, 1, 10));
LocalCaretRect end(body->GetLayoutObject(), PhysicalRect(299, 0, 1, 10));
// LocalCaretRectOfPosition shouldn't crash
for (const Position& p : {Position::BeforeNode(*body), Position(body, 0)})
EXPECT_EQ(start, LocalCaretRectOfPosition(PositionWithAffinity(p)));
for (const Position& p :
{Position::BeforeNode(*option), Position(option, 0), Position(option, 1),
Position::LastPositionInNode(*option), Position::AfterNode(*option)})
EXPECT_EQ(empty, LocalCaretRectOfPosition(PositionWithAffinity(p)));
for (const Position& p :
{Position(body, 1), Position::LastPositionInNode(*body),
Position::AfterNode(*body)})
EXPECT_EQ(end, LocalCaretRectOfPosition(PositionWithAffinity(p)));
}
TEST_F(LocalCaretRectTest, TextCombineOneTextNode) {
LoadAhem();
InsertStyleElement(
"div {"
" font: 100px/110px Ahem;"
" writing-mode: vertical-rl;"
"}"
"tcy { text-combine-upright: all; }");
SetBodyInnerHTML("<div>a<tcy id=target>01234</tcy>b</div>");
// LayoutNGBlockFlow {HTML} at (0,0) size 800x600
// LayoutNGBlockFlow {BODY} at (8,8) size 784x584
// LayoutNGBlockFlow {DIV} at (0,0) size 110x300
// LayoutText {#text} at (5,0) size 100x100
// text run at (5,0) width 100: "a"
// LayoutInline {TCY} at (5,100) size 100x100
// LayoutTextCombine (anonymous) at (5,100) size 100x100
// LayoutText {#text} at (-5,0) size 110x100
// text run at (0,0) width 500: "01234"
// LayoutText {#text} at (5,200) size 100x100
// text run at (5,200) width 100: "b"
const auto& target = *GetElementById("target");
const auto& text_a = *To<Text>(target.previousSibling());
const auto& text_01234 = *To<Text>(target.firstChild());
const auto& text_b = *To<Text>(target.nextSibling());
// text_a
EXPECT_EQ(
LocalCaretRect(text_a.GetLayoutObject(), PhysicalRect(5, 0, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_a, 0))));
EXPECT_EQ(
LocalCaretRect(text_a.GetLayoutObject(), PhysicalRect(5, 100, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_a, 1))));
// text_01234
EXPECT_EQ(
LocalCaretRect(text_01234.GetLayoutObject(), PhysicalRect(0, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_01234, 0))));
EXPECT_EQ(
LocalCaretRect(text_01234.GetLayoutObject(), PhysicalRect(17, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_01234, 1))));
EXPECT_EQ(
LocalCaretRect(text_01234.GetLayoutObject(), PhysicalRect(39, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_01234, 2))));
EXPECT_EQ(
LocalCaretRect(text_01234.GetLayoutObject(), PhysicalRect(61, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_01234, 3))));
EXPECT_EQ(
LocalCaretRect(text_01234.GetLayoutObject(), PhysicalRect(83, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_01234, 4))));
EXPECT_EQ(
LocalCaretRect(text_01234.GetLayoutObject(), PhysicalRect(99, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_01234, 5))));
// text_b
EXPECT_EQ(
LocalCaretRect(text_b.GetLayoutObject(), PhysicalRect(5, 200, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_b, 0))));
EXPECT_EQ(
LocalCaretRect(text_b.GetLayoutObject(), PhysicalRect(5, 299, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_b, 1))));
}
TEST_F(LocalCaretRectTest, TextCombineTwoTextNodes) {
LoadAhem();
InsertStyleElement(
"div {"
" font: 100px/110px Ahem;"
" writing-mode: vertical-rl;"
"}"
"tcy { text-combine-upright: all; }");
SetBodyInnerHTML("<div>a<tcy id=target>012<!-- -->34</tcy>b</div>");
// LayoutNGBlockFlow {HTML} at (0,0) size 800x600
// LayoutNGBlockFlow {BODY} at (8,8) size 784x584
// LayoutNGBlockFlow {DIV} at (0,0) size 110x300
// LayoutText {#text} at (5,0) size 100x100
// text run at (5,0) width 100: "a"
// LayoutInline {TCY} at (5,100) size 100x100
// LayoutTextCombine (anonymous) at (5,100) size 100x100
// LayoutText {#text} at (-5,0) size 66x100
// text run at (0,0) width 300: "012"
// LayoutText {#text} at (61,0) size 44x100
// text run at (300,0) width 200: "34"
// LayoutText {#text} at (5,200) size 100x100
// text run at (5,200) width 100: "b"
const auto& target = *GetElementById("target");
const auto& text_a = *To<Text>(target.previousSibling());
const auto& text_012 = *To<Text>(target.firstChild());
const auto& text_34 = *To<Text>(target.lastChild());
const auto& text_b = *To<Text>(target.nextSibling());
// text_a
EXPECT_EQ(
LocalCaretRect(text_a.GetLayoutObject(), PhysicalRect(5, 0, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_a, 0))));
EXPECT_EQ(
LocalCaretRect(text_a.GetLayoutObject(), PhysicalRect(5, 100, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_a, 1))));
// text_012
EXPECT_EQ(
LocalCaretRect(text_012.GetLayoutObject(), PhysicalRect(0, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_012, 0))));
EXPECT_EQ(
LocalCaretRect(text_012.GetLayoutObject(), PhysicalRect(17, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_012, 1))));
EXPECT_EQ(
LocalCaretRect(text_012.GetLayoutObject(), PhysicalRect(39, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_012, 2))));
EXPECT_EQ(
LocalCaretRect(text_012.GetLayoutObject(), PhysicalRect(61, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_012, 3))));
// text_34
EXPECT_EQ(
LocalCaretRect(text_34.GetLayoutObject(), PhysicalRect(61, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_34, 0))));
EXPECT_EQ(
LocalCaretRect(text_34.GetLayoutObject(), PhysicalRect(83, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_34, 1))));
EXPECT_EQ(
LocalCaretRect(text_34.GetLayoutObject(), PhysicalRect(99, 0, 1, 100)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_34, 2))));
// text_b
EXPECT_EQ(
LocalCaretRect(text_b.GetLayoutObject(), PhysicalRect(5, 200, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_b, 0))));
EXPECT_EQ(
LocalCaretRect(text_b.GetLayoutObject(), PhysicalRect(5, 299, 100, 1)),
LocalCaretRectOfPosition(PositionWithAffinity(Position(text_b, 1))));
}
TEST_F(LocalCaretRectTest,
LocalCaretAtStartOfNonEditableWithDifferentFontSizes) {
LoadAhem();
InsertStyleElement(
"div { width: 110px; padding: 15px; font: 15px/15px Ahem }"
"span { padding: 10px; font: 10px/10px Ahem }");
SetBodyContent(
"<div contenteditable><span contenteditable=false>foo</span> bar</div>");
const Element& div = *GetDocument().QuerySelector(AtomicString("div"));
const Element& span = *To<Element>(div.firstChild());
const Text& text = *To<Text>(span.firstChild());
const Position& position = Position::FirstPositionInNode(div);
EXPECT_EQ(LocalCaretRect(text.GetLayoutObject(), PhysicalRect(25, 19, 1, 10)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(LocalCaretRect(span.GetLayoutObject(), PhysicalRect(15, 15, 1, 15)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
TEST_F(LocalCaretRectTest, LocalCaretAtEndOfNonEditableWithDifferentFontSizes) {
LoadAhem();
InsertStyleElement(
"div { width: 120px; padding: 10px; font: 10px/10px Ahem }"
"span { padding: 15px; font: 15px/15px Ahem }");
SetBodyContent(
"<div contenteditable>foo <span contenteditable=false>bar</span></div>");
const Element& div = *GetDocument().QuerySelector(AtomicString("div"));
const Element& span = *To<Element>(div.lastChild());
const Text& text = *To<Text>(span.firstChild());
const Position& position = Position::LastPositionInNode(div);
EXPECT_EQ(
LocalCaretRect(text.GetLayoutObject(), PhysicalRect(110, 10, 1, 15)),
LocalCaretRectOf(position, kCanCrossEditingBoundary));
EXPECT_EQ(
LocalCaretRect(span.GetLayoutObject(), PhysicalRect(124, 10, 1, 15)),
LocalCaretRectOf(position, kCannotCrossEditingBoundary));
}
TEST_F(LocalCaretRectTest, LocalCaretInSvgTextWithFontScaling) {
LoadAhem();
InsertStyleElement(
"body { margin: 0 }"
"svg { width: 100% }"
"text { font: 10px/10px Ahem }");
SetBodyContent(
"<svg viewBox='0 0 160 120'><text x='10' y='10'>Text</text></svg>");
const Text& text = To<Text>(
*GetDocument().QuerySelector(AtomicString("text"))->firstChild());
EXPECT_EQ(LocalCaretRect(text.GetLayoutObject(), PhysicalRect(10, 2, 1, 10)),
LocalCaretRectOf(Position(text, 0)));
EXPECT_EQ(LocalCaretRect(text.GetLayoutObject(), PhysicalRect(20, 2, 1, 10)),
LocalCaretRectOf(Position(text, 1)));
EXPECT_EQ(LocalCaretRect(text.GetLayoutObject(), PhysicalRect(30, 2, 1, 10)),
LocalCaretRectOf(Position(text, 2)));
}
TEST_F(LocalCaretRectTest, AbsoluteCaretAtStartOrEndOfNonEditableBidi) {
LoadAhem();
InsertStyleElement(
"body { margin: 0 }"
"div { width: 100px; padding: 10px; text-align: center; font: 10px/10px "
"Ahem }"
"span { background-color: red }");
SetBodyContent(
"<div dir=rtl contenteditable><span contenteditable=false>"
"<bdo dir=ltr>abc</bdo> <bdo dir=rtl>ABC</bdo></span></div>");
const Element& div = *GetDocument().QuerySelector(AtomicString("div"));
const Position& startPosition = Position::FirstPositionInNode(div);
EXPECT_EQ("95,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(startPosition), nullptr,
kCanCrossEditingBoundary)
.ToString());
EXPECT_EQ("94,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(startPosition), nullptr,
kCannotCrossEditingBoundary)
.ToString());
const Position& endPosition = Position::LastPositionInNode(div);
EXPECT_EQ("25,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(endPosition), nullptr,
kCanCrossEditingBoundary)
.ToString());
EXPECT_EQ("25,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(endPosition), nullptr,
kCannotCrossEditingBoundary)
.ToString());
}
TEST_F(LocalCaretRectTest, AbsoluteCaretAtStartOrEndOfNonEditableBidiCulled) {
LoadAhem();
InsertStyleElement(
"body { margin: 0 }"
"div { width: 100px; padding: 10px; text-align: center; font: 10px/10px "
"Ahem }");
SetBodyContent(
"<div dir=rtl contenteditable><span contenteditable=false>"
"<bdo dir=ltr>abc</bdo> <bdo dir=rtl>ABC</bdo></span></div>");
const Element& div = *GetDocument().QuerySelector(AtomicString("div"));
const Position& startPosition = Position::FirstPositionInNode(div);
EXPECT_EQ("95,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(startPosition), nullptr,
kCanCrossEditingBoundary)
.ToString());
// TODO(abotella): Should this be 95,10?
EXPECT_EQ("65,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(startPosition), nullptr,
kCannotCrossEditingBoundary)
.ToString());
const Position& endPosition = Position::LastPositionInNode(div);
EXPECT_EQ("25,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(endPosition), nullptr,
kCanCrossEditingBoundary)
.ToString());
EXPECT_EQ("25,10 1x10",
AbsoluteCaretBoundsOf(PositionWithAffinity(endPosition), nullptr,
kCannotCrossEditingBoundary)
.ToString());
}
} // namespace blink