blob: 2e3e6345fd5570b39e0fc785b68373085c9dc3ad [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/editing/InputMethodController.h"
#include <memory>
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/Range.h"
#include "core/editing/Editor.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/markers/DocumentMarkerController.h"
#include "core/events/MouseEvent.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLTextAreaElement.h"
#include "core/testing/DummyPageHolder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class InputMethodControllerTest : public ::testing::Test {
protected:
InputMethodController& Controller() {
return GetFrame().GetInputMethodController();
}
Document& GetDocument() const { return *document_; }
LocalFrame& GetFrame() const { return dummy_page_holder_->GetFrame(); }
Element* InsertHTMLElement(const char* element_code, const char* element_id);
void CreateHTMLWithCompositionInputEventListeners();
void CreateHTMLWithCompositionEndEventListener(const SelectionType);
private:
void SetUp() override;
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
Persistent<Document> document_;
};
void InputMethodControllerTest::SetUp() {
dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
document_ = &dummy_page_holder_->GetDocument();
DCHECK(document_);
}
Element* InputMethodControllerTest::InsertHTMLElement(const char* element_code,
const char* element_id) {
GetDocument().write(element_code);
GetDocument().UpdateStyleAndLayout();
Element* element = GetDocument().getElementById(element_id);
element->focus();
return element;
}
void InputMethodControllerTest::CreateHTMLWithCompositionInputEventListeners() {
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* editable =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Element* script = GetDocument().createElement("script");
script->setInnerHTML(
"document.getElementById('sample').addEventListener('beforeinput', "
" event => document.title = `beforeinput.data:${event.data};`);"
"document.getElementById('sample').addEventListener('input', "
" event => document.title += `input.data:${event.data};`);"
"document.getElementById('sample').addEventListener('compositionend', "
" event => document.title += `compositionend.data:${event.data};`);");
GetDocument().body()->AppendChild(script);
GetDocument().View()->UpdateAllLifecyclePhases();
editable->focus();
}
void InputMethodControllerTest::CreateHTMLWithCompositionEndEventListener(
const SelectionType type) {
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* editable =
InsertHTMLElement("<div id='sample' contentEditable></div>", "sample");
Element* script = GetDocument().createElement("script");
switch (type) {
case kNoSelection:
script->setInnerHTML(
// If the caret position is set before firing 'compositonend' event
// (and it should), the final caret position will be reset to null.
"document.getElementById('sample').addEventListener('compositionend',"
" event => getSelection().removeAllRanges());");
break;
case kCaretSelection:
script->setInnerHTML(
// If the caret position is set before firing 'compositonend' event
// (and it should), the final caret position will be reset to [3,3].
"document.getElementById('sample').addEventListener('compositionend',"
" event => {"
" const node = document.getElementById('sample').firstChild;"
" getSelection().collapse(node, 3);"
"});");
break;
case kRangeSelection:
script->setInnerHTML(
// If the caret position is set before firing 'compositonend' event
// (and it should), the final caret position will be reset to [2,4].
"document.getElementById('sample').addEventListener('compositionend',"
" event => {"
" const node = document.getElementById('sample').firstChild;"
" const selection = getSelection();"
" selection.collapse(node, 2);"
" selection.extend(node, 4);"
"});");
break;
default:
NOTREACHED();
}
GetDocument().body()->AppendChild(script);
GetDocument().View()->UpdateAllLifecyclePhases();
editable->focus();
}
TEST_F(InputMethodControllerTest, BackspaceFromEndOfInput) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("fooX");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("fooX", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(0, 0);
EXPECT_STREQ("fooX", input->value().Utf8().data());
input->setValue("fooX");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("fooX", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
input->setValue(
String::FromUTF8("foo\xE2\x98\x85")); // U+2605 == "black star"
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("foo\xE2\x98\x85", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
input->setValue(
String::FromUTF8("foo\xF0\x9F\x8F\x86")); // U+1F3C6 == "trophy"
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("foo\xF0\x9F\x8F\x86", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
// composed U+0E01 "ka kai" + U+0E49 "mai tho"
input->setValue(String::FromUTF8("foo\xE0\xB8\x81\xE0\xB9\x89"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("foo\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
input->setValue("fooX");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("fooX", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(0, 1);
EXPECT_STREQ("fooX", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, SetCompositionFromExistingText) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>hello world</div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 0, 5);
Range* range = Controller().CompositionRange();
EXPECT_EQ(0u, range->startOffset());
EXPECT_EQ(5u, range->endOffset());
PlainTextRange plain_text_range(PlainTextRange::Create(*div, *range));
EXPECT_EQ(0u, plain_text_range.Start());
EXPECT_EQ(5u, plain_text_range.end());
}
TEST_F(InputMethodControllerTest, SetCompositionAfterEmoji) {
// "trophy" = U+1F3C6 = 0xF0 0x9F 0x8F 0x86 (UTF8).
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>&#x1f3c6</div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
EXPECT_EQ(2, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Start()
.ComputeOffsetInContainerNode());
EXPECT_EQ(2, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.end()
.ComputeOffsetInContainerNode());
Controller().SetComposition(String("a"), underlines, 1, 1);
EXPECT_STREQ("\xF0\x9F\x8F\x86\x61", div->innerText().Utf8().data());
Controller().SetComposition(String("ab"), underlines, 2, 2);
EXPECT_STREQ("\xF0\x9F\x8F\x86\x61\x62", div->innerText().Utf8().data());
}
TEST_F(InputMethodControllerTest, SetCompositionWithGraphemeCluster) {
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(6, 6, Color(255, 0, 0), false, 0));
GetDocument().UpdateStyleAndLayout();
// UTF16 = 0x0939 0x0947 0x0932 0x0932. Note that 0x0932 0x0932 is a grapheme
// cluster.
Controller().SetComposition(
String::FromUTF8("\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA4\xB2"),
underlines, 4, 4);
EXPECT_EQ(4u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(4u, Controller().GetSelectionOffsets().end());
// UTF16 = 0x0939 0x0947 0x0932 0x094D 0x0932 0x094B.
Controller().SetComposition(
String::FromUTF8("\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA5\x8D\xE0"
"\xA4\xB2\xE0\xA5\x8B"),
underlines, 6, 6);
EXPECT_EQ(6u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(6u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest,
SetCompositionWithGraphemeClusterAndMultipleNodes) {
Element* div =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(
CompositionUnderline(12, 12, Color(255, 0, 0), false, 0));
GetDocument().UpdateStyleAndLayout();
// UTF16 = 0x0939 0x0947 0x0932 0x094D 0x0932 0x094B. 0x0939 0x0947 0x0932 is
// a grapheme cluster, so is the remainding 0x0932 0x094B.
Controller().CommitText(
String::FromUTF8("\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA5\x8D\xE0"
"\xA4\xB2\xE0\xA5\x8B"),
underlines, 1);
Controller().CommitText("\nab ", underlines, 1);
Controller().SetComposition(String("c"), underlines, 1, 1);
EXPECT_STREQ(
"\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA5\x8D\xE0\xA4\xB2\xE0\xA5"
"\x8B\nab c",
div->innerText().Utf8().data());
EXPECT_EQ(11u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(11u, Controller().GetSelectionOffsets().end());
Controller().SetComposition(String("cd"), underlines, 2, 2);
EXPECT_STREQ(
"\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA5\x8D\xE0\xA4\xB2\xE0\xA5"
"\x8B\nab cd",
div->innerText().Utf8().data());
EXPECT_EQ(12u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(12u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest, SetCompositionKeepingStyle) {
Element* div = InsertHTMLElement(
"<div id='sample' "
"contenteditable>abc1<b>2</b>34567<b>8</b>9d<b>e</b>f</div>",
"sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(3, 12, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 3, 12);
// Subtract a character.
Controller().SetComposition(String("12345789"), underlines, 8, 8);
EXPECT_STREQ("abc1<b>2</b>3457<b>8</b>9d<b>e</b>f",
div->innerHTML().Utf8().data());
EXPECT_EQ(11u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(11u, Controller().GetSelectionOffsets().end());
// Append a character.
Controller().SetComposition(String("123456789"), underlines, 9, 9);
EXPECT_STREQ("abc1<b>2</b>34567<b>8</b>9d<b>e</b>f",
div->innerHTML().Utf8().data());
EXPECT_EQ(12u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(12u, Controller().GetSelectionOffsets().end());
// Subtract and append characters.
Controller().SetComposition(String("123hello789"), underlines, 11, 11);
EXPECT_STREQ("abc1<b>2</b>3hello7<b>8</b>9d<b>e</b>f",
div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, SetCompositionWithEmojiKeepingStyle) {
// U+1F3E0 = 0xF0 0x9F 0x8F 0xA0 (UTF8). It's an emoji character.
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable><b>&#x1f3e0</b></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 0, 2);
// 0xF0 0x9F 0x8F 0xAB is also an emoji character, with the same leading
// surrogate pair to the previous one.
Controller().SetComposition(String::FromUTF8("\xF0\x9F\x8F\xAB"), underlines,
2, 2);
EXPECT_STREQ("<b>\xF0\x9F\x8F\xAB</b>", div->innerHTML().Utf8().data());
Controller().SetComposition(String::FromUTF8("\xF0\x9F\x8F\xA0"), underlines,
2, 2);
EXPECT_STREQ("<b>\xF0\x9F\x8F\xA0</b>", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest,
SetCompositionWithTeluguSignVisargaKeepingStyle) {
// U+0C03 = 0xE0 0xB0 0x83 (UTF8), a telugu sign visarga with one code point.
// It's one grapheme cluster if separated. It can also form one grapheme
// cluster with another code point(e.g, itself).
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable><b>&#xc03</b></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 0, 1);
// 0xE0 0xB0 0x83 0xE0 0xB0 0x83, a telugu character with 2 code points in
// 1 grapheme cluster.
Controller().SetComposition(String::FromUTF8("\xE0\xB0\x83\xE0\xB0\x83"),
underlines, 2, 2);
EXPECT_STREQ("<b>\xE0\xB0\x83\xE0\xB0\x83</b>",
div->innerHTML().Utf8().data());
Controller().SetComposition(String::FromUTF8("\xE0\xB0\x83"), underlines, 1,
1);
EXPECT_STREQ("<b>\xE0\xB0\x83</b>", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, FinishComposingTextKeepingStyle) {
Element* div = InsertHTMLElement(
"<div id='sample' "
"contenteditable>abc1<b>2</b>34567<b>8</b>9</div>",
"sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(3, 12, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 3, 12);
Controller().SetComposition(String("123hello789"), underlines, 11, 11);
EXPECT_STREQ("abc1<b>2</b>3hello7<b>8</b>9", div->innerHTML().Utf8().data());
Controller().FinishComposingText(InputMethodController::kKeepSelection);
EXPECT_STREQ("abc1<b>2</b>3hello7<b>8</b>9", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, CommitTextKeepingStyle) {
Element* div = InsertHTMLElement(
"<div id='sample' "
"contenteditable>abc1<b>2</b>34567<b>8</b>9</div>",
"sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(3, 12, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 3, 12);
Controller().CommitText(String("123789"), underlines, 0);
EXPECT_STREQ("abc1<b>2</b>37<b>8</b>9", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, InsertTextWithNewLine) {
Element* div =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 11, Color(255, 0, 0), false, 0));
Controller().CommitText(String("hello\nworld"), underlines, 0);
EXPECT_STREQ("hello<div>world</div>", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, InsertTextWithNewLineIncrementally) {
Element* div =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 11, Color(255, 0, 0), false, 0));
Controller().SetComposition("foo", underlines, 0, 2);
EXPECT_STREQ("foo", div->innerHTML().Utf8().data());
Controller().CommitText(String("hello\nworld"), underlines, 0);
EXPECT_STREQ("hello<div>world</div>", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, SelectionOnConfirmExistingText) {
InsertHTMLElement("<div id='sample' contenteditable>hello world</div>",
"sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 0, 5);
Controller().FinishComposingText(InputMethodController::kKeepSelection);
EXPECT_EQ(0, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Start()
.ComputeOffsetInContainerNode());
EXPECT_EQ(0, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.end()
.ComputeOffsetInContainerNode());
}
TEST_F(InputMethodControllerTest, DeleteBySettingEmptyComposition) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("foo ");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("foo ", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(0, 0);
EXPECT_STREQ("foo ", input->value().Utf8().data());
input->setValue("foo ");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("foo ", input->value().Utf8().data());
Controller().ExtendSelectionAndDelete(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 3, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 0, 3);
Controller().SetComposition(String(""), underlines, 0, 3);
EXPECT_STREQ("", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest,
SetCompositionFromExistingTextWithCollapsedWhiteSpace) {
// Creates a div with one leading new line char. The new line char is hidden
// from the user and IME, but is visible to InputMethodController.
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>\nhello world</div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 0, 5);
Range* range = Controller().CompositionRange();
EXPECT_EQ(1u, range->startOffset());
EXPECT_EQ(6u, range->endOffset());
PlainTextRange plain_text_range(PlainTextRange::Create(*div, *range));
EXPECT_EQ(0u, plain_text_range.Start());
EXPECT_EQ(5u, plain_text_range.end());
}
TEST_F(InputMethodControllerTest,
SetCompositionFromExistingTextWithInvalidOffsets) {
InsertHTMLElement("<div id='sample' contenteditable>test</div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(7, 8, Color(255, 0, 0), false, 0));
Controller().SetCompositionFromExistingText(underlines, 7, 8);
EXPECT_FALSE(Controller().CompositionRange());
}
TEST_F(InputMethodControllerTest, ConfirmPasswordComposition) {
HTMLInputElement* input = toHTMLInputElement(InsertHTMLElement(
"<input id='sample' type='password' size='24'>", "sample"));
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetComposition("foo", underlines, 0, 3);
Controller().FinishComposingText(InputMethodController::kKeepSelection);
EXPECT_STREQ("foo", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithEmptyText) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("", input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 0);
EXPECT_STREQ("", input->value().Utf8().data());
input->setValue("");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("", input->value().Utf8().data());
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("", input->value().Utf8().data());
input->setValue("");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("", input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 1);
EXPECT_STREQ("", input->value().Utf8().data());
input->setValue("");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("", input->value().Utf8().data());
Controller().DeleteSurroundingText(1, 1);
EXPECT_STREQ("", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithRangeSelection) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4));
Controller().DeleteSurroundingText(0, 0);
EXPECT_STREQ("hello", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4));
Controller().DeleteSurroundingText(1, 1);
EXPECT_STREQ("ell", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4));
Controller().DeleteSurroundingText(100, 0);
EXPECT_STREQ("ello", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4));
Controller().DeleteSurroundingText(0, 100);
EXPECT_STREQ("hell", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4));
Controller().DeleteSurroundingText(100, 100);
EXPECT_STREQ("ell", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithCursorSelection) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("hllo", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(0, 1);
EXPECT_STREQ("helo", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(0, 0);
EXPECT_STREQ("hello", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(1, 1);
EXPECT_STREQ("hlo", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(100, 0);
EXPECT_STREQ("llo", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(0, 100);
EXPECT_STREQ("he", input->value().Utf8().data());
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("hello", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
Controller().DeleteSurroundingText(100, 100);
EXPECT_STREQ("", input->value().Utf8().data());
input->setValue("h");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("h", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(1, 1));
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("", input->value().Utf8().data());
input->setValue("h");
GetDocument().UpdateStyleAndLayout();
EXPECT_STREQ("h", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
Controller().DeleteSurroundingText(0, 1);
EXPECT_STREQ("", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextWithMultiCodeTextOnTheLeft) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// U+2605 == "black star". It takes up 1 space.
input->setValue(String::FromUTF8("foo\xE2\x98\x85"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
EXPECT_STREQ("foo\xE2\x98\x85", input->value().Utf8().data());
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
// U+1F3C6 == "trophy". It takes up 2 space.
input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(5, 5));
EXPECT_STREQ("foo\xF0\x9F\x8F\x86", input->value().Utf8().data());
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
// composed U+0E01 "ka kai" + U+0E49 "mai tho". It takes up 2 space.
input->setValue(String::FromUTF8("foo\xE0\xB8\x81\xE0\xB9\x89"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(5, 5));
EXPECT_STREQ("foo\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8().data());
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7));
EXPECT_STREQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86",
input->value().Utf8().data());
Controller().DeleteSurroundingText(2, 0);
EXPECT_STREQ("foo\xF0\x9F\x8F\x86", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7));
EXPECT_STREQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86",
input->value().Utf8().data());
Controller().DeleteSurroundingText(3, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7));
EXPECT_STREQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86",
input->value().Utf8().data());
Controller().DeleteSurroundingText(4, 0);
EXPECT_STREQ("foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7));
EXPECT_STREQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86",
input->value().Utf8().data());
Controller().DeleteSurroundingText(5, 0);
EXPECT_STREQ("fo", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextWithMultiCodeTextOnTheRight) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// U+2605 == "black star". It takes up 1 space.
input->setValue(String::FromUTF8("\xE2\x98\x85 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xE2\x98\x85 foo", input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 1);
EXPECT_STREQ(" foo", input->value().Utf8().data());
// U+1F3C6 == "trophy". It takes up 2 space.
input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xF0\x9F\x8F\x86 foo", input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 1);
EXPECT_STREQ(" foo", input->value().Utf8().data());
// composed U+0E01 "ka kai" + U+0E49 "mai tho". It takes up 2 space.
input->setValue(String::FromUTF8("\xE0\xB8\x81\xE0\xB9\x89 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xE0\xB8\x81\xE0\xB9\x89 foo", input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 1);
EXPECT_STREQ(" foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo",
input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 2);
EXPECT_STREQ("\xF0\x9F\x8F\x86 foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo",
input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 3);
EXPECT_STREQ(" foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo",
input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 4);
EXPECT_STREQ(" foo", input->value().Utf8().data());
// "trophy" + "trophy".
input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
EXPECT_STREQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo",
input->value().Utf8().data());
Controller().DeleteSurroundingText(0, 5);
EXPECT_STREQ("foo", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextWithMultiCodeTextOnBothSides) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// "trophy" + "trophy".
input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
EXPECT_STREQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86",
input->value().Utf8().data());
Controller().DeleteSurroundingText(1, 1);
EXPECT_STREQ("", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, DeleteSurroundingTextForMultipleNodes) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>aaa"
"<div id='sample2' contenteditable>bbb"
"<div id='sample3' contenteditable>ccc"
"<div id='sample4' contenteditable>ddd"
"<div id='sample5' contenteditable>eee"
"</div></div></div></div></div>",
"sample");
Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8));
EXPECT_STREQ("aaa\nbbb\nccc\nddd\neee", div->innerText().Utf8().data());
EXPECT_EQ(8u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(8u, Controller().GetSelectionOffsets().end());
Controller().DeleteSurroundingText(1, 0);
EXPECT_STREQ("aaa\nbbbccc\nddd\neee", div->innerText().Utf8().data());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().end());
Controller().DeleteSurroundingText(0, 4);
EXPECT_STREQ("aaa\nbbbddd\neee", div->innerText().Utf8().data());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().end());
Controller().DeleteSurroundingText(5, 5);
EXPECT_STREQ("aaee", div->innerText().Utf8().data());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextInCodePointsWithMultiCodeTextOnTheLeft) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// 'a' + "black star" + SPACE + "trophy" + SPACE + composed text (U+0E01
// "ka kai" + U+0E49 "mai tho").
// A "black star" is 1 grapheme cluster. It has 1 code point, and its length
// is 1 (abbreviated as [1,1,1]). A "trophy": [1,1,2]. The composed text:
// [1,2,2].
input->setValue(String::FromUTF8(
"a\xE2\x98\x85 \xF0\x9F\x8F\x86 \xE0\xB8\x81\xE0\xB9\x89"));
GetDocument().UpdateStyleAndLayout();
// The cursor is at the end of the text.
Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8));
Controller().DeleteSurroundingTextInCodePoints(2, 0);
EXPECT_STREQ("a\xE2\x98\x85 \xF0\x9F\x8F\x86 ", input->value().Utf8().data());
Controller().DeleteSurroundingTextInCodePoints(4, 0);
EXPECT_STREQ("a", input->value().Utf8().data());
// 'a' + "black star" + SPACE + "trophy" + SPACE + composed text
input->setValue(String::FromUTF8(
"a\xE2\x98\x85 \xF0\x9F\x8F\x86 \xE0\xB8\x81\xE0\xB9\x89"));
GetDocument().UpdateStyleAndLayout();
// The cursor is at the end of the text.
Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8));
// TODO(yabinh): We should only delete 1 code point instead of the entire
// grapheme cluster (2 code points). The root cause is that we adjust the
// selection by grapheme cluster in deleteSurroundingText().
Controller().DeleteSurroundingTextInCodePoints(1, 0);
EXPECT_STREQ("a\xE2\x98\x85 \xF0\x9F\x8F\x86 ", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextInCodePointsWithMultiCodeTextOnTheRight) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// 'a' + "black star" + SPACE + "trophy" + SPACE + composed text
input->setValue(String::FromUTF8(
"a\xE2\x98\x85 \xF0\x9F\x8F\x86 \xE0\xB8\x81\xE0\xB9\x89"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
Controller().DeleteSurroundingTextInCodePoints(0, 5);
EXPECT_STREQ("\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8().data());
Controller().DeleteSurroundingTextInCodePoints(0, 1);
// TODO(yabinh): Same here. We should only delete 1 code point.
EXPECT_STREQ("", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextInCodePointsWithMultiCodeTextOnBothSides) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// 'a' + "black star" + SPACE + "trophy" + SPACE + composed text
input->setValue(String::FromUTF8(
"a\xE2\x98\x85 \xF0\x9F\x8F\x86 \xE0\xB8\x81\xE0\xB9\x89"));
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(3, 3));
Controller().DeleteSurroundingTextInCodePoints(2, 2);
EXPECT_STREQ("a\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, DeleteSurroundingTextInCodePointsWithImage) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>aaa"
"<img src='empty.png'>bbb</div>",
"sample");
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
Controller().DeleteSurroundingTextInCodePoints(1, 1);
EXPECT_STREQ("aaabb", div->innerText().Utf8().data());
EXPECT_EQ(3u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(3u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest,
DeleteSurroundingTextInCodePointsWithInvalidSurrogatePair) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
// 'a' + high surrogate of "trophy" + "black star" + low surrogate of "trophy"
// + SPACE
const UChar kUText[] = {'a', 0xD83C, 0x2605, 0xDFC6, ' ', '\0'};
const String& text = String(kUText);
input->setValue(text);
GetDocument().UpdateStyleAndLayout();
// The invalid high surrogate is encoded as '\xED\xA0\xBC', and invalid low
// surrogate is encoded as '\xED\xBF\x86'.
EXPECT_STREQ("a\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86 ",
input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(5, 5));
// Delete a SPACE.
Controller().DeleteSurroundingTextInCodePoints(1, 0);
EXPECT_STREQ("a\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86",
input->value().Utf8().data());
// Do nothing since there is an invalid surrogate in the requested range.
Controller().DeleteSurroundingTextInCodePoints(2, 0);
EXPECT_STREQ("a\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86",
input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0));
// Delete 'a'.
Controller().DeleteSurroundingTextInCodePoints(0, 1);
EXPECT_STREQ("\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86",
input->value().Utf8().data());
// Do nothing since there is an invalid surrogate in the requested range.
Controller().DeleteSurroundingTextInCodePoints(0, 2);
EXPECT_STREQ("\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86",
input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, SetCompositionForInputWithNewCaretPositions) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("hello");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
EXPECT_STREQ("hello", input->value().Utf8().data());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().end());
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
// The caret exceeds left boundary.
// "*heABllo", where * stands for caret.
Controller().SetComposition("AB", underlines, -100, -100);
EXPECT_STREQ("heABllo", input->value().Utf8().data());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().end());
// The caret is on left boundary.
// "*heABllo".
Controller().SetComposition("AB", underlines, -2, -2);
EXPECT_STREQ("heABllo", input->value().Utf8().data());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().end());
// The caret is before the composing text.
// "he*ABllo".
Controller().SetComposition("AB", underlines, 0, 0);
EXPECT_STREQ("heABllo", input->value().Utf8().data());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().end());
// The caret is after the composing text.
// "heAB*llo".
Controller().SetComposition("AB", underlines, 2, 2);
EXPECT_STREQ("heABllo", input->value().Utf8().data());
EXPECT_EQ(4u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(4u, Controller().GetSelectionOffsets().end());
// The caret is on right boundary.
// "heABllo*".
Controller().SetComposition("AB", underlines, 5, 5);
EXPECT_STREQ("heABllo", input->value().Utf8().data());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().end());
// The caret exceeds right boundary.
// "heABllo*".
Controller().SetComposition("AB", underlines, 100, 100);
EXPECT_STREQ("heABllo", input->value().Utf8().data());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(7u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest,
SetCompositionForContentEditableWithNewCaretPositions) {
// There are 7 nodes and 5+1+5+1+3+4+3 characters: "hello", '\n', "world",
// "\n", "012", "3456", "789".
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>"
"hello"
"<div id='sample2' contenteditable>world"
"<p>012<b>3456</b><i>789</i></p>"
"</div>"
"</div>",
"sample");
Controller().SetEditableSelectionOffsets(PlainTextRange(17, 17));
EXPECT_STREQ("hello\nworld\n0123456789", div->innerText().Utf8().data());
EXPECT_EQ(17u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(17u, Controller().GetSelectionOffsets().end());
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
// The caret exceeds left boundary.
// "*hello\nworld\n01234AB56789", where * stands for caret.
Controller().SetComposition("AB", underlines, -100, -100);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().end());
// The caret is on left boundary.
// "*hello\nworld\n01234AB56789".
Controller().SetComposition("AB", underlines, -17, -17);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(0u, Controller().GetSelectionOffsets().end());
// The caret is in the 1st node.
// "he*llo\nworld\n01234AB56789".
Controller().SetComposition("AB", underlines, -15, -15);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().end());
// The caret is on right boundary of the 1st node.
// "hello*\nworld\n01234AB56789".
Controller().SetComposition("AB", underlines, -12, -12);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(5u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(5u, Controller().GetSelectionOffsets().end());
// The caret is on right boundary of the 2nd node.
// "hello\n*world\n01234AB56789".
Controller().SetComposition("AB", underlines, -11, -11);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(6u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(6u, Controller().GetSelectionOffsets().end());
// The caret is on right boundary of the 3rd node.
// "hello\nworld*\n01234AB56789".
Controller().SetComposition("AB", underlines, -6, -6);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(11u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(11u, Controller().GetSelectionOffsets().end());
// The caret is on right boundary of the 4th node.
// "hello\nworld\n*01234AB56789".
Controller().SetComposition("AB", underlines, -5, -5);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(12u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(12u, Controller().GetSelectionOffsets().end());
// The caret is before the composing text.
// "hello\nworld\n01234*AB56789".
Controller().SetComposition("AB", underlines, 0, 0);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(17u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(17u, Controller().GetSelectionOffsets().end());
// The caret is after the composing text.
// "hello\nworld\n01234AB*56789".
Controller().SetComposition("AB", underlines, 2, 2);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(19u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(19u, Controller().GetSelectionOffsets().end());
// The caret is on right boundary.
// "hello\nworld\n01234AB56789*".
Controller().SetComposition("AB", underlines, 7, 7);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(24u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(24u, Controller().GetSelectionOffsets().end());
// The caret exceeds right boundary.
// "hello\nworld\n01234AB56789*".
Controller().SetComposition("AB", underlines, 100, 100);
EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().Utf8().data());
EXPECT_EQ(24u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(24u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest, SetCompositionWithEmptyText) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>hello</div>", "sample");
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
EXPECT_STREQ("hello", div->innerText().Utf8().data());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().end());
Vector<CompositionUnderline> underlines0;
underlines0.push_back(CompositionUnderline(0, 0, Color(255, 0, 0), false, 0));
Vector<CompositionUnderline> underlines2;
underlines2.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
Controller().SetComposition("AB", underlines2, 2, 2);
// With previous composition.
Controller().SetComposition("", underlines0, 2, 2);
EXPECT_STREQ("hello", div->innerText().Utf8().data());
EXPECT_EQ(4u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(4u, Controller().GetSelectionOffsets().end());
// Without previous composition.
Controller().SetComposition("", underlines0, -1, -1);
EXPECT_STREQ("hello", div->innerText().Utf8().data());
EXPECT_EQ(3u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(3u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest, InsertLineBreakWhileComposingText) {
Element* div =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetComposition("hello", underlines, 5, 5);
EXPECT_STREQ("hello", div->innerText().Utf8().data());
EXPECT_EQ(5u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(5u, Controller().GetSelectionOffsets().end());
GetFrame().GetEditor().InsertLineBreak();
EXPECT_STREQ("\n\n", div->innerText().Utf8().data());
EXPECT_EQ(1u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(1u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest, InsertLineBreakAfterConfirmingText) {
Element* div =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));
Controller().CommitText("hello", underlines, 0);
EXPECT_STREQ("hello", div->innerText().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2));
EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(2u, Controller().GetSelectionOffsets().end());
GetFrame().GetEditor().InsertLineBreak();
EXPECT_STREQ("he\nllo", div->innerText().Utf8().data());
EXPECT_EQ(3u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(3u, Controller().GetSelectionOffsets().end());
}
TEST_F(InputMethodControllerTest, CompositionInputEventIsComposing) {
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* editable =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Element* script = GetDocument().createElement("script");
script->setInnerHTML(
"document.getElementById('sample').addEventListener('beforeinput', "
" event => document.title = "
" `beforeinput.isComposing:${event.isComposing};`);"
"document.getElementById('sample').addEventListener('input', "
" event => document.title += "
" `input.isComposing:${event.isComposing};`);");
GetDocument().body()->AppendChild(script);
GetDocument().View()->UpdateAllLifecyclePhases();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
editable->focus();
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("foo", underlines, 0, 3);
EXPECT_STREQ("beforeinput.isComposing:true;input.isComposing:true;",
GetDocument().title().Utf8().data());
GetDocument().setTitle(g_empty_string);
Controller().CommitText("bar", underlines, 0);
// Last pair of InputEvent should also be inside composition scope.
EXPECT_STREQ("beforeinput.isComposing:true;input.isComposing:true;",
GetDocument().title().Utf8().data());
}
TEST_F(InputMethodControllerTest, CompositionInputEventForReplace) {
CreateHTMLWithCompositionInputEventListeners();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("hell", underlines, 4, 4);
EXPECT_STREQ("beforeinput.data:hell;input.data:hell;",
GetDocument().title().Utf8().data());
// Replace the existing composition.
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("hello", underlines, 0, 0);
EXPECT_STREQ("beforeinput.data:hello;input.data:hello;",
GetDocument().title().Utf8().data());
}
TEST_F(InputMethodControllerTest, CompositionInputEventForConfirm) {
CreateHTMLWithCompositionInputEventListeners();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("hello", underlines, 5, 5);
EXPECT_STREQ("beforeinput.data:hello;input.data:hello;",
GetDocument().title().Utf8().data());
// Confirm the ongoing composition.
GetDocument().setTitle(g_empty_string);
Controller().FinishComposingText(InputMethodController::kKeepSelection);
EXPECT_STREQ("compositionend.data:hello;",
GetDocument().title().Utf8().data());
}
TEST_F(InputMethodControllerTest, CompositionInputEventForDelete) {
CreateHTMLWithCompositionInputEventListeners();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("hello", underlines, 5, 5);
EXPECT_STREQ("beforeinput.data:hello;input.data:hello;",
GetDocument().title().Utf8().data());
// Delete the existing composition.
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("", underlines, 0, 0);
EXPECT_STREQ("beforeinput.data:;input.data:null;compositionend.data:;",
GetDocument().title().Utf8().data());
}
TEST_F(InputMethodControllerTest, CompositionInputEventForInsert) {
CreateHTMLWithCompositionInputEventListeners();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
// Insert new text without previous composition.
GetDocument().setTitle(g_empty_string);
GetDocument().UpdateStyleAndLayout();
Controller().CommitText("hello", underlines, 0);
EXPECT_STREQ("beforeinput.data:hello;input.data:hello;",
GetDocument().title().Utf8().data());
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("n", underlines, 1, 1);
EXPECT_STREQ("beforeinput.data:n;input.data:n;",
GetDocument().title().Utf8().data());
// Insert new text with previous composition.
GetDocument().setTitle(g_empty_string);
GetDocument().UpdateStyleAndLayout();
Controller().CommitText("hello", underlines, 1);
EXPECT_STREQ(
"beforeinput.data:hello;input.data:hello;compositionend.data:hello;",
GetDocument().title().Utf8().data());
}
TEST_F(InputMethodControllerTest, CompositionInputEventForInsertEmptyText) {
CreateHTMLWithCompositionInputEventListeners();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
// Insert empty text without previous composition.
GetDocument().setTitle(g_empty_string);
GetDocument().UpdateStyleAndLayout();
Controller().CommitText("", underlines, 0);
EXPECT_STREQ("beforeinput.data:;", GetDocument().title().Utf8().data());
GetDocument().setTitle(g_empty_string);
Controller().SetComposition("n", underlines, 1, 1);
EXPECT_STREQ("beforeinput.data:n;input.data:n;",
GetDocument().title().Utf8().data());
// Insert empty text with previous composition.
GetDocument().setTitle(g_empty_string);
GetDocument().UpdateStyleAndLayout();
Controller().CommitText("", underlines, 1);
EXPECT_STREQ("beforeinput.data:;input.data:null;compositionend.data:;",
GetDocument().title().Utf8().data());
}
TEST_F(InputMethodControllerTest, CompositionEndEventWithNoSelection) {
CreateHTMLWithCompositionEndEventListener(kNoSelection);
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetComposition("hello", underlines, 1, 1);
GetDocument().UpdateStyleAndLayout();
EXPECT_EQ(1u, Controller().GetSelectionOffsets().Start());
EXPECT_EQ(1u, Controller().GetSelectionOffsets().end());
// Confirm the ongoing composition. Note that it moves the caret to the end of
// text [5,5] before firing 'compositonend' event.
Controller().FinishComposingText(InputMethodController::kDoNotKeepSelection);
GetDocument().UpdateStyleAndLayout();
EXPECT_TRUE(Controller().GetSelectionOffsets().IsNull());
}
TEST_F(InputMethodControllerTest, FinishCompositionRemovedRange) {
Element* input_a =
InsertHTMLElement("<input id='a' /><br><input type='tel' id='b' />", "a");
EXPECT_EQ(kWebTextInputTypeText, Controller().TextInputType());
// The test requires non-empty composition.
Controller().SetComposition("hello", Vector<CompositionUnderline>(), 5, 5);
EXPECT_EQ(kWebTextInputTypeText, Controller().TextInputType());
// Remove element 'a'.
input_a->setOuterHTML("", ASSERT_NO_EXCEPTION);
EXPECT_EQ(kWebTextInputTypeNone, Controller().TextInputType());
GetDocument().getElementById("b")->focus();
EXPECT_EQ(kWebTextInputTypeTelephone, Controller().TextInputType());
Controller().FinishComposingText(InputMethodController::kKeepSelection);
EXPECT_EQ(kWebTextInputTypeTelephone, Controller().TextInputType());
}
TEST_F(InputMethodControllerTest, ReflectsSpaceWithoutNbspMangling) {
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
Controller().CommitText(String(" "), underlines, 0);
// In a contenteditable, multiple spaces or a space at the edge needs to be
// nbsp to affect layout properly, but it confuses some IMEs (particularly
// Vietnamese, see crbug.com/663880) to have their spaces reflected back to
// them as nbsp.
EXPECT_EQ(' ', Controller().TextInputInfo().value.Ascii()[0]);
EXPECT_EQ(' ', Controller().TextInputInfo().value.Ascii()[1]);
}
TEST_F(InputMethodControllerTest, SetCompositionPlainTextWithUnderline) {
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 1, Color(255, 0, 0), false, 0));
Controller().SetComposition(" ", underlines, 1, 1);
ASSERT_EQ(1u, GetDocument().Markers().Markers().size());
EXPECT_EQ(0u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(1u, GetDocument().Markers().Markers()[0]->EndOffset());
}
TEST_F(InputMethodControllerTest, CommitPlainTextWithUnderlineInsert) {
InsertHTMLElement("<div id='sample' contenteditable>Initial text.</div>",
"sample");
Vector<CompositionUnderline> underlines;
Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8));
underlines.push_back(CompositionUnderline(1, 11, Color(255, 0, 0), false, 0));
Controller().CommitText(String("underlined"), underlines, 0);
ASSERT_EQ(1u, GetDocument().Markers().Markers().size());
EXPECT_EQ(9u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(19u, GetDocument().Markers().Markers()[0]->EndOffset());
}
TEST_F(InputMethodControllerTest, CommitPlainTextWithUnderlineReplace) {
InsertHTMLElement("<div id='sample' contenteditable>Initial text.</div>",
"sample");
Vector<CompositionUnderline> underlines;
Controller().SetCompositionFromExistingText(underlines, 8, 12);
underlines.push_back(CompositionUnderline(1, 11, Color(255, 0, 0), false, 0));
Controller().CommitText(String("string"), underlines, 0);
ASSERT_EQ(1u, GetDocument().Markers().Markers().size());
EXPECT_EQ(9u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(15u, GetDocument().Markers().Markers()[0]->EndOffset());
}
TEST_F(InputMethodControllerTest,
CompositionUnderlineAppearsCorrectlyAfterNewline) {
Element* div =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<CompositionUnderline> underlines;
Controller().SetComposition(String("hello"), underlines, 6, 6);
Controller().FinishComposingText(InputMethodController::kKeepSelection);
GetFrame().GetEditor().InsertLineBreak();
Controller().SetCompositionFromExistingText(underlines, 8, 8);
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetComposition(String("world"), underlines, 0, 0);
ASSERT_EQ(1u, GetDocument().Markers().Markers().size());
// Verify composition underline shows up on the second line, not the first
ASSERT_FALSE(GetDocument().Markers().MarkerAtPosition(
PlainTextRange(2).CreateRange(*div).StartPosition(),
DocumentMarker::AllMarkers()));
ASSERT_TRUE(GetDocument().Markers().MarkerAtPosition(
PlainTextRange(8).CreateRange(*div).StartPosition(),
DocumentMarker::AllMarkers()));
// Verify marker has correct start/end offsets (measured from the beginning
// of the node, which is the beginning of the line)
EXPECT_EQ(0u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(5u, GetDocument().Markers().Markers()[0]->EndOffset());
}
TEST_F(InputMethodControllerTest, SelectionWhenFocusChangeFinishesComposition) {
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* editable =
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
editable->focus();
// Simulate composition in the |contentEditable|.
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0));
Controller().SetComposition("foo", underlines, 3, 3);
EXPECT_TRUE(Controller().HasComposition());
EXPECT_EQ(0u, Controller().CompositionRange()->startOffset());
EXPECT_EQ(3u, Controller().CompositionRange()->endOffset());
EXPECT_EQ(3, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Start()
.ComputeOffsetInContainerNode());
// Insert 'test'.
NonThrowableExceptionState exception_state;
GetDocument().execCommand("insertText", false, "test", exception_state);
EXPECT_TRUE(Controller().HasComposition());
EXPECT_EQ(7, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Start()
.ComputeOffsetInContainerNode());
// Focus change finishes composition.
editable->blur();
editable->focus();
// Make sure that caret is still at the end of the inserted text.
EXPECT_FALSE(Controller().HasComposition());
EXPECT_EQ(7, GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Start()
.ComputeOffsetInContainerNode());
}
TEST_F(InputMethodControllerTest, SetEmptyCompositionShouldNotMoveCaret) {
HTMLTextAreaElement* textarea =
toHTMLTextAreaElement(InsertHTMLElement("<textarea id='txt'>", "txt"));
textarea->setValue("abc\n");
GetDocument().UpdateStyleAndLayout();
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4));
Vector<CompositionUnderline> underlines;
underlines.push_back(CompositionUnderline(0, 3, Color(255, 0, 0), false, 0));
Controller().SetComposition(String("def"), underlines, 0, 3);
Controller().SetComposition(String(""), underlines, 0, 3);
Controller().CommitText(String("def"), underlines, 0);
EXPECT_STREQ("abc\ndef", textarea->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, WhitespaceFixup) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>Initial text blah</div>", "sample");
// Delete "Initial"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 0, 7);
Controller().CommitText(String(""), empty_underlines, 0);
// The space at the beginning of the string should have been converted to an
// nbsp
EXPECT_STREQ("&nbsp;text blah", div->innerHTML().Utf8().data());
// Delete "blah"
Controller().SetCompositionFromExistingText(empty_underlines, 6, 10);
Controller().CommitText(String(""), empty_underlines, 0);
// The space at the end of the string should have been converted to an nbsp
EXPECT_STREQ("&nbsp;text&nbsp;", div->innerHTML().Utf8().data());
}
TEST_F(InputMethodControllerTest, CommitEmptyTextDeletesSelection) {
HTMLInputElement* input =
toHTMLInputElement(InsertHTMLElement("<input id='sample'>", "sample"));
input->setValue("Abc Def Ghi");
GetDocument().UpdateStyleAndLayout();
Vector<CompositionUnderline> empty_underlines;
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 8));
Controller().CommitText(String(""), empty_underlines, 0);
EXPECT_STREQ("Abc Ghi", input->value().Utf8().data());
Controller().SetEditableSelectionOffsets(PlainTextRange(4, 7));
Controller().CommitText(String("1"), empty_underlines, 0);
EXPECT_STREQ("Abc 1", input->value().Utf8().data());
}
TEST_F(InputMethodControllerTest, ContentDependentMarker_ReplaceStartOfMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>Initial text</div>", "sample");
// Add marker under "Initial text"
EphemeralRange marker_range = PlainTextRange(0, 12).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
// Replace "Initial" with "Original"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 0, 7);
Controller().CommitText(String("Original"), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest,
ContentDependentMarker_ReplaceTextContainsStartOfMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>This is some initial text</div>",
"sample");
// Add marker under "initial text"
EphemeralRange marker_range = PlainTextRange(13, 25).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
// Replace "some initial" with "boring"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 8, 20);
Controller().CommitText(String("boring"), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest, ContentDependentMarker_ReplaceEndOfMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>Initial text</div>", "sample");
// Add marker under "Initial text"
EphemeralRange marker_range = PlainTextRange(0, 12).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
// Replace "text" with "string"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 8, 12);
Controller().CommitText(String("string"), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest,
ContentDependentMarker_ReplaceTextContainsEndOfMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>This is some initial text</div>",
"sample");
// Add marker under "some initial"
EphemeralRange marker_range = PlainTextRange(8, 20).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
// Replace "initial text" with "content"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 13, 25);
Controller().CommitText(String("content"), empty_underlines, 0);
EXPECT_STREQ("This is some content", div->innerHTML().Utf8().data());
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest, ContentDependentMarker_ReplaceEntireMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>Initial text</div>", "sample");
// Add marker under "text"
EphemeralRange marker_range = PlainTextRange(8, 12).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
// Replace "text" with "string"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 8, 12);
Controller().CommitText(String("string"), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest,
ContentDependentMarker_ReplaceTextWithMarkerAtBeginning) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>Initial text</div>", "sample");
// Add marker under "Initial"
EphemeralRange marker_range = PlainTextRange(0, 7).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
// Replace "Initial text" with "New string"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 0, 12);
Controller().CommitText(String("New string"), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest,
ContentDependentMarker_ReplaceTextWithMarkerAtEnd) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>Initial text</div>", "sample");
// Add marker under "text"
EphemeralRange marker_range = PlainTextRange(8, 12).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
// Replace "Initial text" with "New string"
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 0, 12);
Controller().CommitText(String("New string"), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest, ContentDependentMarker_Deletions) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>1111122222333334444455555</div>",
"sample");
EphemeralRange marker_range = PlainTextRange(0, 5).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(5, 10).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(10, 15).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(15, 20).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(20, 25).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
EXPECT_EQ(5u, GetDocument().Markers().Markers().size());
// Delete third marker and portions of second and fourth
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 8, 17);
Controller().CommitText(String(""), empty_underlines, 0);
// Verify markers were updated correctly
EXPECT_EQ(2u, GetDocument().Markers().Markers().size());
EXPECT_EQ(0u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(5u, GetDocument().Markers().Markers()[0]->EndOffset());
EXPECT_EQ(11u, GetDocument().Markers().Markers()[1]->StartOffset());
EXPECT_EQ(16u, GetDocument().Markers().Markers()[1]->EndOffset());
}
TEST_F(InputMethodControllerTest,
ContentDependentMarker_DeleteExactlyOnMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>1111122222333334444455555</div>",
"sample");
EphemeralRange marker_range = PlainTextRange(5, 10).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
// Delete exactly on the marker
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 5, 10);
Controller().CommitText(String(""), empty_underlines, 0);
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest, ContentDependentMarker_DeleteMiddleOfMarker) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>1111122222333334444455555</div>",
"sample");
EphemeralRange marker_range = PlainTextRange(5, 10).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
// Delete middle of marker
Vector<CompositionUnderline> empty_underlines;
Controller().SetCompositionFromExistingText(empty_underlines, 6, 9);
Controller().CommitText(String(""), empty_underlines, 0);
// Verify marker was removed
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
}
TEST_F(InputMethodControllerTest,
ContentDependentMarker_InsertInMarkerInterior) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>1111122222333334444455555</div>",
"sample");
EphemeralRange marker_range = PlainTextRange(0, 5).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(5, 10).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(10, 15).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
EXPECT_EQ(3u, GetDocument().Markers().Markers().size());
// insert in middle of second marker
Vector<CompositionUnderline> empty_underlines;
Controller().SetComposition("", empty_underlines, 7, 7);
Controller().CommitText(String("66666"), empty_underlines, -7);
EXPECT_EQ(2u, GetDocument().Markers().Markers().size());
EXPECT_EQ(0u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(5u, GetDocument().Markers().Markers()[0]->EndOffset());
EXPECT_EQ(15u, GetDocument().Markers().Markers()[1]->StartOffset());
EXPECT_EQ(20u, GetDocument().Markers().Markers()[1]->EndOffset());
}
TEST_F(InputMethodControllerTest, ContentDependentMarker_InsertBetweenMarkers) {
Element* div = InsertHTMLElement(
"<div id='sample' contenteditable>1111122222333334444455555</div>",
"sample");
EphemeralRange marker_range = PlainTextRange(0, 5).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(5, 15).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
marker_range = PlainTextRange(15, 20).CreateRange(*div);
GetDocument().Markers().AddTextMatchMarker(
marker_range, DocumentMarker::MatchStatus::kInactive);
EXPECT_EQ(3u, GetDocument().Markers().Markers().size());
Vector<CompositionUnderline> empty_underlines;
Controller().SetComposition("", empty_underlines, 5, 5);
Controller().CommitText(String("77777"), empty_underlines, 0);
EXPECT_EQ(3u, GetDocument().Markers().Markers().size());
EXPECT_EQ(0u, GetDocument().Markers().Markers()[0]->StartOffset());
EXPECT_EQ(5u, GetDocument().Markers().Markers()[0]->EndOffset());
EXPECT_EQ(10u, GetDocument().Markers().Markers()[1]->StartOffset());
EXPECT_EQ(20u, GetDocument().Markers().Markers()[1]->EndOffset());
EXPECT_EQ(20u, GetDocument().Markers().Markers()[2]->StartOffset());
EXPECT_EQ(25u, GetDocument().Markers().Markers()[2]->EndOffset());
}
// For http://crbug.com/712761
TEST_F(InputMethodControllerTest, TextInputTypeAtBeforeEditable) {
GetDocument().body()->setContentEditable("true", ASSERT_NO_EXCEPTION);
GetDocument().body()->focus();
// Set selection before BODY(editable).
GetFrame().Selection().SetSelection(
SelectionInDOMTree::Builder()
.Collapse(Position(GetDocument().documentElement(), 0))
.Build());
EXPECT_EQ(kWebTextInputTypeContentEditable, Controller().TextInputType());
}
} // namespace blink