blob: ebeb699f85b17feaf7815545e76414b2068777c0 [file] [log] [blame]
// 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 "core/html/HTMLTextFormControlElement.h"
#include "core/dom/Document.h"
#include "core/dom/Text.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/Position.h"
#include "core/editing/VisibleSelection.h"
#include "core/editing/VisibleUnits.h"
#include "core/editing/spellcheck/SpellChecker.h"
#include "core/frame/FrameView.h"
#include "core/html/HTMLBRElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLTextAreaElement.h"
#include "core/layout/LayoutTreeAsText.h"
#include "core/loader/EmptyClients.h"
#include "core/page/SpellCheckerClient.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/testing/UnitTestHelpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/PtrUtil.h"
#include <memory>
namespace blink {
class HTMLTextFormControlElementTest : public ::testing::Test {
protected:
void SetUp() override;
DummyPageHolder& page() const { return *m_dummyPageHolder; }
Document& document() const { return *m_document; }
HTMLTextFormControlElement& textControl() const { return *m_textControl; }
HTMLInputElement& input() const { return *m_input; }
int layoutCount() const { return page().frameView().layoutCount(); }
void forceLayoutFlag();
private:
std::unique_ptr<SpellCheckerClient> m_spellCheckerClient;
std::unique_ptr<DummyPageHolder> m_dummyPageHolder;
Persistent<Document> m_document;
Persistent<HTMLTextFormControlElement> m_textControl;
Persistent<HTMLInputElement> m_input;
};
class DummySpellCheckerClient : public EmptySpellCheckerClient {
public:
virtual ~DummySpellCheckerClient() { }
bool isContinuousSpellCheckingEnabled() override { return true; }
TextCheckerClient& textChecker() override { return m_emptyTextCheckerClient; }
private:
EmptyTextCheckerClient m_emptyTextCheckerClient;
};
void HTMLTextFormControlElementTest::SetUp()
{
Page::PageClients pageClients;
fillWithEmptyClients(pageClients);
m_spellCheckerClient = wrapUnique(new DummySpellCheckerClient);
pageClients.spellCheckerClient = m_spellCheckerClient.get();
m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600), &pageClients);
m_document = &m_dummyPageHolder->document();
m_document->documentElement()->setInnerHTML("<body><textarea id=textarea></textarea><input id=input /></body>", ASSERT_NO_EXCEPTION);
m_document->view()->updateAllLifecyclePhases();
m_textControl = toHTMLTextFormControlElement(m_document->getElementById("textarea"));
m_textControl->focus();
m_input = toHTMLInputElement(m_document->getElementById("input"));
}
void HTMLTextFormControlElementTest::forceLayoutFlag()
{
FrameView& frameView = page().frameView();
IntRect frameRect = frameView.frameRect();
frameRect.setWidth(frameRect.width() + 1);
frameRect.setHeight(frameRect.height() + 1);
page().frameView().setFrameRect(frameRect);
}
TEST_F(HTMLTextFormControlElementTest, SetSelectionRange)
{
EXPECT_EQ(0, textControl().selectionStart());
EXPECT_EQ(0, textControl().selectionEnd());
textControl().setInnerEditorValue("Hello, text form.");
EXPECT_EQ(0, textControl().selectionStart());
EXPECT_EQ(0, textControl().selectionEnd());
textControl().setSelectionRange(1, 3);
EXPECT_EQ(1, textControl().selectionStart());
EXPECT_EQ(3, textControl().selectionEnd());
}
TEST_F(HTMLTextFormControlElementTest, SetSelectionRangeDoesNotCauseLayout)
{
input().focus();
input().setValue("Hello, input form.");
input().setSelectionRange(1, 1);
FrameSelection& frameSelection = document().frame()->selection();
forceLayoutFlag();
LayoutRect oldCaretRect(frameSelection.absoluteCaretBounds());
EXPECT_FALSE(oldCaretRect.isEmpty());
int startLayoutCount = layoutCount();
input().setSelectionRange(1, 1);
EXPECT_EQ(startLayoutCount, layoutCount());
LayoutRect newCaretRect(frameSelection.absoluteCaretBounds());
EXPECT_EQ(oldCaretRect, newCaretRect);
forceLayoutFlag();
oldCaretRect = LayoutRect(frameSelection.absoluteCaretBounds());
EXPECT_FALSE(oldCaretRect.isEmpty());
startLayoutCount = layoutCount();
input().setSelectionRange(2, 2);
EXPECT_EQ(startLayoutCount, layoutCount());
newCaretRect = LayoutRect(frameSelection.absoluteCaretBounds());
EXPECT_NE(oldCaretRect, newCaretRect);
}
typedef Position (*PositionFunction)(const Position&);
typedef VisiblePosition(*VisblePositionFunction)(const VisiblePosition&);
void testFunctionEquivalence(const Position& position, PositionFunction positionFunction, VisblePositionFunction visibleFunction)
{
VisiblePosition visiblePosition = createVisiblePosition(position);
VisiblePosition expected = visibleFunction(visiblePosition);
VisiblePosition actual = createVisiblePosition(positionFunction(position));
EXPECT_EQ(expected.deepEquivalent(), actual.deepEquivalent());
}
static VisiblePosition startOfWord(const VisiblePosition& position)
{
return startOfWord(position, LeftWordIfOnBoundary);
}
static VisiblePosition endOfWord(const VisiblePosition& position)
{
return endOfWord(position, RightWordIfOnBoundary);
}
void testBoundary(Document& document, HTMLTextFormControlElement& textControl)
{
for (unsigned i = 0; i < textControl.innerEditorValue().length(); i++) {
textControl.setSelectionRange(i, i);
Position position = document.frame()->selection().start();
SCOPED_TRACE(::testing::Message() << "offset " << position.computeEditingOffset() << " of " << nodePositionAsStringForTesting(position.anchorNode()).ascii().data());
{
SCOPED_TRACE("HTMLTextFormControlElement::startOfWord");
testFunctionEquivalence(position, HTMLTextFormControlElement::startOfWord, startOfWord);
}
{
SCOPED_TRACE("HTMLTextFormControlElement::endOfWord");
testFunctionEquivalence(position, HTMLTextFormControlElement::endOfWord, endOfWord);
}
{
SCOPED_TRACE("HTMLTextFormControlElement::startOfSentence");
testFunctionEquivalence(position, HTMLTextFormControlElement::startOfSentence, startOfSentence);
}
{
SCOPED_TRACE("HTMLTextFormControlElement::endOfSentence");
testFunctionEquivalence(position, HTMLTextFormControlElement::endOfSentence, endOfSentence);
}
}
}
TEST_F(HTMLTextFormControlElementTest, WordAndSentenceBoundary)
{
HTMLElement* innerText = textControl().innerEditorElement();
{
SCOPED_TRACE("String is value.");
innerText->removeChildren();
innerText->setNodeValue("Hel\nlo, text form.\n");
testBoundary(document(), textControl());
}
{
SCOPED_TRACE("A Text node and a BR element");
innerText->removeChildren();
innerText->setNodeValue("");
innerText->appendChild(Text::create(document(), "Hello, text form."));
innerText->appendChild(HTMLBRElement::create(document()));
testBoundary(document(), textControl());
}
{
SCOPED_TRACE("Text nodes.");
innerText->removeChildren();
innerText->setNodeValue("");
innerText->appendChild(Text::create(document(), "Hel\nlo, te"));
innerText->appendChild(Text::create(document(), "xt form."));
testBoundary(document(), textControl());
}
}
TEST_F(HTMLTextFormControlElementTest, SpellCheckDoesNotCauseUpdateLayout)
{
HTMLInputElement* input = toHTMLInputElement(document().getElementById("input"));
input->focus();
input->setValue("Hello, input field");
VisibleSelection oldSelection = document().frame()->selection().selection();
Position newPosition(input->innerEditorElement()->firstChild(), 3);
VisibleSelection newSelection(newPosition, TextAffinity::Downstream);
document().frame()->selection().setSelection(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::DoNotUpdateAppearance);
ASSERT_EQ(3, input->selectionStart());
Persistent<SpellChecker> spellChecker(SpellChecker::create(page().frame()));
forceLayoutFlag();
int startCount = layoutCount();
spellChecker->respondToChangedSelection(oldSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
EXPECT_EQ(startCount, layoutCount());
}
} // namespace blink