| // 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 |