| // Copyright 2014 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 <memory> |
| #include "base/memory/scoped_refptr.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/Text.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/SelectionTemplate.h" |
| #include "core/editing/VisiblePosition.h" |
| #include "core/editing/VisibleSelection.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/HTMLBodyElement.h" |
| #include "core/html/HTMLSpanElement.h" |
| #include "core/testing/PageTestBase.h" |
| #include "platform/heap/Handle.h" |
| #include "platform/wtf/StdLibExtras.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace blink { |
| |
| #define EXPECT_EQ_SELECTED_TEXT(text) \ |
| EXPECT_EQ(text, WebString(Selection().SelectedText()).Utf8()) |
| |
| IntPoint VisiblePositionToContentsPoint(const VisiblePosition& pos) { |
| IntPoint result = AbsoluteSelectionBoundsOf(pos).MinXMaxYCorner(); |
| // Need to move the point at least by 1 - caret's minXMaxYCorner is not |
| // evaluated to the same line as the text by hit testing. |
| result.Move(0, -1); |
| return result; |
| } |
| |
| using TextNodeVector = HeapVector<Member<Text>>; |
| |
| class GranularityStrategyTest : public PageTestBase { |
| protected: |
| void SetUp() override; |
| |
| void SetSelectionAndEndTyping(const VisibleSelection&); |
| Text* AppendTextNode(const String& data); |
| int LayoutCount() const { |
| return GetDummyPageHolder().GetFrameView().LayoutCount(); |
| } |
| void SetInnerHTML(const char*); |
| // Parses the text node, appending the info to m_letterPos and m_wordMiddles. |
| void ParseText(Text*); |
| void ParseText(const TextNodeVector&); |
| |
| Text* SetupTranslateZ(String); |
| Text* SetupTransform(String); |
| Text* SetupRotate(String); |
| void SetupTextSpan(String str1, |
| String str2, |
| String str3, |
| size_t sel_begin, |
| size_t sel_end); |
| void SetupVerticalAlign(String str1, |
| String str2, |
| String str3, |
| size_t sel_begin, |
| size_t sel_end); |
| void SetupFontSize(String str1, |
| String str2, |
| String str3, |
| size_t sel_begin, |
| size_t sel_end); |
| |
| void TestDirectionExpand(); |
| void TestDirectionShrink(); |
| void TestDirectionSwitchSide(); |
| |
| // Pixel coordinates of the positions for each letter within the text being |
| // tested. |
| Vector<IntPoint> letter_pos_; |
| // Pixel coordinates of the middles of the words in the text being tested. |
| // (y coordinate is based on y coordinates of m_letterPos) |
| Vector<IntPoint> word_middles_; |
| }; |
| |
| void GranularityStrategyTest::SetUp() { |
| PageTestBase::SetUp(); |
| GetFrame().GetSettings()->SetDefaultFontSize(12); |
| GetFrame().GetSettings()->SetSelectionStrategy(SelectionStrategy::kDirection); |
| } |
| |
| void GranularityStrategyTest::SetSelectionAndEndTyping( |
| const VisibleSelection& new_selection) { |
| Selection().SetSelectionAndEndTyping(new_selection.AsSelection()); |
| } |
| |
| Text* GranularityStrategyTest::AppendTextNode(const String& data) { |
| Text* text = GetDocument().createTextNode(data); |
| GetDocument().body()->AppendChild(text); |
| return text; |
| } |
| |
| void GranularityStrategyTest::SetInnerHTML(const char* html_content) { |
| GetDocument().documentElement()->SetInnerHTMLFromString( |
| String::FromUTF8(html_content)); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| } |
| |
| void GranularityStrategyTest::ParseText(Text* text) { |
| TextNodeVector text_nodes; |
| text_nodes.push_back(text); |
| ParseText(text_nodes); |
| } |
| |
| void GranularityStrategyTest::ParseText(const TextNodeVector& text_nodes) { |
| bool word_started = false; |
| int word_start_index = 0; |
| for (auto& text : text_nodes) { |
| int word_start_index_offset = letter_pos_.size(); |
| String str = text->wholeText(); |
| for (size_t i = 0; i < str.length(); i++) { |
| letter_pos_.push_back(VisiblePositionToContentsPoint( |
| CreateVisiblePosition(Position(text, i)))); |
| char c = str[i]; |
| if (IsASCIIAlphanumeric(c) && !word_started) { |
| word_start_index = i + word_start_index_offset; |
| word_started = true; |
| } else if (!IsASCIIAlphanumeric(c) && word_started) { |
| IntPoint word_middle((letter_pos_[word_start_index].X() + |
| letter_pos_[i + word_start_index_offset].X()) / |
| 2, |
| letter_pos_[word_start_index].Y()); |
| word_middles_.push_back(word_middle); |
| word_started = false; |
| } |
| } |
| } |
| if (word_started) { |
| const auto& last_node = text_nodes.back(); |
| int x_end = VisiblePositionToContentsPoint( |
| CreateVisiblePosition( |
| Position(last_node, last_node->wholeText().length()))) |
| .X(); |
| IntPoint word_middle((letter_pos_[word_start_index].X() + x_end) / 2, |
| letter_pos_[word_start_index].Y()); |
| word_middles_.push_back(word_middle); |
| } |
| } |
| |
| Text* GranularityStrategyTest::SetupTranslateZ(String str) { |
| SetInnerHTML( |
| "<html>" |
| "<head>" |
| "<style>" |
| "div {" |
| "transform: translateZ(0);" |
| "}" |
| "</style>" |
| "</head>" |
| "<body>" |
| "<div id='mytext'></div>" |
| "</body>" |
| "</html>"); |
| |
| Text* text = GetDocument().createTextNode(str); |
| Element* div = GetDocument().getElementById("mytext"); |
| div->AppendChild(text); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| ParseText(text); |
| return text; |
| } |
| |
| Text* GranularityStrategyTest::SetupTransform(String str) { |
| SetInnerHTML( |
| "<html>" |
| "<head>" |
| "<style>" |
| "div {" |
| "transform: scale(1,-1) translate(0,-100px);" |
| "}" |
| "</style>" |
| "</head>" |
| "<body>" |
| "<div id='mytext'></div>" |
| "</body>" |
| "</html>"); |
| |
| Text* text = GetDocument().createTextNode(str); |
| Element* div = GetDocument().getElementById("mytext"); |
| div->AppendChild(text); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| ParseText(text); |
| return text; |
| } |
| |
| Text* GranularityStrategyTest::SetupRotate(String str) { |
| SetInnerHTML( |
| "<html>" |
| "<head>" |
| "<style>" |
| "div {" |
| "transform: translate(0px,600px) rotate(90deg);" |
| "}" |
| "</style>" |
| "</head>" |
| "<body>" |
| "<div id='mytext'></div>" |
| "</body>" |
| "</html>"); |
| |
| Text* text = GetDocument().createTextNode(str); |
| Element* div = GetDocument().getElementById("mytext"); |
| div->AppendChild(text); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| ParseText(text); |
| return text; |
| } |
| |
| void GranularityStrategyTest::SetupTextSpan(String str1, |
| String str2, |
| String str3, |
| size_t sel_begin, |
| size_t sel_end) { |
| Text* text1 = GetDocument().createTextNode(str1); |
| Text* text2 = GetDocument().createTextNode(str2); |
| Text* text3 = GetDocument().createTextNode(str3); |
| Element* span = HTMLSpanElement::Create(GetDocument()); |
| Element* div = GetDocument().getElementById("mytext"); |
| div->AppendChild(text1); |
| div->AppendChild(span); |
| span->AppendChild(text2); |
| div->AppendChild(text3); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| Vector<IntPoint> letter_pos; |
| Vector<IntPoint> word_middle_pos; |
| |
| TextNodeVector text_nodes; |
| text_nodes.push_back(text1); |
| text_nodes.push_back(text2); |
| text_nodes.push_back(text3); |
| ParseText(text_nodes); |
| |
| Position p1; |
| Position p2; |
| if (sel_begin < str1.length()) |
| p1 = Position(text1, sel_begin); |
| else if (sel_begin < str1.length() + str2.length()) |
| p1 = Position(text2, sel_begin - str1.length()); |
| else |
| p1 = Position(text3, sel_begin - str1.length() - str2.length()); |
| if (sel_end < str1.length()) |
| p2 = Position(text1, sel_end); |
| else if (sel_end < str1.length() + str2.length()) |
| p2 = Position(text2, sel_end - str1.length()); |
| else |
| p2 = Position(text3, sel_end - str1.length() - str2.length()); |
| |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder().SetBaseAndExtent(p1, p2).Build()); |
| } |
| |
| void GranularityStrategyTest::SetupVerticalAlign(String str1, |
| String str2, |
| String str3, |
| size_t sel_begin, |
| size_t sel_end) { |
| SetInnerHTML( |
| "<html>" |
| "<head>" |
| "<style>" |
| "span {" |
| "vertical-align:20px;" |
| "}" |
| "</style>" |
| "</head>" |
| "<body>" |
| "<div id='mytext'></div>" |
| "</body>" |
| "</html>"); |
| |
| SetupTextSpan(str1, str2, str3, sel_begin, sel_end); |
| } |
| |
| void GranularityStrategyTest::SetupFontSize(String str1, |
| String str2, |
| String str3, |
| size_t sel_begin, |
| size_t sel_end) { |
| SetInnerHTML( |
| "<html>" |
| "<head>" |
| "<style>" |
| "span {" |
| "font-size: 200%;" |
| "}" |
| "</style>" |
| "</head>" |
| "<body>" |
| "<div id='mytext'></div>" |
| "</body>" |
| "</html>"); |
| |
| SetupTextSpan(str1, str2, str3, sel_begin, sel_end); |
| } |
| |
| // Tests expanding selection on text "abcdef ghij kl mno^p|>qr stuvwi inm mnii," |
| // (^ means base, | means extent, < means start, and > means end). Text needs to |
| // be laid out on a single line with no rotation. |
| void GranularityStrategyTest::TestDirectionExpand() { |
| // Expand selection using character granularity until the end of the word |
| // is reached. |
| // "abcdef ghij kl mno^pq|>r stuvwi inm mnii," |
| Selection().MoveRangeSelectionExtent(letter_pos_[20]); |
| EXPECT_EQ_SELECTED_TEXT("pq"); |
| // Move to the same postion shouldn't change anything. |
| Selection().MoveRangeSelectionExtent(letter_pos_[20]); |
| EXPECT_EQ_SELECTED_TEXT("pq"); |
| // "abcdef ghij kl mno^pqr|> stuvwi inm mnii," |
| Selection().MoveRangeSelectionExtent(letter_pos_[21]); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| // Selection should stay the same until the middle of the word is passed. |
| // "abcdef ghij kl mno^pqr |>stuvwi inm mnii," - |
| Selection().MoveRangeSelectionExtent(letter_pos_[22]); |
| EXPECT_EQ_SELECTED_TEXT("pqr "); |
| // "abcdef ghij kl mno^pqr >st|uvwi inm mnii," |
| Selection().MoveRangeSelectionExtent(letter_pos_[24]); |
| EXPECT_EQ_SELECTED_TEXT("pqr "); |
| IntPoint p = word_middles_[4]; |
| p.Move(-1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr "); |
| p.Move(1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi"); |
| // Selection should stay the same until the end of the word is reached. |
| // "abcdef ghij kl mno^pqr stuvw|i> inm mnii," |
| Selection().MoveRangeSelectionExtent(letter_pos_[27]); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi"); |
| // "abcdef ghij kl mno^pqr stuvwi|> inm mnii," |
| Selection().MoveRangeSelectionExtent(letter_pos_[28]); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi"); |
| // "abcdef ghij kl mno^pqr stuvwi |>inm mnii," |
| Selection().MoveRangeSelectionExtent(letter_pos_[29]); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi "); |
| // Now expand slowly to the middle of word #5. |
| int y = letter_pos_[29].Y(); |
| for (int x = letter_pos_[29].X() + 1; x < word_middles_[5].X(); x++) { |
| Selection().MoveRangeSelectionExtent(IntPoint(x, y)); |
| Selection().MoveRangeSelectionExtent(IntPoint(x, y)); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi "); |
| } |
| Selection().MoveRangeSelectionExtent(word_middles_[5]); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi inm"); |
| // Jump over quickly to just before the middle of the word #6 and then |
| // move over it. |
| p = word_middles_[6]; |
| p.Move(-1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi inm "); |
| p.Move(1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr stuvwi inm mnii"); |
| } |
| |
| // Tests shrinking selection on text "abcdef ghij kl mno^pqr|> iiinmni, abc" |
| // (^ means base, | means extent, < means start, and > means end). |
| // Text needs to be laid out on a single line with no rotation. |
| void GranularityStrategyTest::TestDirectionShrink() { |
| // Move to the middle of word #4 to it and then move back, confirming |
| // that the selection end is moving with the extent. The offset between the |
| // extent and the selection end will be equal to half the width of "iiinmni". |
| Selection().MoveRangeSelectionExtent(word_middles_[4]); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiinmni"); |
| IntPoint p = word_middles_[4]; |
| p.Move(letter_pos_[28].X() - letter_pos_[29].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiinmn"); |
| p.Move(letter_pos_[27].X() - letter_pos_[28].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiinm"); |
| p.Move(letter_pos_[26].X() - letter_pos_[27].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiin"); |
| // Move right by the width of char 30 ('m'). Selection shouldn't change, |
| // but offset should be reduced. |
| p.Move(letter_pos_[27].X() - letter_pos_[26].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiin"); |
| // Move back a couple of character widths and confirm the selection still |
| // updates accordingly. |
| p.Move(letter_pos_[25].X() - letter_pos_[26].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr iii"); |
| p.Move(letter_pos_[24].X() - letter_pos_[25].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr ii"); |
| // "Catch up" with the handle - move the extent to where the handle is. |
| // "abcdef ghij kl mno^pqr ii|>inmni, abc" |
| Selection().MoveRangeSelectionExtent(letter_pos_[24]); |
| EXPECT_EQ_SELECTED_TEXT("pqr ii"); |
| // Move ahead and confirm the selection expands accordingly |
| // "abcdef ghij kl mno^pqr iii|>nmni, abc" |
| Selection().MoveRangeSelectionExtent(letter_pos_[25]); |
| EXPECT_EQ_SELECTED_TEXT("pqr iii"); |
| |
| // Confirm we stay in character granularity if the user moves within a word. |
| // "abcdef ghij kl mno^pqr |>iiinmni, abc" |
| Selection().MoveRangeSelectionExtent(letter_pos_[22]); |
| EXPECT_EQ_SELECTED_TEXT("pqr "); |
| // It's possible to get a move when position doesn't change. |
| // It shouldn't affect anything. |
| p = letter_pos_[22]; |
| p.Move(1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("pqr "); |
| // "abcdef ghij kl mno^pqr i|>iinmni, abc" |
| Selection().MoveRangeSelectionExtent(letter_pos_[23]); |
| EXPECT_EQ_SELECTED_TEXT("pqr i"); |
| } |
| |
| // Tests moving selection extent over to the other side of the base |
| // on text "abcd efgh ijkl mno^pqr|> iiinmni, abc" |
| // (^ means base, | means extent, < means start, and > means end). |
| // Text needs to be laid out on a single line with no rotation. |
| void GranularityStrategyTest::TestDirectionSwitchSide() { |
| // Move to the middle of word #4, selecting it - this will set the offset to |
| // be half the width of "iiinmni. |
| Selection().MoveRangeSelectionExtent(word_middles_[4]); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiinmni"); |
| // Move back leaving only one letter selected. |
| IntPoint p = word_middles_[4]; |
| p.Move(letter_pos_[19].X() - letter_pos_[29].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("p"); |
| // Confirm selection doesn't change if extent is positioned at base. |
| p.Move(letter_pos_[18].X() - letter_pos_[19].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("p"); |
| // Move over to the other side of the base. Confirm the offset is preserved. |
| // (i.e. the selection start stays on the right of the extent) |
| // Confirm we stay in character granularity until the beginning of the word |
| // is passed. |
| p.Move(letter_pos_[17].X() - letter_pos_[18].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("o"); |
| p.Move(letter_pos_[16].X() - letter_pos_[17].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("no"); |
| p.Move(letter_pos_[14].X() - letter_pos_[16].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT(" mno"); |
| // Move to just one pixel on the right before the middle of the word #2. |
| // We should switch to word granularity, so the selection shouldn't change. |
| p.Move(word_middles_[2].X() - letter_pos_[14].X() + 1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT(" mno"); |
| // Move over the middle of the word. The word should get selected. |
| // This should reduce the offset, but it should still stay greated than 0, |
| // since the width of "iiinmni" is greater than the width of "ijkl". |
| p.Move(-2, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("ijkl mno"); |
| // Move to just one pixel on the right of the middle of word #1. |
| // The selection should now include the space between the words. |
| p.Move(word_middles_[1].X() - letter_pos_[10].X() + 1, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT(" ijkl mno"); |
| // Move over the middle of the word. The word should get selected. |
| p.Move(-2, 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("efgh ijkl mno"); |
| } |
| |
| // Test for the default CharacterGranularityStrategy |
| TEST_F(GranularityStrategyTest, Character) { |
| GetDummyPageHolder().GetFrame().GetSettings()->SetSelectionStrategy( |
| SelectionStrategy::kCharacter); |
| GetDummyPageHolder().GetFrame().GetSettings()->SetDefaultFontSize(12); |
| // "Foo Bar Baz," |
| Text* text = AppendTextNode("Foo Bar Baz,"); |
| GetDocument().UpdateStyleAndLayout(); |
| |
| // "Foo B^a|>r Baz," (^ means base, | means extent, , < means start, and > |
| // means end). |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 5), Position(text, 6)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("a"); |
| // "Foo B^ar B|>az," |
| Selection().MoveRangeSelectionExtent( |
| VisiblePositionToContentsPoint(CreateVisiblePosition(Position(text, 9)))); |
| EXPECT_EQ_SELECTED_TEXT("ar B"); |
| // "F<|oo B^ar Baz," |
| Selection().MoveRangeSelectionExtent( |
| VisiblePositionToContentsPoint(CreateVisiblePosition(Position(text, 1)))); |
| EXPECT_EQ_SELECTED_TEXT("oo B"); |
| } |
| |
| // DirectionGranularityStrategy strategy on rotated text should revert to the |
| // same behavior as CharacterGranularityStrategy |
| TEST_F(GranularityStrategyTest, DirectionRotate) { |
| Text* text = SetupRotate("Foo Bar Baz,"); |
| // "Foo B^a|>r Baz," (^ means base, | means extent, , < means start, and > |
| // means end). |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 5), Position(text, 6)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("a"); |
| IntPoint p = letter_pos_[9]; |
| // Need to move by one pixel, otherwise this point is not evaluated |
| // to the same line as the text by hit testing. |
| p.Move(1, 0); |
| // "Foo B^ar B|>az," |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("ar B"); |
| p = letter_pos_[1]; |
| p.Move(1, 0); |
| // "F<|oo B^ar Baz," |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("oo B"); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionExpandTranslateZ) { |
| Text* text = SetupTranslateZ("abcdef ghij kl mnopqr stuvwi inm mnii,"); |
| // "abcdef ghij kl mno^p|>qr stuvwi inm mnii," (^ means base, | means extent, |
| // < means start, and > means end). |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 19)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("p"); |
| TestDirectionExpand(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionExpandTransform) { |
| Text* text = SetupTransform("abcdef ghij kl mnopqr stuvwi inm mnii,"); |
| // "abcdef ghij kl mno^p|>qr stuvwi inm mnii," (^ means base, | means extent, |
| // < means start, and > means end). |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 19)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("p"); |
| TestDirectionExpand(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionExpandVerticalAlign) { |
| // "abcdef ghij kl mno^p|>qr stuvwi inm mnii," (^ means base, | means extent, |
| // < means start, and > means end). |
| SetupVerticalAlign("abcdef ghij kl m", "nopq", "r stuvwi inm mnii,", 18, 19); |
| EXPECT_EQ_SELECTED_TEXT("p"); |
| TestDirectionExpand(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionExpandFontSizes) { |
| SetupFontSize("abcdef ghij kl mnopqr st", "uv", "wi inm mnii,", 18, 19); |
| EXPECT_EQ_SELECTED_TEXT("p"); |
| TestDirectionExpand(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionShrinkTranslateZ) { |
| Text* text = SetupTranslateZ("abcdef ghij kl mnopqr iiinmni, abc"); |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 21)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionShrink(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionShrinkTransform) { |
| Text* text = SetupTransform("abcdef ghij kl mnopqr iiinmni, abc"); |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 21)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionShrink(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionShrinkVerticalAlign) { |
| SetupVerticalAlign("abcdef ghij kl mnopqr ii", "inm", "ni, abc", 18, 21); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionShrink(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionShrinkFontSizes) { |
| SetupFontSize("abcdef ghij kl mnopqr ii", "inm", "ni, abc", 18, 21); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionShrink(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionSwitchSideTranslateZ) { |
| Text* text = SetupTranslateZ("abcd efgh ijkl mnopqr iiinmni, abc"); |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 21)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionSwitchSide(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionSwitchSideTransform) { |
| Text* text = SetupTransform("abcd efgh ijkl mnopqr iiinmni, abc"); |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 21)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionSwitchSide(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionSwitchSideVerticalAlign) { |
| SetupVerticalAlign("abcd efgh ijkl", " mnopqr", " iiinmni, abc", 18, 21); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionSwitchSide(); |
| } |
| |
| TEST_F(GranularityStrategyTest, DirectionSwitchSideFontSizes) { |
| SetupFontSize("abcd efgh i", "jk", "l mnopqr iiinmni, abc", 18, 21); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| TestDirectionSwitchSide(); |
| } |
| |
| // Tests moving extent over to the other side of the vase and immediately |
| // passing the word boundary and going into word granularity. |
| TEST_F(GranularityStrategyTest, DirectionSwitchSideWordGranularityThenShrink) { |
| GetDummyPageHolder().GetFrame().GetSettings()->SetDefaultFontSize(12); |
| String str = "ab cd efghijkl mnopqr iiin, abc"; |
| Text* text = GetDocument().createTextNode(str); |
| GetDocument().body()->AppendChild(text); |
| GetDocument().UpdateStyleAndLayout(); |
| GetDummyPageHolder().GetFrame().GetSettings()->SetSelectionStrategy( |
| SelectionStrategy::kDirection); |
| |
| ParseText(text); |
| |
| // "abcd efgh ijkl mno^pqr|> iiin, abc" (^ means base, | means extent, < means |
| // start, and > means end). |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 18), Position(text, 21)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("pqr"); |
| // Move to the middle of word #4 selecting it - this will set the offset to |
| // be half the width of "iiin". |
| Selection().MoveRangeSelectionExtent(word_middles_[4]); |
| EXPECT_EQ_SELECTED_TEXT("pqr iiin"); |
| // Move to the middle of word #2 - extent will switch over to the other |
| // side of the base, and we should enter word granularity since we pass |
| // the word boundary. The offset should become negative since the width |
| // of "efghjkkl" is greater than that of "iiin". |
| int offset = letter_pos_[26].X() - word_middles_[4].X(); |
| IntPoint p = |
| IntPoint(word_middles_[2].X() - offset - 1, word_middles_[2].Y()); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("efghijkl mno"); |
| p.Move(letter_pos_[7].X() - letter_pos_[6].X(), 0); |
| Selection().MoveRangeSelectionExtent(p); |
| EXPECT_EQ_SELECTED_TEXT("fghijkl mno"); |
| } |
| |
| // Make sure we switch to word granularity right away when starting on a |
| // word boundary and extending. |
| TEST_F(GranularityStrategyTest, DirectionSwitchStartOnBoundary) { |
| GetDummyPageHolder().GetFrame().GetSettings()->SetDefaultFontSize(12); |
| String str = "ab cd efghijkl mnopqr iiin, abc"; |
| Text* text = GetDocument().createTextNode(str); |
| GetDocument().body()->AppendChild(text); |
| GetDocument().UpdateStyleAndLayout(); |
| GetDummyPageHolder().GetFrame().GetSettings()->SetSelectionStrategy( |
| SelectionStrategy::kDirection); |
| |
| ParseText(text); |
| |
| // "ab cd efghijkl ^mnopqr |>stuvwi inm," (^ means base and | means extent, |
| // > means end). |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 15), Position(text, 22)) |
| .Build()); |
| EXPECT_EQ_SELECTED_TEXT("mnopqr "); |
| Selection().MoveRangeSelectionExtent(word_middles_[4]); |
| EXPECT_EQ_SELECTED_TEXT("mnopqr iiin"); |
| } |
| |
| // For http://crbug.com/704529 |
| TEST_F(GranularityStrategyTest, UpdateExtentWithNullPositionForCharacter) { |
| GetDummyPageHolder().GetFrame().GetSettings()->SetSelectionStrategy( |
| SelectionStrategy::kCharacter); |
| GetDocument().body()->SetInnerHTMLFromString( |
| "<div id=host></div><div id=sample>ab</div>"); |
| // Simulate VIDEO element which has a RANGE as slider of video time. |
| Element* const host = GetDocument().getElementById("host"); |
| ShadowRoot& shadow_root = |
| host->AttachShadowRootInternal(ShadowRootType::kOpen); |
| shadow_root.SetInnerHTMLFromString("<input type=range>"); |
| Element* const sample = GetDocument().getElementById("sample"); |
| GetDocument().UpdateStyleAndLayout(); |
| const SelectionInDOMTree& selection_in_dom_tree = |
| SelectionInDOMTree::Builder() |
| .Collapse(Position(sample->firstChild(), 2)) |
| .SetIsDirectional(true) |
| .Build(); |
| Selection().SetSelection(selection_in_dom_tree, |
| SetSelectionOptions::Builder() |
| .SetShouldCloseTyping(true) |
| .SetShouldClearTypingStyle(true) |
| .SetShouldShowHandle(true) |
| .SetIsDirectional(true) |
| .Build()); |
| |
| // Since, it is not obvious that |visiblePositionForContentsPoint()| returns |
| // null position, we verify here. |
| ASSERT_EQ(Position(), |
| VisiblePositionForContentsPoint(IntPoint(0, 0), &GetFrame()) |
| .DeepEquivalent()) |
| << "This test requires null position."; |
| |
| // Point to RANGE inside shadow root to get null position from |
| // |visiblePositionForContentsPoint()|. |
| Selection().MoveRangeSelectionExtent(IntPoint(0, 0)); |
| EXPECT_EQ(selection_in_dom_tree, Selection().GetSelectionInDOMTree()); |
| } |
| |
| // For http://crbug.com/704529 |
| TEST_F(GranularityStrategyTest, UpdateExtentWithNullPositionForDirectional) { |
| GetDocument().body()->SetInnerHTMLFromString( |
| "<div id=host></div><div id=sample>ab</div>"); |
| // Simulate VIDEO element which has a RANGE as slider of video time. |
| Element* const host = GetDocument().getElementById("host"); |
| ShadowRoot& shadow_root = |
| host->AttachShadowRootInternal(ShadowRootType::kOpen); |
| shadow_root.SetInnerHTMLFromString("<input type=range>"); |
| Element* const sample = GetDocument().getElementById("sample"); |
| GetDocument().UpdateStyleAndLayout(); |
| const SelectionInDOMTree& selection_in_dom_tree = |
| SelectionInDOMTree::Builder() |
| .Collapse(Position(sample->firstChild(), 2)) |
| .SetIsDirectional(true) |
| .Build(); |
| Selection().SetSelection(selection_in_dom_tree, |
| SetSelectionOptions::Builder() |
| .SetShouldCloseTyping(true) |
| .SetShouldClearTypingStyle(true) |
| .SetShouldShowHandle(true) |
| .SetIsDirectional(true) |
| .Build()); |
| |
| // Since, it is not obvious that |visiblePositionForContentsPoint()| returns |
| // null position, we verify here. |
| ASSERT_EQ(Position(), |
| VisiblePositionForContentsPoint(IntPoint(0, 0), &GetFrame()) |
| .DeepEquivalent()) |
| << "This test requires null position."; |
| |
| // Point to RANGE inside shadow root to get null position from |
| // |visiblePositionForContentsPoint()|. |
| Selection().MoveRangeSelectionExtent(IntPoint(0, 0)); |
| |
| EXPECT_EQ(selection_in_dom_tree, Selection().GetSelectionInDOMTree()); |
| } |
| |
| } // namespace blink |