TEST_F(InputMethodControllerTest, DeleteBySettingEmptyComposition) { HTMLInputElement* input = toHTMLInputElement( insertHTMLElement("<input id='sample'>", "sample")); input->setValue("foo "); 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 "); 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.append(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, CompositionFireBeforeInput) { document().settings()->setScriptEnabled(true); Element* editable = insertHTMLElement("<div id='sample' contentEditable='true'></div>", "sample"); Element* script = document().createElement("script", ASSERT_NO_EXCEPTION); script->setInnerHTML( "document.getElementById('sample').addEventListener('beforeinput', function(event) {" " document.title = `beforeinput.isComposing:${event.isComposing};`;" "});" "document.getElementById('sample').addEventListener('input', function(event) {" " document.title += `input.isComposing:${event.isComposing};`;" "});", ASSERT_NO_EXCEPTION); document().body()->appendChild(script, ASSERT_NO_EXCEPTION); document().view()->updateAllLifecyclePhases(); // Simulate composition in the |contentEditable|. Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); editable->focus(); document().setTitle(emptyString()); controller().setComposition("foo", underlines, 0, 3); EXPECT_STREQ("beforeinput.isComposing:true;input.isComposing:true;", document().title().utf8().data()); document().setTitle(emptyString()); controller().confirmComposition(); // Last 'beforeinput' should also be inside composition scope. EXPECT_STREQ("beforeinput.isComposing:true;input.isComposing:true;", document().title().utf8().data()); }
TEST_F(InputMethodControllerTest, SetCompositionForInputWithDifferentNewCursorPositions) { HTMLInputElement* input = toHTMLInputElement( insertHTMLElement("<input id='sample'>", "sample")); input->setValue("hello"); 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.append(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0)); // The cursor exceeds left boundary. // "*heABllo", where * stands for cursor. 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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, SetCompositionFromExistingTextWithInvalidOffsets) { insertHTMLElement("<div id='sample' contenteditable='true'>test</div>", "sample"); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(7, 8, Color(255, 0, 0), false, 0)); controller().setCompositionFromExistingText(underlines, 7, 8); EXPECT_FALSE(controller().compositionRange()); }
void WebViewInputMethodFilter::setPreedit(String newPreedit, int cursorOffset) { Frame* frame = focusedOrMainFrame(); if (!frame || !frame->editor().canEdit()) return; // TODO: We should parse the PangoAttrList that we get from the IM context here. Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, newPreedit.length(), Color(1, 1, 1), false)); frame->editor().setComposition(newPreedit, underlines, m_cursorOffset, m_cursorOffset); }
TEST_F(InputMethodControllerTest, ConfirmPasswordComposition) { HTMLInputElement* input = toHTMLInputElement( insertHTMLElement("<input id='sample' type='password' size='24'>", "sample")); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); controller().setComposition("foo", underlines, 0, 3); controller().confirmComposition(); EXPECT_STREQ("foo", input->value().utf8().data()); }
TEST_F(InputMethodControllerTest, SelectionOnConfirmExistingText) { insertHTMLElement( "<div id='sample' contenteditable='true'>hello world</div>", "sample"); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); controller().setCompositionFromExistingText(underlines, 0, 5); controller().confirmComposition(); EXPECT_EQ(0, frame().selection().start().computeOffsetInContainerNode()); EXPECT_EQ(0, frame().selection().end().computeOffsetInContainerNode()); }
void InputMethodFilter::updatePreedit() { #if ENABLE(API_TESTS) if (m_testingMode) { logSetPreeditForTesting(); return; } #endif // FIXME: We should parse the PangoAttrList that we get from the IM context here. m_page->setComposition(m_preedit, Vector<CompositionUnderline>{ CompositionUnderline(0, m_preedit.length(), Color(1, 1, 1), false) }, m_cursorOffset, m_cursorOffset, 0 /* replacement start */, 0 /* replacement end */); m_preeditChanged = false; }
static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client) { Frame* frame = core(static_cast<WebKitWebView*>(client->webView()))->focusController()->focusedOrMainFrame(); if (!frame || !frame->editor()->canEdit()) return; // We ignore the provided PangoAttrList for now. GOwnPtr<gchar> newPreedit(0); gtk_im_context_get_preedit_string(context, &newPreedit.outPtr(), 0, 0); String preeditString = String::fromUTF8(newPreedit.get()); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false)); frame->editor()->setComposition(preeditString, underlines, 0, 0); }
TEST_F(InputMethodControllerTest, SetCompositionFromExistingText) { Element* div = insertHTMLElement( "<div id='sample' contenteditable='true'>hello world</div>", "sample"); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); controller().setCompositionFromExistingText(underlines, 0, 5); Range* range = controller().compositionRange(); EXPECT_EQ(0, range->startOffset()); EXPECT_EQ(5, range->endOffset()); PlainTextRange plainTextRange(PlainTextRange::create(*div, *range)); EXPECT_EQ(0u, plainTextRange.start()); EXPECT_EQ(5u, plainTextRange.end()); }
void InputMethodContextEfl::onIMFPreeditSequenceChanged(void* data, Ecore_IMF_Context* context, void*) { InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data); if (!inputMethodContext->m_view->page()->focusedFrame() || !inputMethodContext->m_focused) return; char* buffer = 0; ecore_imf_context_preedit_string_get(context, &buffer, 0); if (!buffer) return; String preeditString = String::fromUTF8(buffer); free(buffer); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false)); inputMethodContext->m_view->page()->setComposition(preeditString, underlines, 0); }
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='true'>\nhello world</div>", "sample"); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); controller().setCompositionFromExistingText(underlines, 0, 5); Range* range = controller().compositionRange(); EXPECT_EQ(1, range->startOffset()); EXPECT_EQ(6, range->endOffset()); PlainTextRange plainTextRange(PlainTextRange::create(*div, *range)); EXPECT_EQ(0u, plainTextRange.start()); EXPECT_EQ(5u, plainTextRange.end()); }
static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client) { Frame* frame = core(client->m_webView)->focusController()->focusedOrMainFrame(); Editor* editor = frame->editor(); gchar* preedit = NULL; gint cursorPos = 0; // We ignore the provided PangoAttrList for now. gtk_im_context_get_preedit_string(context, &preedit, NULL, &cursorPos); String preeditString = String::fromUTF8(preedit); g_free(preedit); // setComposition() will replace the user selection if passed an empty // preedit. We don't want this to happen. if (preeditString.isEmpty() && !editor->hasComposition()) return; Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false)); editor->setComposition(preeditString, underlines, cursorPos, 0); }
void InputMethodFilter::handleKeyboardEventWithCompositionResults(GdkEventKey* event, ResultsToSend resultsToSend, EventFakedForComposition faked) { #if ENABLE(API_TESTS) if (m_testingMode) { logHandleKeyboardEventWithCompositionResultsForTesting(event, resultsToSend, faked); return; } #endif if (m_filterKeyEventCompletionHandler) { m_filterKeyEventCompletionHandler(CompositionResults(CompositionResults::WillSendCompositionResultsSoon), faked); m_filterKeyEventCompletionHandler = nullptr; } else m_page->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(event), CompositionResults(CompositionResults::WillSendCompositionResultsSoon), faked, Vector<String>())); if (resultsToSend & Composition && !m_confirmedComposition.isNull()) m_page->confirmComposition(m_confirmedComposition, -1, 0); if (resultsToSend & Preedit && !m_preedit.isNull()) { m_page->setComposition(m_preedit, Vector<CompositionUnderline>{ CompositionUnderline(0, m_preedit.length(), Color(1, 1, 1), false) }, m_cursorOffset, m_cursorOffset, 0 /* replacement start */, 0 /* replacement end */); } }
TEST_F(InputMethodControllerTest, SetCompositionForContentEditableWithDifferentNewCursorPositions) { // 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='true'>" "hello" "<div id='sample2' contenteditable='true'>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.append(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0)); // The cursor exceeds left boundary. // "*hello\nworld\n01234AB56789", where * stands for cursor. 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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 cursor 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()); }
void QtWebPageEventHandler::handleInputMethodEvent(QInputMethodEvent* ev) { QString commit = ev->commitString(); QString composition = ev->preeditString(); int replacementStart = ev->replacementStart(); int replacementLength = ev->replacementLength(); // NOTE: We might want to handle events of one char as special // and resend them as key events to make web site completion work. int cursorPositionWithinComposition = 0; Vector<CompositionUnderline> underlines; for (int i = 0; i < ev->attributes().size(); ++i) { const QInputMethodEvent::Attribute& attr = ev->attributes().at(i); switch (attr.type) { case QInputMethodEvent::TextFormat: { if (composition.isEmpty()) break; QTextCharFormat textCharFormat = attr.value.value<QTextFormat>().toCharFormat(); QColor qcolor = textCharFormat.underlineColor(); Color color = makeRGBA(qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha()); int start = qMin(attr.start, (attr.start + attr.length)); int end = qMax(attr.start, (attr.start + attr.length)); underlines.append(CompositionUnderline(start, end, color, false)); break; } case QInputMethodEvent::Cursor: if (attr.length) cursorPositionWithinComposition = attr.start; break; // Selection is handled further down. default: break; } } if (composition.isEmpty()) { int selectionStart = -1; int selectionLength = 0; for (int i = 0; i < ev->attributes().size(); ++i) { const QInputMethodEvent::Attribute& attr = ev->attributes().at(i); if (attr.type == QInputMethodEvent::Selection) { selectionStart = attr.start; selectionLength = attr.length; ASSERT(selectionStart >= 0); ASSERT(selectionLength >= 0); break; } } m_webPageProxy->confirmComposition(commit, selectionStart, selectionLength); } else { ASSERT(cursorPositionWithinComposition >= 0); ASSERT(replacementStart >= 0); m_webPageProxy->setComposition(composition, underlines, cursorPositionWithinComposition, cursorPositionWithinComposition, replacementStart, replacementLength); } ev->accept(); }