// 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 "third_party/blink/renderer/core/editing/ime/input_method_controller.h" #include #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/node_list.h" #include "third_party/blink/renderer/core/dom/range.h" #include "third_party/blink/renderer/core/editing/editor.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" #include "third_party/blink/renderer/core/editing/selection_template.h" #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h" #include "third_party/blink/renderer/core/events/mouse_event.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" #include "third_party/blink/renderer/core/layout/layout_theme.h" using ui::mojom::ImeTextSpanThickness; using ui::mojom::ImeTextSpanUnderlineStyle; namespace blink { class InputMethodControllerTest : public EditingTestBase { protected: enum SelectionType { kNoSelection, kCaretSelection, kRangeSelection }; InputMethodController& Controller() { return GetFrame().GetInputMethodController(); } // TODO(editing-dev): We should use |CompositionEphemeralRange()| instead // of having |GetCompositionRange()| and marking |InputMethodControllerTest| // as friend class. Range* GetCompositionRange() { return Controller().composition_range_; } Element* InsertHTMLElement(const char* element_code, const char* element_id); void CreateHTMLWithCompositionInputEventListeners(); void CreateHTMLWithCompositionEndEventListener(const SelectionType); }; Element* InputMethodControllerTest::InsertHTMLElement(const char* element_code, const char* element_id) { GetDocument().write(element_code); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Element* element = GetElementById(element_id); element->focus(); return element; } void InputMethodControllerTest::CreateHTMLWithCompositionInputEventListeners() { GetDocument().GetSettings()->SetScriptEnabled(true); Element* editable = InsertHTMLElement("
", "sample"); Element* script = GetDocument().CreateRawElement(html_names::kScriptTag); 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); UpdateAllLifecyclePhasesForTest(); editable->focus(); } void InputMethodControllerTest::CreateHTMLWithCompositionEndEventListener( const SelectionType type) { GetDocument().GetSettings()->SetScriptEnabled(true); Element* editable = InsertHTMLElement("
", "sample"); Element* script = GetDocument().CreateRawElement(html_names::kScriptTag); 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); UpdateAllLifecyclePhasesForTest(); editable->focus(); } TEST_F(InputMethodControllerTest, BackspaceFromEndOfInput) { auto* input = To(InsertHTMLElement("", "sample")); input->setValue("fooX"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("fooX", input->value()); Controller().ExtendSelectionAndDelete(0, 0); EXPECT_EQ("fooX", input->value()); input->setValue("fooX"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("fooX", input->value()); Controller().ExtendSelectionAndDelete(1, 0); EXPECT_EQ("foo", input->value()); input->setValue( String::FromUTF8("foo\xE2\x98\x85")); // U+2605 == "black star" GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("foo\xE2\x98\x85", input->value().Utf8()); Controller().ExtendSelectionAndDelete(1, 0); EXPECT_EQ("foo", input->value()); input->setValue( String::FromUTF8("foo\xF0\x9F\x8F\x86")); // U+1F3C6 == "trophy" GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("foo\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().ExtendSelectionAndDelete(1, 0); EXPECT_EQ("foo", input->value()); // composed U+0E01 "ka kai" + U+0E49 "mai tho" input->setValue(String::FromUTF8("foo\xE0\xB8\x81\xE0\xB9\x89")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("foo\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8()); Controller().ExtendSelectionAndDelete(1, 0); EXPECT_EQ("foo", input->value()); input->setValue("fooX"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("fooX", input->value()); Controller().ExtendSelectionAndDelete(0, 1); EXPECT_EQ("fooX", input->value()); } TEST_F(InputMethodControllerTest, SetCompositionFromExistingText) { Element* div = InsertHTMLElement( "
hello world
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 0, 5); Range* range = GetCompositionRange(); 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, AddImeTextSpansToExistingText) { InsertHTMLElement("
hello world
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kAutocorrect, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().AddImeTextSpansToExistingText(ime_text_spans, 0, 5); EXPECT_EQ(1u, GetDocument().Markers().Markers().size()); EXPECT_EQ(0u, GetDocument().Markers().Markers()[0]->StartOffset()); EXPECT_EQ(5u, GetDocument().Markers().Markers()[0]->EndOffset()); EXPECT_EQ(DocumentMarker::MarkerType::kSuggestion, GetDocument().Markers().Markers()[0]->GetType()); EXPECT_EQ(SuggestionMarker::SuggestionType::kAutocorrect, To(GetDocument().Markers().Markers()[0].Get()) ->GetSuggestionType()); } TEST_F(InputMethodControllerTest, GetImeTextSpansAroundPosition) { InsertHTMLElement("
hello world
", "sample"); ImeTextSpan span1 = ImeTextSpan(ImeTextSpan::Type::kAutocorrect, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0); ImeTextSpan span2 = ImeTextSpan(ImeTextSpan::Type::kComposition, 1, 3, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0); ImeTextSpan span3 = ImeTextSpan( ImeTextSpan::Type::kMisspellingSuggestion, 1, 3, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0); Controller().AddImeTextSpansToExistingText({span1, span2, span3}, 0, 5); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 1)); const WebVector& ime_text_spans = Controller().TextInputInfo().ime_text_spans; EXPECT_EQ(1u, ime_text_spans.size()); EXPECT_EQ(0u, ime_text_spans[0].start_offset); EXPECT_EQ(5u, ime_text_spans[0].end_offset); EXPECT_EQ(ui::ImeTextSpan::Type::kAutocorrect, ime_text_spans[0].type); } TEST_F(InputMethodControllerTest, SetCompositionAfterEmoji) { // "trophy" = U+1F3C6 = 0xF0 0x9F 0x8F 0x86 (UTF8). Element* div = InsertHTMLElement( "
🏆
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); EXPECT_EQ(2, GetFrame() .Selection() .GetSelectionInDOMTree() .Base() .ComputeOffsetInContainerNode()); EXPECT_EQ(2, GetFrame() .Selection() .GetSelectionInDOMTree() .Extent() .ComputeOffsetInContainerNode()); Controller().SetComposition(String("a"), ime_text_spans, 1, 1); EXPECT_EQ("\xF0\x9F\x8F\x86\x61", div->innerText().Utf8()); Controller().SetComposition(String("ab"), ime_text_spans, 2, 2); EXPECT_EQ("\xF0\x9F\x8F\x86\x61\x62", div->innerText().Utf8()); } TEST_F(InputMethodControllerTest, SetCompositionWithGraphemeCluster) { InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 6, 6, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); // 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"), ime_text_spans, 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"), ime_text_spans, 6, 6); EXPECT_EQ(6u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(6u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, SetCompositionWithGraphemeClusterAndMultipleNodes) { Element* div = InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 12, 12, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); // 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"), ime_text_spans, 1); Controller().CommitText("\nab ", ime_text_spans, 1); Controller().SetComposition(String("c"), ime_text_spans, 1, 1); EXPECT_EQ( "\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA5\x8D\xE0\xA4\xB2\xE0\xA5" "\x8B\nab c", div->innerText().Utf8()); EXPECT_EQ(11u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(11u, Controller().GetSelectionOffsets().End()); Controller().SetComposition(String("cd"), ime_text_spans, 2, 2); EXPECT_EQ( "\xE0\xA4\xB9\xE0\xA5\x87\xE0\xA4\xB2\xE0\xA5\x8D\xE0\xA4\xB2\xE0\xA5" "\x8B\nab cd", div->innerText().Utf8()); EXPECT_EQ(12u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(12u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, SetCompositionKeepingStyle) { Element* div = InsertHTMLElement( "
abc123456789def
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 3, 12, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 3, 12); // Subtract a character. Controller().SetComposition(String("12345789"), ime_text_spans, 8, 8); EXPECT_EQ("abc12345789def", div->innerHTML().Utf8()); EXPECT_EQ(11u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(11u, Controller().GetSelectionOffsets().End()); // Append a character. Controller().SetComposition(String("123456789"), ime_text_spans, 9, 9); EXPECT_EQ("abc123456789def", div->innerHTML().Utf8()); EXPECT_EQ(12u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(12u, Controller().GetSelectionOffsets().End()); // Subtract and append characters. Controller().SetComposition(String("123hello789"), ime_text_spans, 11, 11); EXPECT_EQ("abc123hello789def", div->innerHTML().Utf8()); } TEST_F(InputMethodControllerTest, SetCompositionWithEmojiKeepingStyle) { // U+1F3E0 = 0xF0 0x9F 0x8F 0xA0 (UTF8). It's an emoji character. Element* div = InsertHTMLElement( "
🏠
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 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"), ime_text_spans, 2, 2); EXPECT_EQ("\xF0\x9F\x8F\xAB", div->innerHTML().Utf8()); Controller().SetComposition(String::FromUTF8("\xF0\x9F\x8F\xA0"), ime_text_spans, 2, 2); EXPECT_EQ("\xF0\x9F\x8F\xA0", div->innerHTML().Utf8()); } 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( "
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 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"), ime_text_spans, 2, 2); EXPECT_EQ("\xE0\xB0\x83\xE0\xB0\x83", div->innerHTML().Utf8()); Controller().SetComposition(String::FromUTF8("\xE0\xB0\x83"), ime_text_spans, 1, 1); EXPECT_EQ("\xE0\xB0\x83", div->innerHTML().Utf8()); } TEST_F(InputMethodControllerTest, FinishComposingTextKeepingStyle) { Element* div = InsertHTMLElement( "
abc123456789
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 3, 12, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 3, 12); Controller().SetComposition(String("123hello789"), ime_text_spans, 11, 11); EXPECT_EQ("abc123hello789", div->innerHTML()); Controller().FinishComposingText(InputMethodController::kKeepSelection); EXPECT_EQ("abc123hello789", div->innerHTML()); } TEST_F(InputMethodControllerTest, FinishComposingTextKeepingBackwardSelection) { GetFrame().Selection().SetSelectionAndEndTyping( SetSelectionTextToBody("
|abc^
")); Controller().FinishComposingText(InputMethodController::kKeepSelection); EXPECT_EQ("
|abc^
", GetSelectionTextFromBody()); } TEST_F(InputMethodControllerTest, CommitTextKeepingStyle) { Element* div = InsertHTMLElement( "
abc123456789
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 3, 12, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 3, 12); Controller().CommitText(String("123789"), ime_text_spans, 0); EXPECT_EQ("abc123789", div->innerHTML()); } TEST_F(InputMethodControllerTest, InsertTextWithNewLine) { Element* div = InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 11, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().CommitText(String("hello\nworld"), ime_text_spans, 0); EXPECT_EQ("hello
world
", div->innerHTML()); } TEST_F(InputMethodControllerTest, InsertTextWithNewLineIncrementally) { Element* div = InsertHTMLElement("
", "sample"); Vector ime_text_spans; Controller().CommitText("a", ime_text_spans, 0); Controller().SetComposition("bcd", ime_text_spans, 0, 2); EXPECT_EQ("abcd", div->innerHTML()); Controller().CommitText(String("bcd\nefgh\nijkl"), ime_text_spans, 0); EXPECT_EQ("abcd
efgh
ijkl
", div->innerHTML()); } TEST_F(InputMethodControllerTest, SelectionOnConfirmExistingText) { InsertHTMLElement("
hello world
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 0, 5); Controller().FinishComposingText(InputMethodController::kKeepSelection); EXPECT_EQ(0, GetFrame() .Selection() .GetSelectionInDOMTree() .Base() .ComputeOffsetInContainerNode()); EXPECT_EQ(0, GetFrame() .Selection() .GetSelectionInDOMTree() .Extent() .ComputeOffsetInContainerNode()); } TEST_F(InputMethodControllerTest, DeleteBySettingEmptyComposition) { auto* input = To(InsertHTMLElement("", "sample")); input->setValue("foo "); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("foo ", input->value()); Controller().ExtendSelectionAndDelete(0, 0); EXPECT_EQ("foo ", input->value()); input->setValue("foo "); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("foo ", input->value()); Controller().ExtendSelectionAndDelete(1, 0); EXPECT_EQ("foo", input->value()); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 3, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 0, 3); Controller().SetComposition(String(""), ime_text_spans, 0, 3); EXPECT_EQ("", input->value()); } 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( "
\nhello world
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 0, 5); Range* range = GetCompositionRange(); 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("
test
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 7, 8, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetCompositionFromExistingText(ime_text_spans, 7, 8); EXPECT_FALSE(GetCompositionRange()); } TEST_F(InputMethodControllerTest, ConfirmPasswordComposition) { auto* input = To(InsertHTMLElement( "", "sample")); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition("foo", ime_text_spans, 0, 3); Controller().FinishComposingText(InputMethodController::kKeepSelection); EXPECT_EQ("foo", input->value()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithEmptyText) { auto* input = To(InsertHTMLElement("", "sample")); input->setValue(""); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("", input->value()); Controller().DeleteSurroundingText(0, 0); EXPECT_EQ("", input->value()); input->setValue(""); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("", input->value()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("", input->value()); input->setValue(""); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("", input->value()); Controller().DeleteSurroundingText(0, 1); EXPECT_EQ("", input->value()); input->setValue(""); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("", input->value()); Controller().DeleteSurroundingText(1, 1); EXPECT_EQ("", input->value()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithRangeSelection) { auto* input = To(InsertHTMLElement("", "sample")); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4)); Controller().DeleteSurroundingText(0, 0); EXPECT_EQ("hello", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4)); Controller().DeleteSurroundingText(1, 1); EXPECT_EQ("ell", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4)); Controller().DeleteSurroundingText(100, 0); EXPECT_EQ("ello", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4)); Controller().DeleteSurroundingText(0, 100); EXPECT_EQ("hell", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 4)); Controller().DeleteSurroundingText(100, 100); EXPECT_EQ("ell", input->value()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithCursorSelection) { auto* input = To(InsertHTMLElement("", "sample")); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("hllo", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(0, 1); EXPECT_EQ("helo", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(0, 0); EXPECT_EQ("hello", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(1, 1); EXPECT_EQ("hlo", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(100, 0); EXPECT_EQ("llo", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(0, 100); EXPECT_EQ("he", input->value()); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("hello", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); Controller().DeleteSurroundingText(100, 100); EXPECT_EQ("", input->value()); input->setValue("h"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("h", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(1, 1)); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("", input->value()); input->setValue("h"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); EXPECT_EQ("h", input->value()); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); Controller().DeleteSurroundingText(0, 1); EXPECT_EQ("", input->value()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithMultiCodeTextOnTheLeft) { auto* input = To(InsertHTMLElement("", "sample")); // U+2605 == "black star". It takes up 1 space. input->setValue(String::FromUTF8("foo\xE2\x98\x85")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("foo\xE2\x98\x85", input->value().Utf8()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("foo", input->value()); // U+1F3C6 == "trophy". It takes up 2 space. input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(5, 5)); EXPECT_EQ("foo\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("foo\xED\xA0\xBC", input->value().Utf8()); // 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(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(5, 5)); EXPECT_EQ("foo\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("foo\xE0\xB8\x81", input->value().Utf8()); // "trophy" + "trophy". input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7)); EXPECT_EQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().DeleteSurroundingText(2, 0); EXPECT_EQ("foo\xF0\x9F\x8F\x86", input->value().Utf8()); // "trophy" + "trophy". input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7)); EXPECT_EQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().DeleteSurroundingText(3, 0); EXPECT_EQ("foo\xED\xA0\xBC", input->value().Utf8()); // "trophy" + "trophy". input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7)); EXPECT_EQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().DeleteSurroundingText(4, 0); EXPECT_EQ("foo", input->value()); // "trophy" + "trophy". input->setValue(String::FromUTF8("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(7, 7)); EXPECT_EQ("foo\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().DeleteSurroundingText(5, 0); EXPECT_EQ("fo", input->value()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithMultiCodeTextOnTheRight) { auto* input = To(InsertHTMLElement("", "sample")); // U+2605 == "black star". It takes up 1 space. input->setValue(String::FromUTF8("\xE2\x98\x85 foo")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xE2\x98\x85 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 1); EXPECT_EQ(" foo", input->value()); // U+1F3C6 == "trophy". It takes up 2 space. input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86 foo")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xF0\x9F\x8F\x86 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 1); EXPECT_EQ("\xED\xBF\x86 foo", input->value().Utf8()); // 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(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xE0\xB8\x81\xE0\xB9\x89 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 1); EXPECT_EQ("\xE0\xB9\x89 foo", input->value().Utf8()); // "trophy" + "trophy". input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 2); EXPECT_EQ("\xF0\x9F\x8F\x86 foo", input->value().Utf8()); // "trophy" + "trophy". input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 3); EXPECT_EQ("\xED\xBF\x86 foo", input->value().Utf8()); // "trophy" + "trophy". input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 4); EXPECT_EQ(" foo", input->value()); // "trophy" + "trophy". input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); EXPECT_EQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86 foo", input->value().Utf8()); Controller().DeleteSurroundingText(0, 5); EXPECT_EQ("foo", input->value()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextWithMultiCodeTextOnBothSides) { auto* input = To(InsertHTMLElement("", "sample")); // "trophy" + "trophy". input->setValue(String::FromUTF8("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); EXPECT_EQ("\xF0\x9F\x8F\x86\xF0\x9F\x8F\x86", input->value().Utf8()); Controller().DeleteSurroundingText(1, 1); // Deleted second half of the first trophy and the first half of the second // trophy, so we ended up with a complete trophy. EXPECT_EQ("\xF0\x9F\x8F\x86", input->value().Utf8()); } // This test comes from http://crbug.com/1024738. It is basically the same to // composed text (U+0E01 "ka kai" + U+0E49 "mai tho"), but easier to understand. TEST_F(InputMethodControllerTest, DeleteSurroundingTextForComposedCharacter) { auto* input = To(InsertHTMLElement("", "sample")); // p̂p̂ (U+0070 U+0302 U+0070 U+0302) input->setValue(String::FromUTF8("\x70\xCC\x82\x70\xCC\x82")); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); EXPECT_EQ("\x70\xCC\x82\x70\xCC\x82", input->value().Utf8()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("\x70\xCC\x82\x70", input->value().Utf8()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("\x70\xCC\x82", input->value().Utf8()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextForMultipleNodes) { Element* div = InsertHTMLElement( "
aaa" "
bbb" "
ccc" "
ddd" "
eee" "
", "sample"); Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8)); EXPECT_EQ("aaa\nbbb\nccc\nddd\neee", div->innerText()); EXPECT_EQ(8u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(8u, Controller().GetSelectionOffsets().End()); Controller().DeleteSurroundingText(1, 0); EXPECT_EQ("aaa\nbbbccc\nddd\neee", div->innerText()); EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(7u, Controller().GetSelectionOffsets().End()); Controller().DeleteSurroundingText(0, 4); EXPECT_EQ("aaa\nbbbddd\neee", div->innerText()); EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(7u, Controller().GetSelectionOffsets().End()); Controller().DeleteSurroundingText(5, 5); EXPECT_EQ("aaee", div->innerText()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextInCodePointsWithMultiCodeTextOnTheLeft) { auto* input = To(InsertHTMLElement("", "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(DocumentUpdateReason::kTest); // The cursor is at the end of the text. Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8)); Controller().DeleteSurroundingTextInCodePoints(2, 0); EXPECT_EQ("a\xE2\x98\x85 \xF0\x9F\x8F\x86 ", input->value().Utf8()); Controller().DeleteSurroundingTextInCodePoints(4, 0); EXPECT_EQ("a", input->value()); // '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(DocumentUpdateReason::kTest); // The cursor is at the end of the text. Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8)); // We should only delete 1 code point. Controller().DeleteSurroundingTextInCodePoints(1, 0); EXPECT_EQ("a\xE2\x98\x85 \xF0\x9F\x8F\x86 \xE0\xB8\x81", input->value().Utf8()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextInCodePointsWithMultiCodeTextOnTheRight) { auto* input = To(InsertHTMLElement("", "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(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); Controller().DeleteSurroundingTextInCodePoints(0, 5); EXPECT_EQ("\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8()); Controller().DeleteSurroundingTextInCodePoints(0, 1); // We should only delete 1 code point. EXPECT_EQ("\xE0\xB9\x89", input->value().Utf8()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextInCodePointsWithMultiCodeTextOnBothSides) { auto* input = To(InsertHTMLElement("", "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(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(3, 3)); Controller().DeleteSurroundingTextInCodePoints(2, 2); EXPECT_EQ("a\xE0\xB8\x81\xE0\xB9\x89", input->value().Utf8()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextInCodePointsWithImage) { Element* div = InsertHTMLElement( "
aaa" "bbb
", "sample"); Controller().SetEditableSelectionOffsets(PlainTextRange(4, 4)); Controller().DeleteSurroundingTextInCodePoints(1, 1); EXPECT_EQ("aaabb", div->innerText()); EXPECT_EQ(3u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(3u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, DeleteSurroundingTextInCodePointsWithInvalidSurrogatePair) { auto* input = To(InsertHTMLElement("", "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(DocumentUpdateReason::kTest); // The invalid high surrogate is encoded as '\xED\xA0\xBC', and invalid low // surrogate is encoded as '\xED\xBF\x86'. EXPECT_EQ("a\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86 ", input->value().Utf8()); Controller().SetEditableSelectionOffsets(PlainTextRange(5, 5)); // Delete a SPACE. Controller().DeleteSurroundingTextInCodePoints(1, 0); EXPECT_EQ("a\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86", input->value().Utf8()); // Do nothing since there is an invalid surrogate in the requested range. Controller().DeleteSurroundingTextInCodePoints(2, 0); EXPECT_EQ("a\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86", input->value().Utf8()); Controller().SetEditableSelectionOffsets(PlainTextRange(0, 0)); // Delete 'a'. Controller().DeleteSurroundingTextInCodePoints(0, 1); EXPECT_EQ("\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86", input->value().Utf8()); // Do nothing since there is an invalid surrogate in the requested range. Controller().DeleteSurroundingTextInCodePoints(0, 2); EXPECT_EQ("\xED\xA0\xBC\xE2\x98\x85\xED\xBF\x86", input->value().Utf8()); } TEST_F(InputMethodControllerTest, SetCompositionForInputWithNewCaretPositions) { auto* input = To(InsertHTMLElement("", "sample")); input->setValue("hello"); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); EXPECT_EQ("hello", input->value()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().End()); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); // The caret exceeds left boundary. // "*heABllo", where * stands for caret. Controller().SetComposition("AB", ime_text_spans, -100, -100); EXPECT_EQ("heABllo", input->value()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().End()); // The caret is on left boundary. // "*heABllo". Controller().SetComposition("AB", ime_text_spans, -2, -2); EXPECT_EQ("heABllo", input->value()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().End()); // The caret is before the composing text. // "he*ABllo". Controller().SetComposition("AB", ime_text_spans, 0, 0); EXPECT_EQ("heABllo", input->value()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().End()); // The caret is after the composing text. // "heAB*llo". Controller().SetComposition("AB", ime_text_spans, 2, 2); EXPECT_EQ("heABllo", input->value()); EXPECT_EQ(4u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(4u, Controller().GetSelectionOffsets().End()); // The caret is on right boundary. // "heABllo*". Controller().SetComposition("AB", ime_text_spans, 5, 5); EXPECT_EQ("heABllo", input->value()); EXPECT_EQ(7u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(7u, Controller().GetSelectionOffsets().End()); // The caret exceeds right boundary. // "heABllo*". Controller().SetComposition("AB", ime_text_spans, 100, 100); EXPECT_EQ("heABllo", input->value()); 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( "
" "hello" "
world" "

0123456789

" "
" "
", "sample"); Controller().SetEditableSelectionOffsets(PlainTextRange(17, 17)); EXPECT_EQ("hello\nworld\n\n0123456789", div->innerText()); EXPECT_EQ(17u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(17u, Controller().GetSelectionOffsets().End()); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); // The caret exceeds left boundary. // "*hello\nworld\n\n01234AB56789", where * stands for caret. Controller().SetComposition("AB", ime_text_spans, -100, -100); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().End()); // The caret is on left boundary. // "*hello\nworld\n\n01234AB56789". Controller().SetComposition("AB", ime_text_spans, -17, -17); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(0u, Controller().GetSelectionOffsets().End()); // The caret is in the 1st node. // "he*llo\nworld\n\n01234AB56789". Controller().SetComposition("AB", ime_text_spans, -15, -15); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().End()); // The caret is on right boundary of the 1st node. // "hello*\nworld\n\n01234AB56789". Controller().SetComposition("AB", ime_text_spans, -12, -12); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); 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\n\n01234AB56789". Controller().SetComposition("AB", ime_text_spans, -11, -11); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); 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", ime_text_spans, -6, -6); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); 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", ime_text_spans, -5, -5); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(12u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(12u, Controller().GetSelectionOffsets().End()); // The caret is before the composing text. // "hello\nworld\n\n01234*AB56789". Controller().SetComposition("AB", ime_text_spans, 0, 0); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(17u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(17u, Controller().GetSelectionOffsets().End()); // The caret is after the composing text. // "hello\nworld\n\n01234AB*56789". Controller().SetComposition("AB", ime_text_spans, 2, 2); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(19u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(19u, Controller().GetSelectionOffsets().End()); // The caret is on right boundary. // "hello\nworld\n\n01234AB56789*". Controller().SetComposition("AB", ime_text_spans, 7, 7); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(24u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(24u, Controller().GetSelectionOffsets().End()); // The caret exceeds right boundary. // "hello\nworld\n\n01234AB56789*". Controller().SetComposition("AB", ime_text_spans, 100, 100); EXPECT_EQ("hello\nworld\n\n01234AB56789", div->innerText()); EXPECT_EQ(24u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(24u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, SetCompositionWithEmptyText) { Element* div = InsertHTMLElement( "
hello
", "sample"); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); EXPECT_EQ("hello", div->innerText()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().End()); Vector ime_text_spans0; ime_text_spans0.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 0, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Vector ime_text_spans2; ime_text_spans2.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition("AB", ime_text_spans2, 2, 2); // With previous composition. Controller().SetComposition("", ime_text_spans0, 2, 2); EXPECT_EQ("hello", div->innerText()); EXPECT_EQ(4u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(4u, Controller().GetSelectionOffsets().End()); // Without previous composition. Controller().SetComposition("", ime_text_spans0, -1, -1); EXPECT_EQ("hello", div->innerText()); EXPECT_EQ(3u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(3u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, InsertLineBreakWhileComposingText) { Element* div = InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition("hello", ime_text_spans, 5, 5); EXPECT_EQ("hello", div->innerText()); EXPECT_EQ(5u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(5u, Controller().GetSelectionOffsets().End()); GetFrame().GetEditor().InsertLineBreak(); EXPECT_EQ("hello\n\n", div->innerText()); EXPECT_EQ(6u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(6u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, InsertLineBreakAfterConfirmingText) { Element* div = InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 2, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().CommitText("hello", ime_text_spans, 0); EXPECT_EQ("hello", div->innerText()); Controller().SetEditableSelectionOffsets(PlainTextRange(2, 2)); EXPECT_EQ(2u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(2u, Controller().GetSelectionOffsets().End()); GetFrame().GetEditor().InsertLineBreak(); EXPECT_EQ("he\nllo", div->innerText()); EXPECT_EQ(3u, Controller().GetSelectionOffsets().Start()); EXPECT_EQ(3u, Controller().GetSelectionOffsets().End()); } TEST_F(InputMethodControllerTest, CompositionInputEventIsComposing) { GetDocument().GetSettings()->SetScriptEnabled(true); Element* editable = InsertHTMLElement("
", "sample"); Element* script = GetDocument().CreateRawElement(html_names::kScriptTag); 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); UpdateAllLifecyclePhasesForTest(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); editable->focus(); GetDocument().setTitle(g_empty_string); Controller().SetComposition("foo", ime_text_spans, 0, 3); EXPECT_EQ("beforeinput.isComposing:true;input.isComposing:true;", GetDocument().title()); GetDocument().setTitle(g_empty_string); Controller().CommitText("bar", ime_text_spans, 0); // Last pair of InputEvent should also be inside composition scope. EXPECT_EQ("beforeinput.isComposing:true;input.isComposing:true;", GetDocument().title()); } TEST_F(InputMethodControllerTest, CompositionInputEventForReplace) { CreateHTMLWithCompositionInputEventListeners(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); GetDocument().setTitle(g_empty_string); Controller().SetComposition("hell", ime_text_spans, 4, 4); EXPECT_EQ("beforeinput.data:hell;input.data:hell;", GetDocument().title()); // Replace the existing composition. GetDocument().setTitle(g_empty_string); Controller().SetComposition("hello", ime_text_spans, 0, 0); EXPECT_EQ("beforeinput.data:hello;input.data:hello;", GetDocument().title()); } TEST_F(InputMethodControllerTest, CompositionInputEventForConfirm) { CreateHTMLWithCompositionInputEventListeners(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); GetDocument().setTitle(g_empty_string); Controller().SetComposition("hello", ime_text_spans, 5, 5); EXPECT_EQ("beforeinput.data:hello;input.data:hello;", GetDocument().title()); // Confirm the ongoing composition. GetDocument().setTitle(g_empty_string); Controller().FinishComposingText(InputMethodController::kKeepSelection); EXPECT_EQ("compositionend.data:hello;", GetDocument().title()); } TEST_F(InputMethodControllerTest, CompositionInputEventForDelete) { CreateHTMLWithCompositionInputEventListeners(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); GetDocument().setTitle(g_empty_string); Controller().SetComposition("hello", ime_text_spans, 5, 5); EXPECT_EQ("beforeinput.data:hello;input.data:hello;", GetDocument().title()); // Delete the existing composition. GetDocument().setTitle(g_empty_string); Controller().SetComposition("", ime_text_spans, 0, 0); EXPECT_EQ("beforeinput.data:;input.data:null;compositionend.data:;", GetDocument().title()); } TEST_F(InputMethodControllerTest, CompositionInputEventForInsert) { CreateHTMLWithCompositionInputEventListeners(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); // Insert new text without previous composition. GetDocument().setTitle(g_empty_string); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().CommitText("hello", ime_text_spans, 0); EXPECT_EQ("beforeinput.data:hello;input.data:hello;", GetDocument().title()); GetDocument().setTitle(g_empty_string); Controller().SetComposition("n", ime_text_spans, 1, 1); EXPECT_EQ("beforeinput.data:n;input.data:n;", GetDocument().title()); // Insert new text with previous composition. GetDocument().setTitle(g_empty_string); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().CommitText("hello", ime_text_spans, 1); EXPECT_EQ( "beforeinput.data:hello;input.data:hello;compositionend.data:hello;", GetDocument().title()); } TEST_F(InputMethodControllerTest, CompositionInputEventForInsertEmptyText) { CreateHTMLWithCompositionInputEventListeners(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); // Insert empty text without previous composition. GetDocument().setTitle(g_empty_string); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().CommitText("", ime_text_spans, 0); EXPECT_EQ("", GetDocument().title().Utf8()); GetDocument().setTitle(g_empty_string); Controller().SetComposition("n", ime_text_spans, 1, 1); EXPECT_EQ("beforeinput.data:n;input.data:n;", GetDocument().title()); // Insert empty text with previous composition. GetDocument().setTitle(g_empty_string); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); Controller().CommitText("", ime_text_spans, 1); EXPECT_EQ("beforeinput.data:;input.data:null;compositionend.data:;", GetDocument().title()); } TEST_F(InputMethodControllerTest, CompositionEndEventWithNoSelection) { CreateHTMLWithCompositionEndEventListener(kNoSelection); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition("hello", ime_text_spans, 1, 1); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); 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(DocumentUpdateReason::kTest); EXPECT_TRUE(Controller().GetSelectionOffsets().IsNull()); } TEST_F(InputMethodControllerTest, FinishCompositionRemovedRange) { Element* input_a = InsertHTMLElement("
", "a"); EXPECT_EQ(kWebTextInputTypeText, Controller().TextInputType()); // The test requires non-empty composition. Controller().SetComposition("hello", Vector(), 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("
", "sample"); Vector ime_text_spans; Controller().CommitText(String(" "), ime_text_spans, 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, SetCompositionPlainTextWithIme_Text_Span) { InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 1, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition(" ", ime_text_spans, 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, SetCompositionPlainTextWithIme_Text_Span_Interim_Char_Selection) { InsertHTMLElement("
", "sample"); Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 1, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0, 0, false, true /*interim_char_selection*/)); Controller().SetComposition("a", ime_text_spans, 0, 1); ASSERT_EQ(1u, GetDocument().Markers().Markers().size()); auto* styleable_marker = DynamicTo(GetDocument().Markers().Markers()[0].Get()); EXPECT_EQ(ImeTextSpanUnderlineStyle::kSolid, styleable_marker->UnderlineStyle()); } TEST_F(InputMethodControllerTest, CommitPlainTextWithIme_Text_SpanInsert) { InsertHTMLElement("
Initial text.
", "sample"); Vector ime_text_spans; Controller().SetEditableSelectionOffsets(PlainTextRange(8, 8)); ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 1, 11, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().CommitText(String("ime_text_spand"), ime_text_spans, 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, CommitPlainTextWithIme_Text_SpanReplace) { InsertHTMLElement("
Initial text.
", "sample"); Vector ime_text_spans; Controller().SetCompositionFromExistingText(ime_text_spans, 8, 12); ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 1, 11, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().CommitText(String("string"), ime_text_spans, 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, ImeTextSpanAppearsCorrectlyAfterNewline) { Element* div = InsertHTMLElement("
", "sample"); Vector ime_text_spans; Controller().SetComposition(String("hello"), ime_text_spans, 6, 6); Controller().FinishComposingText(InputMethodController::kKeepSelection); GetFrame().GetEditor().InsertLineBreak(); Controller().SetCompositionFromExistingText(ime_text_spans, 8, 8); ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition(String("world"), ime_text_spans, 0, 0); ASSERT_EQ(1u, GetDocument().Markers().Markers().size()); // Verify composition marker shows up on the second line, not the first const Position& first_line_position = PlainTextRange(2).CreateRange(*div).StartPosition(); const Position& second_line_position = PlainTextRange(8).CreateRange(*div).StartPosition(); ASSERT_EQ( 0u, GetDocument() .Markers() .MarkersFor(To(*first_line_position.ComputeContainerNode())) .size()); ASSERT_EQ(1u, GetDocument() .Markers() .MarkersFor( To(*second_line_position.ComputeContainerNode())) .size()); // 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("
", "sample"); editable->focus(); // Simulate composition in the |contentEditable|. Vector ime_text_spans; ime_text_spans.push_back(ImeTextSpan( ImeTextSpan::Type::kComposition, 0, 5, Color(255, 0, 0), ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0)); Controller().SetComposition("foo", ime_text_spans, 3, 3); EXPECT_TRUE(Controller().HasComposition()); EXPECT_EQ(0u, GetCompositionRange()->startOffset()); EXPECT_EQ(3u, GetCompositionRange()->endOffset()); EXPECT_EQ(3, GetFrame() .Selection() .GetSelectionInDOMTree() .Base() .ComputeOffsetInContainerNode()); // Insert 'test'. NonThrowableExceptionState exception_state; GetDocument().execCommand("insertText", false, "test", exception_state); EXPECT_TRUE(Controller().HasComposition()); EXPECT_EQ(7, GetFrame() .Selection() .GetSelectionInDOMTree() .Base() .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() .GetSelectionInDOMTree() .Base() .ComputeOffsetInContainerNode()); } TEST_F(InputMethodControllerTest, SetEmptyCompositionShouldNotMoveCaret) { auto* textarea = To(InsertHTMLElement("", kWebTextInputFlagAutocapitalizeSentences}, {"", kWebTextInputFlagAutocapitalizeNone}, {"", kWebTextInputFlagAutocapitalizeCharacters}, {"", kWebTextInputFlagAutocapitalizeSentences}, {"", kWebTextInputFlagAutocapitalizeWords}, {"
", kWebTextInputFlagAutocapitalizeSentences}, {"
", kWebTextInputFlagAutocapitalizeNone}, {"
", kWebTextInputFlagAutocapitalizeCharacters}, {"
", kWebTextInputFlagAutocapitalizeSentences}, {"
", kWebTextInputFlagAutocapitalizeWords}, }; const int autocapitalize_mask = kWebTextInputFlagAutocapitalizeNone | kWebTextInputFlagAutocapitalizeCharacters | kWebTextInputFlagAutocapitalizeWords | kWebTextInputFlagAutocapitalizeSentences; for (const std::pair& element_and_expected_flags_pair : element_and_expected_flags_pairs) { const String& element = element_and_expected_flags_pair.first; const int expected_flags = element_and_expected_flags_pair.second; GetDocument().write(element); GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); To(GetDocument().body()->lastChild())->focus(); EXPECT_EQ(expected_flags, Controller().TextInputInfo().flags & autocapitalize_mask); } } TEST_F(InputMethodControllerTest, ExecCommandDuringComposition) { Element* div = InsertHTMLElement("
", "sample"); // Open a composition. Controller().SetComposition(String::FromUTF8("hello"), Vector(), 5, 5); // Turn on bold formatting. GetDocument().execCommand("bold", false, "", ASSERT_NO_EXCEPTION); // Extend the composition with some more text. Controller().SetComposition(String::FromUTF8("helloworld"), Vector(), 10, 10); // "world" should be bold. EXPECT_EQ("helloworld", div->innerHTML()); } TEST_F(InputMethodControllerTest, SetCompositionAfterNonEditableElement) { GetFrame().Selection().SetSelectionAndEndTyping( SetSelectionTextToBody("
" "a|b
")); Element* const div = GetDocument().getElementById("sample"); div->focus(); // Open a composition and insert some text. Controller().SetComposition(String::FromUTF8("c"), Vector(), 1, 1); // Add some more text to the composition. Controller().SetComposition(String::FromUTF8("cd"), Vector(), 2, 2); EXPECT_EQ( "
" "a^cd|b
", GetSelectionTextFromBody( SelectionInDOMTree::Builder() .SetBaseAndExtent(Controller().CompositionEphemeralRange()) .Build())); } TEST_F(InputMethodControllerTest, SetCompositionInTableCell) { GetFrame().Selection().SetSelection( SetSelectionTextToBody( "
a|
"), SetSelectionOptions()); Element* const table = GetDocument().getElementById("sample"); table->focus(); Controller().SetComposition(String::FromUTF8("c"), Vector(), 1, 1); Element* const td2 = GetDocument().getElementById("td2"); const Node* const text_node = td2->firstChild(); Range* range = GetCompositionRange(); EXPECT_EQ(text_node, range->startContainer()); EXPECT_EQ(0u, range->startOffset()); EXPECT_EQ(text_node, range->endContainer()); EXPECT_EQ(1u, range->endOffset()); } TEST_F(InputMethodControllerTest, SetCompositionInMyanmar) { Element* div = InsertHTMLElement("
", "sample"); // Add character U+200C: 'kZeroWidthNonJoinerCharacter' and Myanmar vowel Controller().SetComposition(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), Vector(), 0, 0); EXPECT_EQ(1u, div->CountChildren()); EXPECT_EQ(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), div->innerHTML()); Range* range = GetCompositionRange(); EXPECT_EQ(0u, range->startOffset()); EXPECT_EQ(2u, range->endOffset()); Controller().CommitText(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), Vector(), 1); EXPECT_EQ(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), div->innerHTML()); // Add character U+200C: 'kZeroWidthNonJoinerCharacter' and Myanmar vowel Controller().SetComposition(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), Vector(), 2, 2); Controller().CommitText(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), Vector(), 1); EXPECT_EQ( String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1\xE2\x80\x8C\xE1\x80\xB1"), div->innerHTML()); } TEST_F(InputMethodControllerTest, VirtualKeyboardPolicyOfFocusedElement) { EXPECT_EQ(ui::mojom::VirtualKeyboardPolicy::AUTO, Controller().VirtualKeyboardPolicyOfFocusedElement()); InsertHTMLElement("", "a") ->focus(); EXPECT_EQ(ui::mojom::VirtualKeyboardPolicy::MANUAL, Controller().VirtualKeyboardPolicyOfFocusedElement()); } TEST_F(InputMethodControllerTest, SetCompositionInTibetan) { GetFrame().Selection().SetSelectionAndEndTyping( SetSelectionTextToBody(u8"
|
")); Element* const div = GetDocument().getElementById("sample"); div->focus(); Vector ime_text_spans; Controller().SetComposition(String(Vector{0xF56}), ime_text_spans, 1, 1); EXPECT_EQ(u8"
\u0F56|
", GetSelectionTextFromBody()); Controller().CommitText(String(Vector{0xF56}), ime_text_spans, 0); EXPECT_EQ(u8"
\u0F56|
", GetSelectionTextFromBody()); Controller().SetComposition(String(Vector{0xFB7}), ime_text_spans, 1, 1); EXPECT_EQ(u8"
\u0F56\u0FB7|
", GetSelectionTextFromBody()); // Attempt to replace part of grapheme cluster "\u0FB7" in composition Controller().CommitText(String(Vector{0xFB7}), ime_text_spans, 0); EXPECT_EQ(u8"
\u0F56\u0FB7|
", GetSelectionTextFromBody()); Controller().SetComposition(String(Vector{0xF74}), ime_text_spans, 1, 1); EXPECT_EQ(u8"
\u0F56\u0FB7\u0F74|
", GetSelectionTextFromBody()); } TEST_F(InputMethodControllerTest, SetCompositionInDevanagari) { GetFrame().Selection().SetSelectionAndEndTyping(SetSelectionTextToBody( u8"
\u0958|
")); Element* const div = GetDocument().getElementById("sample"); div->focus(); Vector ime_text_spans; Controller().SetComposition(String(Vector{0x94D}), ime_text_spans, 1, 1); EXPECT_EQ(u8"
\u0958\u094D|
", GetSelectionTextFromBody()); Controller().CommitText(String(Vector{0x94D, 0x930}), ime_text_spans, 0); EXPECT_EQ(u8"
\u0958\u094D\u0930|
", GetSelectionTextFromBody()); } TEST_F(InputMethodControllerTest, SetCompositionTamil) { GetFrame().Selection().SetSelectionAndEndTyping( SetSelectionTextToBody(u8"
|
")); Element* const div = GetDocument().getElementById("sample"); div->focus(); Vector ime_text_spans; // Note: region starts out with space. Controller().CommitText(String(Vector{0xA0}), ime_text_spans, 0); // Add character U+0BB5: 'TAMIL LETTER VA' Controller().SetComposition(String(Vector{0xBB5}), ime_text_spans, 0, 0); // Add character U+0BC7: 'TAMIL VOWEL SIGN EE' Controller().CommitText(String(Vector{0xBB5, 0xBC7}), ime_text_spans, 1); EXPECT_EQ(u8"
\u00A0\u0BB5\u0BC7|
", GetSelectionTextFromBody()); } } // namespace blink