1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "core/html/HTMLTextFormControlElement.h"
8 #include "core/dom/Position.h"
9 #include "core/dom/Text.h"
10 #include "core/editing/FrameSelection.h"
11 #include "core/editing/SpellChecker.h"
12 #include "core/editing/VisibleSelection.h"
13 #include "core/editing/VisibleUnits.h"
14 #include "core/frame/FrameView.h"
15 #include "core/html/HTMLBRElement.h"
16 #include "core/html/HTMLDocument.h"
17 #include "core/html/HTMLInputElement.h"
18 #include "core/html/HTMLTextAreaElement.h"
19 #include "core/loader/EmptyClients.h"
20 #include "core/page/SpellCheckerClient.h"
21 #include "core/rendering/RenderTreeAsText.h"
22 #include "core/testing/DummyPageHolder.h"
23 #include "core/testing/UnitTestHelpers.h"
24 #include "wtf/OwnPtr.h"
25 #include <gtest/gtest.h>
27 using namespace blink;
31 class HTMLTextFormControlElementTest : public ::testing::Test {
33 virtual void SetUp() override;
35 DummyPageHolder& page() const { return *m_dummyPageHolder; }
36 HTMLDocument& document() const { return *m_document; }
37 HTMLTextFormControlElement& textControl() const { return *m_textControl; }
38 HTMLInputElement& input() const { return *m_input; }
40 int layoutCount() const { return page().frameView().layoutCount(); }
41 void forceLayoutFlag();
44 OwnPtr<SpellCheckerClient> m_spellCheckerClient;
45 OwnPtr<DummyPageHolder> m_dummyPageHolder;
47 RefPtrWillBePersistent<HTMLDocument> m_document;
48 RefPtrWillBePersistent<HTMLTextFormControlElement> m_textControl;
49 RefPtrWillBePersistent<HTMLInputElement> m_input;
52 class DummyTextCheckerClient : public EmptyTextCheckerClient {
54 ~DummyTextCheckerClient() { }
56 virtual bool shouldEraseMarkersAfterChangeSelection(TextCheckingType) const override { return false; }
59 class DummySpellCheckerClient : public EmptySpellCheckerClient {
61 virtual ~DummySpellCheckerClient() { }
63 virtual bool isContinuousSpellCheckingEnabled() override { return true; }
64 virtual bool isGrammarCheckingEnabled() override { return true; }
66 virtual TextCheckerClient& textChecker() override { return m_dummyTextCheckerClient; }
69 DummyTextCheckerClient m_dummyTextCheckerClient;
72 void HTMLTextFormControlElementTest::SetUp()
74 Page::PageClients pageClients;
75 fillWithEmptyClients(pageClients);
76 m_spellCheckerClient = adoptPtr(new DummySpellCheckerClient);
77 pageClients.spellCheckerClient = m_spellCheckerClient.get();
78 m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600), &pageClients);
80 m_document = toHTMLDocument(&m_dummyPageHolder->document());
81 m_document->documentElement()->setInnerHTML("<body><textarea id=textarea></textarea><input id=input /></body>", ASSERT_NO_EXCEPTION);
82 m_document->view()->updateLayoutAndStyleIfNeededRecursive();
83 m_textControl = toHTMLTextFormControlElement(m_document->getElementById("textarea"));
84 m_textControl->focus();
85 m_input = toHTMLInputElement(m_document->getElementById("input"));
88 void HTMLTextFormControlElementTest::forceLayoutFlag()
90 FrameView& frameView = page().frameView();
91 IntRect frameRect = frameView.frameRect();
92 frameRect.setWidth(frameRect.width() + 1);
93 frameRect.setHeight(frameRect.height() + 1);
94 page().frameView().setFrameRect(frameRect);
97 TEST_F(HTMLTextFormControlElementTest, SetSelectionRange)
99 EXPECT_EQ(0, textControl().selectionStart());
100 EXPECT_EQ(0, textControl().selectionEnd());
102 textControl().setInnerEditorValue("Hello, text form.");
103 EXPECT_EQ(0, textControl().selectionStart());
104 EXPECT_EQ(0, textControl().selectionEnd());
106 textControl().setSelectionRange(1, 3);
107 EXPECT_EQ(1, textControl().selectionStart());
108 EXPECT_EQ(3, textControl().selectionEnd());
111 TEST_F(HTMLTextFormControlElementTest, SetSelectionRangeDoesNotCauseLayout)
114 input().setValue("Hello, input form.");
115 input().setSelectionRange(1, 1);
116 FrameSelection& frameSelection = document().frame()->selection();
118 LayoutRect oldCaretRect = frameSelection.absoluteCaretBounds();
119 EXPECT_FALSE(oldCaretRect.isEmpty());
120 int startLayoutCount = layoutCount();
121 input().setSelectionRange(1, 1);
122 EXPECT_EQ(startLayoutCount, layoutCount());
123 LayoutRect newCaretRect = frameSelection.absoluteCaretBounds();
124 EXPECT_EQ(oldCaretRect, newCaretRect);
127 oldCaretRect = frameSelection.absoluteCaretBounds();
128 EXPECT_FALSE(oldCaretRect.isEmpty());
129 startLayoutCount = layoutCount();
130 input().setSelectionRange(2, 2);
131 EXPECT_EQ(startLayoutCount, layoutCount());
132 newCaretRect = frameSelection.absoluteCaretBounds();
133 EXPECT_NE(oldCaretRect, newCaretRect);
136 typedef Position (*PositionFunction)(const Position&);
137 typedef VisiblePosition(*VisblePositionFunction)(const VisiblePosition&);
139 void testFunctionEquivalence(const Position& position, PositionFunction positionFunction, VisblePositionFunction visibleFunction)
141 VisiblePosition visiblePosition(position);
142 VisiblePosition expected = visibleFunction(visiblePosition);
143 VisiblePosition actual = VisiblePosition(positionFunction(position));
144 EXPECT_EQ(expected, actual);
147 static VisiblePosition startOfWord(const VisiblePosition& position)
149 return startOfWord(position, LeftWordIfOnBoundary);
152 static VisiblePosition endOfWord(const VisiblePosition& position)
154 return endOfWord(position, RightWordIfOnBoundary);
157 void testBoundary(HTMLDocument& document, HTMLTextFormControlElement& textControl)
159 for (unsigned i = 0; i < textControl.innerEditorValue().length(); i++) {
160 textControl.setSelectionRange(i, i);
161 Position position = document.frame()->selection().start();
162 SCOPED_TRACE(::testing::Message() << "offset " << position.deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(position.deprecatedNode()).ascii().data());
164 SCOPED_TRACE("HTMLTextFormControlElement::startOfWord");
165 testFunctionEquivalence(position, HTMLTextFormControlElement::startOfWord, startOfWord);
168 SCOPED_TRACE("HTMLTextFormControlElement::endOfWord");
169 testFunctionEquivalence(position, HTMLTextFormControlElement::endOfWord, endOfWord);
172 SCOPED_TRACE("HTMLTextFormControlElement::startOfSentence");
173 testFunctionEquivalence(position, HTMLTextFormControlElement::startOfSentence, startOfSentence);
176 SCOPED_TRACE("HTMLTextFormControlElement::endOfSentence");
177 testFunctionEquivalence(position, HTMLTextFormControlElement::endOfSentence, endOfSentence);
182 TEST_F(HTMLTextFormControlElementTest, WordAndSentenceBoundary)
184 HTMLElement* innerText = textControl().innerEditorElement();
186 SCOPED_TRACE("String is value.");
187 innerText->removeChildren();
188 innerText->setNodeValue("Hel\nlo, text form.\n");
189 testBoundary(document(), textControl());
192 SCOPED_TRACE("A Text node and a BR element");
193 innerText->removeChildren();
194 innerText->setNodeValue("");
195 innerText->appendChild(Text::create(document(), "Hello, text form."));
196 innerText->appendChild(HTMLBRElement::create(document()));
197 testBoundary(document(), textControl());
200 SCOPED_TRACE("Text nodes.");
201 innerText->removeChildren();
202 innerText->setNodeValue("");
203 innerText->appendChild(Text::create(document(), "Hel\nlo, te"));
204 innerText->appendChild(Text::create(document(), "xt form."));
205 testBoundary(document(), textControl());
209 TEST_F(HTMLTextFormControlElementTest, SpellCheckDoesNotCauseUpdateLayout)
211 HTMLInputElement* input = toHTMLInputElement(document().getElementById("input"));
213 input->setValue("Hello, input field");
214 VisibleSelection oldSelection = document().frame()->selection().selection();
216 Position newPosition(input->innerEditorElement()->firstChild(), 3, Position::PositionIsOffsetInAnchor);
217 VisibleSelection newSelection(newPosition, DOWNSTREAM);
218 document().frame()->selection().setSelection(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::DoNotUpdateAppearance);
219 ASSERT_EQ(3, input->selectionStart());
221 OwnPtrWillBePersistent<SpellChecker> spellChecker(SpellChecker::create(page().frame()));
223 int startCount = layoutCount();
224 spellChecker->respondToChangedSelection(oldSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
225 EXPECT_EQ(startCount, layoutCount());