blob: 39a434a9602269c29dc496756a368f6a65e9c159 [file] [log] [blame]
// Copyright 2015 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/editing/visible_position.h"
#include "third_party/blink/renderer/core/css/css_style_declaration.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
namespace blink {
class VisiblePositionTest : public EditingTestBase {};
// Helper class to run the same test code with and without LayoutNG
class ParameterizedVisiblePositionTest
: public testing::WithParamInterface<bool>,
private ScopedLayoutNGForTest,
public VisiblePositionTest {
protected:
ParameterizedVisiblePositionTest() : ScopedLayoutNGForTest(GetParam()) {}
bool LayoutNGEnabled() const {
return RuntimeEnabledFeatures::LayoutNGEnabled();
}
};
INSTANTIATE_TEST_SUITE_P(All,
ParameterizedVisiblePositionTest,
testing::Bool());
TEST_F(VisiblePositionTest, EmptyEditable) {
SetBodyContent("<div id=target contenteditable></div>");
const Element& target = *GetElementById("target");
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position(target, 0)).DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position::FirstPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position::LastPositionInNode(target))
.DeepEquivalent());
}
TEST_F(VisiblePositionTest, EmptyEditableWithBlockChild) {
// Note: Placeholder <br> is needed to have non-zero editable.
SetBodyContent("<div id=target contenteditable><div><br></div></div>");
const Element& target = *GetElementById("target");
const Node& div = *target.firstChild();
const Node& br = *div.firstChild();
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 0)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::FirstPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::LastPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 1)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(div, 0)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::BeforeNode(div)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::AfterNode(div)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::BeforeNode(br)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::AfterNode(br)).DeepEquivalent());
}
TEST_F(VisiblePositionTest, EmptyEditableWithInlineChild) {
SetBodyContent("<div id=target contenteditable><span></span></div>");
const Element& target = *GetElementById("target");
const Node& span = *target.firstChild();
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position(target, 0)).DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position::FirstPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position::LastPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position(target, 1)).DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position(span, 0)).DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position::BeforeNode(span)).DeepEquivalent());
EXPECT_EQ(Position(target, 0),
CreateVisiblePosition(Position::AfterNode(span)).DeepEquivalent());
}
TEST_F(VisiblePositionTest, PlaceholderBR) {
SetBodyContent("<div id=target><br id=br></div>");
const Element& target = *GetElementById("target");
const Element& br = *GetElementById("br");
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 0)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::FirstPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::LastPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 1)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(br, 0)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::BeforeNode(br)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::AfterNode(br)).DeepEquivalent());
}
TEST_F(VisiblePositionTest, PlaceholderBRWithCollapsedSpace) {
SetBodyContent("<div id=target> <br id=br> </div>");
const Element& target = *GetElementById("target");
const Element& br = *GetElementById("br");
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 0)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::FirstPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::LastPositionInNode(target))
.DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 1)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(target, 2)).DeepEquivalent());
EXPECT_EQ(
Position::BeforeNode(br),
CreateVisiblePosition(Position(target.firstChild(), 0)).DeepEquivalent());
EXPECT_EQ(
Position::BeforeNode(br),
CreateVisiblePosition(Position(target.firstChild(), 1)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position(br, 0)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::BeforeNode(br)).DeepEquivalent());
EXPECT_EQ(Position::BeforeNode(br),
CreateVisiblePosition(Position::AfterNode(br)).DeepEquivalent());
EXPECT_EQ(
Position::BeforeNode(br),
CreateVisiblePosition(Position(target.lastChild(), 0)).DeepEquivalent());
EXPECT_EQ(
Position::BeforeNode(br),
CreateVisiblePosition(Position(target.lastChild(), 1)).DeepEquivalent());
}
#if DCHECK_IS_ON()
TEST_F(VisiblePositionTest, NullIsValid) {
EXPECT_TRUE(VisiblePosition().IsValid());
}
TEST_F(VisiblePositionTest, NonNullIsValidBeforeMutation) {
SetBodyContent("<p>one</p>");
Element* paragraph = GetDocument().QuerySelector("p");
Position position(paragraph->firstChild(), 1);
EXPECT_TRUE(CreateVisiblePosition(position).IsValid());
}
TEST_F(VisiblePositionTest, NonNullInvalidatedAfterDOMChange) {
SetBodyContent("<p>one</p>");
Element* paragraph = GetDocument().QuerySelector("p");
Position position(paragraph->firstChild(), 1);
VisiblePosition null_visible_position;
VisiblePosition non_null_visible_position = CreateVisiblePosition(position);
Element* div = GetDocument().CreateRawElement(html_names::kDivTag);
GetDocument().body()->AppendChild(div);
EXPECT_TRUE(null_visible_position.IsValid());
EXPECT_FALSE(non_null_visible_position.IsValid());
UpdateAllLifecyclePhasesForTest();
// Invalid VisiblePosition can never become valid again.
EXPECT_FALSE(non_null_visible_position.IsValid());
}
TEST_F(VisiblePositionTest, NonNullInvalidatedAfterStyleChange) {
SetBodyContent("<div>one</div><p>two</p>");
Element* paragraph = GetDocument().QuerySelector("p");
Element* div = GetDocument().QuerySelector("div");
Position position(paragraph->firstChild(), 1);
VisiblePosition visible_position1 = CreateVisiblePosition(position);
div->style()->setProperty(GetDocument().GetExecutionContext(), "color", "red",
"important", ASSERT_NO_EXCEPTION);
EXPECT_FALSE(visible_position1.IsValid());
UpdateAllLifecyclePhasesForTest();
VisiblePosition visible_position2 = CreateVisiblePosition(position);
div->style()->setProperty(GetDocument().GetExecutionContext(), "display",
"none", "important", ASSERT_NO_EXCEPTION);
EXPECT_FALSE(visible_position2.IsValid());
UpdateAllLifecyclePhasesForTest();
// Invalid VisiblePosition can never become valid again.
EXPECT_FALSE(visible_position1.IsValid());
EXPECT_FALSE(visible_position2.IsValid());
}
#endif
TEST_F(VisiblePositionTest, NormalizationAroundLineBreak) {
LoadAhem();
InsertStyleElement(
"div {"
"width: 5.5ch;"
"font: 10px/10px Ahem;"
"word-wrap: break-word;"
"}");
SetBodyContent(
"<div>line1line2</div>"
"<div>line1<br>line2</div>"
"<div>line1<wbr>line2</div>"
"<div>line1<span></span>line2</div>"
"<div>line1<span></span><span></span>line2</div>");
StaticElementList* tests = GetDocument().QuerySelectorAll("div");
for (unsigned i = 0; i < tests->length(); ++i) {
Element* test = tests->item(i);
Node* node1 = test->firstChild();
Node* node2 = test->lastChild();
PositionWithAffinity line1_end(Position(node1, 5), TextAffinity::kUpstream);
PositionWithAffinity line2_start(Position(node2, node1 == node2 ? 5 : 0),
TextAffinity::kDownstream);
PositionWithAffinity line1_end_normalized =
CreateVisiblePosition(line1_end).ToPositionWithAffinity();
PositionWithAffinity line2_start_normalized =
CreateVisiblePosition(line2_start).ToPositionWithAffinity();
EXPECT_FALSE(InSameLine(line1_end, line2_start));
EXPECT_FALSE(InSameLine(line1_end_normalized, line2_start_normalized));
EXPECT_TRUE(InSameLine(line1_end, line1_end_normalized));
EXPECT_TRUE(InSameLine(line2_start, line2_start_normalized));
}
}
TEST_P(ParameterizedVisiblePositionTest, SpacesAroundLineBreak) {
// Narrow <body> forces "a" and "b" to be in different lines.
InsertStyleElement("body { width: 1px }");
{
SetBodyContent("a b");
Node* ab = GetDocument().body()->firstChild();
EXPECT_EQ(Position(ab, 0),
CreateVisiblePosition(Position(ab, 0)).DeepEquivalent());
EXPECT_EQ(Position(ab, 1),
CreateVisiblePosition(Position(ab, 1)).DeepEquivalent());
EXPECT_EQ(Position(ab, 2),
CreateVisiblePosition(Position(ab, 2)).DeepEquivalent());
}
{
SetBodyContent("a<span> b</span>");
Node* a = GetDocument().body()->firstChild();
Node* b = a->nextSibling()->firstChild();
EXPECT_EQ(Position(a, 0),
CreateVisiblePosition(Position(a, 0)).DeepEquivalent());
EXPECT_EQ(Position(a, 1),
CreateVisiblePosition(Position(a, 1)).DeepEquivalent());
EXPECT_EQ(Position(a, 1),
CreateVisiblePosition(Position(b, 0)).DeepEquivalent());
EXPECT_EQ(Position(LayoutNGEnabled() ? b : a, 1),
CreateVisiblePosition(Position(b, 1)).DeepEquivalent());
EXPECT_EQ(Position(b, 2),
CreateVisiblePosition(Position(b, 2)).DeepEquivalent());
}
{
SetBodyContent("<span>a</span> b");
Node* b = GetDocument().body()->lastChild();
Node* a = b->previousSibling()->firstChild();
EXPECT_EQ(Position(a, 0),
CreateVisiblePosition(Position(a, 0)).DeepEquivalent());
EXPECT_EQ(Position(a, 1),
CreateVisiblePosition(Position(a, 1)).DeepEquivalent());
EXPECT_EQ(Position(a, 1),
CreateVisiblePosition(Position(b, 0)).DeepEquivalent());
EXPECT_EQ(Position(LayoutNGEnabled() ? b : a, 1),
CreateVisiblePosition(Position(b, 1)).DeepEquivalent());
EXPECT_EQ(Position(b, 2),
CreateVisiblePosition(Position(b, 2)).DeepEquivalent());
}
{
SetBodyContent("a <span>b</span>");
Node* a = GetDocument().body()->firstChild();
Node* b = a->nextSibling()->firstChild();
EXPECT_EQ(Position(a, 0),
CreateVisiblePosition(Position(a, 0)).DeepEquivalent());
EXPECT_EQ(Position(a, 1),
CreateVisiblePosition(Position(a, 1)).DeepEquivalent());
EXPECT_EQ(Position(a, LayoutNGEnabled() ? 2 : 1),
CreateVisiblePosition(Position(a, 2)).DeepEquivalent());
EXPECT_EQ(Position(a, LayoutNGEnabled() ? 2 : 1),
CreateVisiblePosition(Position(b, 0)).DeepEquivalent());
EXPECT_EQ(Position(b, 1),
CreateVisiblePosition(Position(b, 1)).DeepEquivalent());
}
{
SetBodyContent("<span>a </span>b");
Node* b = GetDocument().body()->lastChild();
Node* a = b->previousSibling()->firstChild();
EXPECT_EQ(Position(a, 0),
CreateVisiblePosition(Position(a, 0)).DeepEquivalent());
EXPECT_EQ(Position(a, 1),
CreateVisiblePosition(Position(a, 1)).DeepEquivalent());
EXPECT_EQ(Position(a, LayoutNGEnabled() ? 2 : 1),
CreateVisiblePosition(Position(a, 2)).DeepEquivalent());
EXPECT_EQ(Position(a, LayoutNGEnabled() ? 2 : 1),
CreateVisiblePosition(Position(b, 0)).DeepEquivalent());
EXPECT_EQ(Position(b, 1),
CreateVisiblePosition(Position(b, 1)).DeepEquivalent());
}
}
} // namespace blink