blob: 85cc6c02f6156f6a2b0dc8b80ef78c575c3d4f52 [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_units.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.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_text_fragment.h"
namespace blink {
namespace visible_units_test {
PositionWithAffinity PositionWithAffinityInDOMTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return PositionWithAffinity(CanonicalPositionOf(Position(&anchor, offset)),
affinity);
}
VisiblePosition CreateVisiblePositionInDOMTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return CreateVisiblePosition(Position(&anchor, offset), affinity);
}
PositionInFlatTreeWithAffinity PositionWithAffinityInFlatTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return PositionInFlatTreeWithAffinity(
CanonicalPositionOf(PositionInFlatTree(&anchor, offset)), affinity);
}
VisiblePositionInFlatTree CreateVisiblePositionInFlatTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return CreateVisiblePosition(PositionInFlatTree(&anchor, offset), affinity);
}
class VisibleUnitsTest : public EditingTestBase {};
TEST_F(VisibleUnitsTest, caretMinOffset) {
const char* body_content = "<p id=one>one</p>";
SetBodyContent(body_content);
Element* one = GetDocument().getElementById("one");
EXPECT_EQ(0, CaretMinOffset(one->firstChild()));
}
TEST_F(VisibleUnitsTest, caretMinOffsetWithFirstLetter) {
const char* body_content =
"<style>#one:first-letter { font-size: 200%; }</style><p id=one>one</p>";
SetBodyContent(body_content);
Element* one = GetDocument().getElementById("one");
EXPECT_EQ(0, CaretMinOffset(one->firstChild()));
}
TEST_F(VisibleUnitsTest, characterAfter) {
const char* body_content =
"<p id='host'><b id='one'>1</b><b id='two'>22</b></p><b "
"id='three'>333</b>";
const char* shadow_content =
"<b id='four'>4444</b><content select=#two></content><content "
"select=#one></content><b id='five'>5555</b>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
EXPECT_EQ('2', CharacterAfter(
CreateVisiblePositionInDOMTree(*one->firstChild(), 1)));
EXPECT_EQ('5', CharacterAfter(
CreateVisiblePositionInFlatTree(*one->firstChild(), 1)));
EXPECT_EQ(
0, CharacterAfter(CreateVisiblePositionInDOMTree(*two->firstChild(), 2)));
EXPECT_EQ('1', CharacterAfter(
CreateVisiblePositionInFlatTree(*two->firstChild(), 2)));
}
TEST_F(VisibleUnitsTest, canonicalPositionOfWithHTMLHtmlElement) {
const char* body_content =
"<html><div id=one contenteditable>1</div><span id=two "
"contenteditable=false>22</span><span id=three "
"contenteditable=false>333</span><span id=four "
"contenteditable=false>333</span></html>";
SetBodyContent(body_content);
Node* one = GetDocument().QuerySelector("#one");
Node* two = GetDocument().QuerySelector("#two");
Node* three = GetDocument().QuerySelector("#three");
Node* four = GetDocument().QuerySelector("#four");
Element* html = GetDocument().CreateRawElement(html_names::kHTMLTag);
// Move two, three and four into second html element.
html->AppendChild(two);
html->AppendChild(three);
html->AppendChild(four);
one->appendChild(html);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(Position(),
CanonicalPositionOf(Position(GetDocument().documentElement(), 0)));
EXPECT_EQ(Position(one->firstChild(), 0),
CanonicalPositionOf(Position(one, 0)));
EXPECT_EQ(Position(one->firstChild(), 1),
CanonicalPositionOf(Position(one, 1)));
EXPECT_EQ(Position(one->firstChild(), 0),
CanonicalPositionOf(Position(one->firstChild(), 0)));
EXPECT_EQ(Position(one->firstChild(), 1),
CanonicalPositionOf(Position(one->firstChild(), 1)));
EXPECT_EQ(Position(html, 0), CanonicalPositionOf(Position(html, 0)));
EXPECT_EQ(Position(html, 1), CanonicalPositionOf(Position(html, 1)));
EXPECT_EQ(Position(html, 2), CanonicalPositionOf(Position(html, 2)));
EXPECT_EQ(Position(two->firstChild(), 0),
CanonicalPositionOf(Position(two, 0)));
EXPECT_EQ(Position(two->firstChild(), 2),
CanonicalPositionOf(Position(two, 1)));
}
// For http://crbug.com/695317
TEST_F(VisibleUnitsTest, canonicalPositionOfWithInputElement) {
SetBodyContent("<input>123");
Element* const input = GetDocument().QuerySelector("input");
EXPECT_EQ(Position::BeforeNode(*input),
CanonicalPositionOf(Position::FirstPositionInNode(
*GetDocument().documentElement())));
EXPECT_EQ(PositionInFlatTree::BeforeNode(*input),
CanonicalPositionOf(PositionInFlatTree::FirstPositionInNode(
*GetDocument().documentElement())));
}
TEST_F(VisibleUnitsTest, characterBefore) {
const char* body_content =
"<p id=host><b id=one>1</b><b id=two>22</b></p><b id=three>333</b>";
const char* shadow_content =
"<b id=four>4444</b><content select=#two></content><content "
"select=#one></content><b id=five>5555</b>";
SetBodyContent(body_content);
ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
Node* one = GetDocument().getElementById("one")->firstChild();
Node* two = GetDocument().getElementById("two")->firstChild();
Node* five = shadow_root->getElementById("five")->firstChild();
EXPECT_EQ(0, CharacterBefore(CreateVisiblePositionInDOMTree(*one, 0)));
EXPECT_EQ('2', CharacterBefore(CreateVisiblePositionInFlatTree(*one, 0)));
EXPECT_EQ('1', CharacterBefore(CreateVisiblePositionInDOMTree(*one, 1)));
EXPECT_EQ('1', CharacterBefore(CreateVisiblePositionInFlatTree(*one, 1)));
EXPECT_EQ('1', CharacterBefore(CreateVisiblePositionInDOMTree(*two, 0)));
EXPECT_EQ('4', CharacterBefore(CreateVisiblePositionInFlatTree(*two, 0)));
EXPECT_EQ('4', CharacterBefore(CreateVisiblePositionInDOMTree(*five, 0)));
EXPECT_EQ('1', CharacterBefore(CreateVisiblePositionInFlatTree(*five, 0)));
}
TEST_F(VisibleUnitsTest, endOfDocument) {
const char* body_content = "<a id=host><b id=one>1</b><b id=two>22</b></a>";
const char* shadow_content =
"<p><content select=#two></content></p><p><content "
"select=#one></content></p>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
EXPECT_EQ(Position(two->firstChild(), 2),
EndOfDocument(CreateVisiblePositionInDOMTree(*one->firstChild(), 0))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(one->firstChild(), 1),
EndOfDocument(CreateVisiblePositionInFlatTree(*one->firstChild(), 0))
.DeepEquivalent());
EXPECT_EQ(Position(two->firstChild(), 2),
EndOfDocument(CreateVisiblePositionInDOMTree(*two->firstChild(), 1))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(one->firstChild(), 1),
EndOfDocument(CreateVisiblePositionInFlatTree(*two->firstChild(), 1))
.DeepEquivalent());
}
TEST_F(VisibleUnitsTest,
AdjustForwardPositionToAvoidCrossingEditingBoundariesNestedEditable) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(
"<div contenteditable>"
"abc"
"<span contenteditable=\"false\">A^BC</span>"
"d|ef"
"</div>");
const PositionWithAffinity& result =
AdjustForwardPositionToAvoidCrossingEditingBoundaries(
PositionWithAffinity(selection.Extent()), selection.Base());
ASSERT_TRUE(result.IsNotNull());
EXPECT_EQ(
"<div contenteditable>"
"abc"
"<span contenteditable=\"false\">ABC|</span>"
"def"
"</div>",
GetCaretTextFromBody(result.GetPosition()));
EXPECT_EQ(TextAffinity::kDownstream, result.Affinity());
}
TEST_F(VisibleUnitsTest, isEndOfEditableOrNonEditableContent) {
const char* body_content =
"<a id=host><b id=one contenteditable>1</b><b id=two>22</b></a>";
const char* shadow_content =
"<content select=#two></content></p><p><content select=#one></content>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
EXPECT_FALSE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInDOMTree(*one->firstChild(), 1)));
EXPECT_TRUE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInFlatTree(*one->firstChild(), 1)));
EXPECT_TRUE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInDOMTree(*two->firstChild(), 2)));
EXPECT_FALSE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInFlatTree(*two->firstChild(), 2)));
}
TEST_F(VisibleUnitsTest, isEndOfEditableOrNonEditableContentWithInput) {
const char* body_content = "<input id=sample value=ab>cde";
SetBodyContent(body_content);
Node* text = ToTextControl(GetDocument().getElementById("sample"))
->InnerEditorElement()
->firstChild();
EXPECT_FALSE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInDOMTree(*text, 0)));
EXPECT_FALSE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInFlatTree(*text, 0)));
EXPECT_FALSE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInDOMTree(*text, 1)));
EXPECT_FALSE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInFlatTree(*text, 1)));
EXPECT_TRUE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInDOMTree(*text, 2)));
EXPECT_TRUE(IsEndOfEditableOrNonEditableContent(
CreateVisiblePositionInFlatTree(*text, 2)));
}
TEST_F(VisibleUnitsTest, IsVisuallyEquivalentCandidateWithHTMLHtmlElement) {
const char* body_content =
"<html><div id=one contenteditable>1</div><span id=two "
"contenteditable=false>22</span><span id=three "
"contenteditable=false>333</span><span id=four "
"contenteditable=false>333</span></html>";
SetBodyContent(body_content);
Node* one = GetDocument().QuerySelector("#one");
Node* two = GetDocument().QuerySelector("#two");
Node* three = GetDocument().QuerySelector("#three");
Node* four = GetDocument().QuerySelector("#four");
Element* html = GetDocument().CreateRawElement(html_names::kHTMLTag);
// Move two, three and four into second html element.
html->AppendChild(two);
html->AppendChild(three);
html->AppendChild(four);
one->appendChild(html);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(IsVisuallyEquivalentCandidate(
Position(GetDocument().documentElement(), 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(one, 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(one, 1)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(one->firstChild(), 0)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(one->firstChild(), 1)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(html, 0)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(html, 1)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(html, 2)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(two, 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(two, 1)));
}
TEST_F(VisibleUnitsTest, isVisuallyEquivalentCandidateWithHTMLBodyElement) {
const char* body_content =
"<div id=one contenteditable>1</div><span id=two "
"contenteditable=false>22</span><span id=three "
"contenteditable=false>333</span><span id=four "
"contenteditable=false>333</span>";
SetBodyContent(body_content);
Node* one = GetDocument().QuerySelector("#one");
Node* two = GetDocument().QuerySelector("#two");
Node* three = GetDocument().QuerySelector("#three");
Node* four = GetDocument().QuerySelector("#four");
Element* body = GetDocument().CreateRawElement(html_names::kBodyTag);
Element* empty_body = GetDocument().CreateRawElement(html_names::kBodyTag);
Element* div = GetDocument().CreateRawElement(html_names::kDivTag);
Element* br = GetDocument().CreateRawElement(html_names::kBrTag);
empty_body->appendChild(div);
empty_body->appendChild(br);
one->appendChild(empty_body);
// Move two, three and four into second body element.
body->appendChild(two);
body->AppendChild(three);
body->AppendChild(four);
one->appendChild(body);
GetDocument().UpdateStyleAndLayout();
EXPECT_FALSE(IsVisuallyEquivalentCandidate(
Position(GetDocument().documentElement(), 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(one, 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(one, 1)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(one->firstChild(), 0)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(one->firstChild(), 1)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(body, 0)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(body, 1)));
EXPECT_TRUE(IsVisuallyEquivalentCandidate(Position(body, 2)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(two, 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(two, 1)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(empty_body, 0)));
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(empty_body, 1)));
}
TEST_F(VisibleUnitsTest, isVisuallyEquivalentCandidateWithDocument) {
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(IsVisuallyEquivalentCandidate(Position(&GetDocument(), 0)));
}
TEST_F(VisibleUnitsTest, mostBackwardCaretPositionAfterAnchor) {
const char* body_content =
"<p id='host'><b id='one'>1</b></p><b id='two'>22</b>";
const char* shadow_content =
"<b id='two'>22</b><content select=#one></content><b id='three'>333</b>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* host = GetDocument().getElementById("host");
EXPECT_EQ(Position::LastPositionInNode(*host),
MostForwardCaretPosition(Position::AfterNode(*host)));
EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*host),
MostForwardCaretPosition(PositionInFlatTree::AfterNode(*host)));
}
TEST_F(VisibleUnitsTest, mostBackwardCaretPositionFirstLetter) {
// Note: first-letter pseudo element contains letter and punctuations.
const char* body_content =
"<style>p:first-letter {color:red;}</style><p id=sample> (2)45 </p>";
SetBodyContent(body_content);
Node* sample = GetDocument().getElementById("sample")->firstChild();
EXPECT_EQ(Position(sample->parentNode(), 0),
MostBackwardCaretPosition(Position(sample, 0)));
EXPECT_EQ(Position(sample->parentNode(), 0),
MostBackwardCaretPosition(Position(sample, 1)));
EXPECT_EQ(Position(sample, 2),
MostBackwardCaretPosition(Position(sample, 2)));
EXPECT_EQ(Position(sample, 3),
MostBackwardCaretPosition(Position(sample, 3)));
EXPECT_EQ(Position(sample, 4),
MostBackwardCaretPosition(Position(sample, 4)));
EXPECT_EQ(Position(sample, 5),
MostBackwardCaretPosition(Position(sample, 5)));
EXPECT_EQ(Position(sample, 6),
MostBackwardCaretPosition(Position(sample, 6)));
EXPECT_EQ(Position(sample, 6),
MostBackwardCaretPosition(Position(sample, 7)));
EXPECT_EQ(Position(sample, 6),
MostBackwardCaretPosition(
Position::LastPositionInNode(*sample->parentNode())));
EXPECT_EQ(
Position(sample, 6),
MostBackwardCaretPosition(Position::AfterNode(*sample->parentNode())));
EXPECT_EQ(Position::LastPositionInNode(*GetDocument().body()),
MostBackwardCaretPosition(
Position::LastPositionInNode(*GetDocument().body())));
}
TEST_F(VisibleUnitsTest, mostBackwardCaretPositionFirstLetterSplit) {
V8TestingScope scope;
const char* body_content =
"<style>p:first-letter {color:red;}</style><p id=sample>abc</p>";
SetBodyContent(body_content);
Node* sample = GetDocument().getElementById("sample");
Node* first_letter = sample->firstChild();
// Split "abc" into "a" "bc"
Text* remaining = ToText(first_letter)->splitText(1, ASSERT_NO_EXCEPTION);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(Position(sample, 0),
MostBackwardCaretPosition(Position(first_letter, 0)));
EXPECT_EQ(Position(first_letter, 1),
MostBackwardCaretPosition(Position(first_letter, 1)));
EXPECT_EQ(Position(first_letter, 1),
MostBackwardCaretPosition(Position(remaining, 0)));
EXPECT_EQ(Position(remaining, 1),
MostBackwardCaretPosition(Position(remaining, 1)));
EXPECT_EQ(Position(remaining, 2),
MostBackwardCaretPosition(Position(remaining, 2)));
EXPECT_EQ(Position(remaining, 2),
MostBackwardCaretPosition(Position::LastPositionInNode(*sample)));
EXPECT_EQ(Position(remaining, 2),
MostBackwardCaretPosition(Position::AfterNode(*sample)));
}
TEST_F(VisibleUnitsTest, mostForwardCaretPositionAfterAnchor) {
const char* body_content = "<p id='host'><b id='one'>1</b></p>";
const char* shadow_content =
"<b id='two'>22</b><content select=#one></content><b id='three'>333</b>";
SetBodyContent(body_content);
ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
UpdateAllLifecyclePhasesForTest();
Element* host = GetDocument().getElementById("host");
Element* one = GetDocument().getElementById("one");
Element* three = shadow_root->getElementById("three");
EXPECT_EQ(Position(one->firstChild(), 1),
MostBackwardCaretPosition(Position::AfterNode(*host)));
EXPECT_EQ(PositionInFlatTree(three->firstChild(), 3),
MostBackwardCaretPosition(PositionInFlatTree::AfterNode(*host)));
}
TEST_F(VisibleUnitsTest, mostForwardCaretPositionFirstLetter) {
// Note: first-letter pseudo element contains letter and punctuations.
const char* body_content =
"<style>p:first-letter {color:red;}</style><p id=sample> (2)45 </p>";
SetBodyContent(body_content);
Node* sample = GetDocument().getElementById("sample")->firstChild();
EXPECT_EQ(Position(GetDocument().body(), 0),
MostForwardCaretPosition(
Position::FirstPositionInNode(*GetDocument().body())));
EXPECT_EQ(
Position(sample, 1),
MostForwardCaretPosition(Position::BeforeNode(*sample->parentNode())));
EXPECT_EQ(Position(sample, 1),
MostForwardCaretPosition(
Position::FirstPositionInNode(*sample->parentNode())));
EXPECT_EQ(Position(sample, 1), MostForwardCaretPosition(Position(sample, 0)));
EXPECT_EQ(Position(sample, 1), MostForwardCaretPosition(Position(sample, 1)));
EXPECT_EQ(Position(sample, 2), MostForwardCaretPosition(Position(sample, 2)));
EXPECT_EQ(Position(sample, 3), MostForwardCaretPosition(Position(sample, 3)));
EXPECT_EQ(Position(sample, 4), MostForwardCaretPosition(Position(sample, 4)));
EXPECT_EQ(Position(sample, 5), MostForwardCaretPosition(Position(sample, 5)));
EXPECT_EQ(Position(sample, 7), MostForwardCaretPosition(Position(sample, 6)));
EXPECT_EQ(Position(sample, 7), MostForwardCaretPosition(Position(sample, 7)));
}
TEST_F(VisibleUnitsTest, nextPositionOf) {
const char* body_content =
"<b id=zero>0</b><p id=host><b id=one>1</b><b id=two>22</b></p><b "
"id=three>333</b>";
const char* shadow_content =
"<b id=four>4444</b><content select=#two></content><content "
"select=#one></content><b id=five>55555</b>";
SetBodyContent(body_content);
ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
Element* zero = GetDocument().getElementById("zero");
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
Element* three = GetDocument().getElementById("three");
Element* four = shadow_root->getElementById("four");
Element* five = shadow_root->getElementById("five");
EXPECT_EQ(Position(one->firstChild(), 0),
NextPositionOf(CreateVisiblePosition(Position(zero, 1)))
.DeepEquivalent());
EXPECT_EQ(PositionInFlatTree(four->firstChild(), 0),
NextPositionOf(CreateVisiblePosition(PositionInFlatTree(zero, 1)))
.DeepEquivalent());
EXPECT_EQ(
Position(one->firstChild(), 1),
NextPositionOf(CreateVisiblePosition(Position(one, 0))).DeepEquivalent());
EXPECT_EQ(PositionInFlatTree(one->firstChild(), 1),
NextPositionOf(CreateVisiblePosition(PositionInFlatTree(one, 0)))
.DeepEquivalent());
EXPECT_EQ(
Position(two->firstChild(), 1),
NextPositionOf(CreateVisiblePosition(Position(one, 1))).DeepEquivalent());
EXPECT_EQ(PositionInFlatTree(five->firstChild(), 1),
NextPositionOf(CreateVisiblePosition(PositionInFlatTree(one, 1)))
.DeepEquivalent());
EXPECT_EQ(
Position(three->firstChild(), 0),
NextPositionOf(CreateVisiblePosition(Position(two, 1))).DeepEquivalent());
EXPECT_EQ(PositionInFlatTree(one->firstChild(), 1),
NextPositionOf(CreateVisiblePosition(PositionInFlatTree(two, 1)))
.DeepEquivalent());
}
TEST_F(VisibleUnitsTest, previousPositionOf) {
const char* body_content =
"<b id=zero>0</b><p id=host><b id=one>1</b><b id=two>22</b></p><b "
"id=three>333</b>";
const char* shadow_content =
"<b id=four>4444</b><content select=#two></content><content "
"select=#one></content><b id=five>55555</b>";
SetBodyContent(body_content);
ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
Node* zero = GetDocument().getElementById("zero")->firstChild();
Node* one = GetDocument().getElementById("one")->firstChild();
Node* two = GetDocument().getElementById("two")->firstChild();
Node* three = GetDocument().getElementById("three")->firstChild();
Node* four = shadow_root->getElementById("four")->firstChild();
Node* five = shadow_root->getElementById("five")->firstChild();
EXPECT_EQ(Position(zero, 0),
PreviousPositionOf(CreateVisiblePosition(Position(zero, 1)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(zero, 0),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(zero, 1)))
.DeepEquivalent());
EXPECT_EQ(Position(zero, 1),
PreviousPositionOf(CreateVisiblePosition(Position(one, 0)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(two, 1),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(one, 0)))
.DeepEquivalent());
EXPECT_EQ(Position(one, 0),
PreviousPositionOf(CreateVisiblePosition(Position(one, 1)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(two, 2),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(one, 1)))
.DeepEquivalent());
EXPECT_EQ(Position(one, 0),
PreviousPositionOf(CreateVisiblePosition(Position(two, 0)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(four, 3),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(two, 0)))
.DeepEquivalent());
// DOM tree to shadow tree
EXPECT_EQ(Position(two, 2),
PreviousPositionOf(CreateVisiblePosition(Position(three, 0)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(five, 5),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(three, 0)))
.DeepEquivalent());
// Shadow tree to DOM tree
EXPECT_EQ(Position(),
PreviousPositionOf(CreateVisiblePosition(Position(four, 0)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(zero, 1),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(four, 0)))
.DeepEquivalent());
// Note: Canonicalization maps (five, 0) to (four, 4) in DOM tree and
// (one, 1) in flat tree.
EXPECT_EQ(Position(four, 4),
PreviousPositionOf(CreateVisiblePosition(Position(five, 1)))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(one, 1),
PreviousPositionOf(CreateVisiblePosition(PositionInFlatTree(five, 1)))
.DeepEquivalent());
}
TEST_F(VisibleUnitsTest, previousPositionOfOneCharPerLine) {
const char* body_content =
"<div id=sample style='font-size: 500px'>A&#x714a;&#xfa67;</div>";
SetBodyContent(body_content);
Node* sample = GetDocument().getElementById("sample")->firstChild();
// In case of each line has one character, VisiblePosition are:
// [C,Dn] [C,Up] [B, Dn] [B, Up]
// A A A A|
// B B| |B B
// |C C C C
EXPECT_EQ(PositionWithAffinity(Position(sample, 1)),
PreviousPositionOf(CreateVisiblePosition(Position(sample, 2)))
.ToPositionWithAffinity());
EXPECT_EQ(PositionWithAffinity(Position(sample, 1)),
PreviousPositionOf(CreateVisiblePosition(Position(sample, 2),
TextAffinity::kUpstream))
.ToPositionWithAffinity());
}
TEST_F(VisibleUnitsTest, previousPositionOfNoPreviousPosition) {
SetBodyContent(
"<span contenteditable='true'>"
"<span> </span>"
" " // This whitespace causes no previous position.
"<div id='anchor'> bar</div>"
"</span>");
const Position position(GetDocument().getElementById("anchor")->firstChild(),
1);
EXPECT_EQ(
Position(),
PreviousPositionOf(CreateVisiblePosition(position)).DeepEquivalent());
}
TEST_F(VisibleUnitsTest, rendersInDifferentPositionAfterAnchor) {
const char* body_content = "<p id='sample'>00</p>";
SetBodyContent(body_content);
Element* sample = GetDocument().getElementById("sample");
EXPECT_FALSE(RendersInDifferentPosition(Position(), Position()));
EXPECT_FALSE(
RendersInDifferentPosition(Position(), Position::AfterNode(*sample)))
<< "if one of position is null, the reuslt is false.";
EXPECT_FALSE(RendersInDifferentPosition(Position::AfterNode(*sample),
Position(sample, 1)));
EXPECT_FALSE(RendersInDifferentPosition(Position::LastPositionInNode(*sample),
Position(sample, 1)));
}
TEST_F(VisibleUnitsTest, rendersInDifferentPositionAfterAnchorWithHidden) {
const char* body_content =
"<p><span id=one>11</span><span id=two style='display:none'> "
"</span></p>";
SetBodyContent(body_content);
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
EXPECT_TRUE(RendersInDifferentPosition(Position::LastPositionInNode(*one),
Position(two, 0)))
<< "two doesn't have layout object";
}
TEST_F(VisibleUnitsTest,
rendersInDifferentPositionAfterAnchorWithDifferentLayoutObjects) {
const char* body_content =
"<p><span id=one>11</span><span id=two> </span></p>";
SetBodyContent(body_content);
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
EXPECT_FALSE(RendersInDifferentPosition(Position::LastPositionInNode(*one),
Position(two, 0)));
EXPECT_FALSE(RendersInDifferentPosition(Position::LastPositionInNode(*one),
Position(two, 1)))
<< "width of two is zero since contents is collapsed whitespaces";
}
TEST_F(VisibleUnitsTest, renderedOffset) {
const char* body_content =
"<div contenteditable><span id='sample1'>1</span><span "
"id='sample2'>22</span></div>";
SetBodyContent(body_content);
Element* sample1 = GetDocument().getElementById("sample1");
Element* sample2 = GetDocument().getElementById("sample2");
EXPECT_FALSE(
RendersInDifferentPosition(Position::AfterNode(*sample1->firstChild()),
Position(sample2->firstChild(), 0)));
EXPECT_FALSE(RendersInDifferentPosition(
Position::LastPositionInNode(*sample1->firstChild()),
Position(sample2->firstChild(), 0)));
}
TEST_F(VisibleUnitsTest, startOfDocument) {
const char* body_content = "<a id=host><b id=one>1</b><b id=two>22</b></a>";
const char* shadow_content =
"<p><content select=#two></content></p><p><content "
"select=#one></content></p>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Node* one = GetDocument().getElementById("one")->firstChild();
Node* two = GetDocument().getElementById("two")->firstChild();
EXPECT_EQ(Position(one, 0),
StartOfDocument(CreateVisiblePositionInDOMTree(*one, 0))
.DeepEquivalent());
EXPECT_EQ(PositionInFlatTree(two, 0),
StartOfDocument(CreateVisiblePositionInFlatTree(*one, 0))
.DeepEquivalent());
EXPECT_EQ(Position(one, 0),
StartOfDocument(CreateVisiblePositionInDOMTree(*two, 1))
.DeepEquivalent());
EXPECT_EQ(PositionInFlatTree(two, 0),
StartOfDocument(CreateVisiblePositionInFlatTree(*two, 1))
.DeepEquivalent());
}
TEST_F(VisibleUnitsTest,
endsOfNodeAreVisuallyDistinctPositionsWithInvisibleChild) {
// Repro case of crbug.com/582247
const char* body_content =
"<button> </button><script>document.designMode = 'on'</script>";
SetBodyContent(body_content);
Node* button = GetDocument().QuerySelector("button");
EXPECT_TRUE(EndsOfNodeAreVisuallyDistinctPositions(button));
}
TEST_F(VisibleUnitsTest,
endsOfNodeAreVisuallyDistinctPositionsWithEmptyLayoutChild) {
// Repro case of crbug.com/584030
const char* body_content =
"<button><rt><script>document.designMode = 'on'</script></rt></button>";
SetBodyContent(body_content);
Node* button = GetDocument().QuerySelector("button");
EXPECT_TRUE(EndsOfNodeAreVisuallyDistinctPositions(button));
}
// Regression test for crbug.com/675429
TEST_F(VisibleUnitsTest,
canonicalizationWithCollapsedSpaceAndIsolatedCombiningCharacter) {
SetBodyContent("<p> &#x20E3;</p>"); // Leading space is necessary
Node* paragraph = GetDocument().QuerySelector("p");
Node* text = paragraph->firstChild();
Position start = CanonicalPositionOf(Position::BeforeNode(*paragraph));
EXPECT_EQ(Position(text, 2), start);
}
TEST_F(VisibleUnitsTest, MostForwardCaretPositionWithInvisibleFirstLetter) {
InsertStyleElement("div::first-letter{visibility:hidden}");
// Use special syntax to set input position DIV@0
const Position position = SetCaretTextToBody("<div><!--|-->foo</div>");
const Node* foo = GetDocument().QuerySelector("div")->firstChild();
EXPECT_EQ(Position(foo, 1), MostForwardCaretPosition(position));
}
} // namespace visible_units_test
} // namespace blink