summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Varga <pvarga@inf.u-szeged.hu>2018-04-18 16:46:31 +0200
committerPeter Varga <pvarga@inf.u-szeged.hu>2018-04-26 14:11:38 +0000
commit216240a31baae6e54e38de8157332f272ddf57a7 (patch)
treef9c14206827960bc8e91c9d1704398568012fb07
parentb3b95a5bf3f04c18182fcc4519ec8285e290037d (diff)
Fix finishing IME composition
Call RenderWidgetHostImpl::ImeCommitText() instead of RenderWidgetHostImpl::ImeFinishComposingText() to trigger the necessary JavaScript events on composing. This fixes IME composition (eg. for dead keys) on web pages which use custom JavaScript IME handler like facebook. Task-number: QTBUG-66046 Change-Id: Ibc177995ba6e85eca42ae333decacfe6e788ce41 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r--src/core/render_widget_host_view_qt.cpp98
-rw-r--r--src/core/render_widget_host_view_qt.h2
-rw-r--r--tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp136
3 files changed, 185 insertions, 51 deletions
diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp
index 66d9b819e..ec3add2f6 100644
--- a/src/core/render_widget_host_view_qt.cpp
+++ b/src/core/render_widget_host_view_qt.cpp
@@ -327,7 +327,7 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget
, m_adapterClient(0)
, m_rendererCompositorFrameSink(0)
, m_imeInProgress(false)
- , m_receivedEmptyImeText(false)
+ , m_receivedEmptyImeEvent(false)
, m_initPending(false)
, m_beginFrameSource(nullptr)
, m_needsBeginFrames(false)
@@ -1219,22 +1219,20 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev)
if (IsMouseLocked() && ev->key() == Qt::Key_Escape && ev->type() == QEvent::KeyRelease)
UnlockMouse();
- if (m_receivedEmptyImeText) {
+ if (m_receivedEmptyImeEvent) {
// IME composition was not finished with a valid commit string.
// We're getting the composition result in a key event.
if (ev->key() != 0) {
// The key event is not a result of an IME composition. Cancel IME.
m_host->ImeCancelComposition();
- m_receivedEmptyImeText = false;
+ m_receivedEmptyImeEvent = false;
} else {
if (ev->type() == QEvent::KeyRelease) {
- m_receivedEmptyImeText = false;
- m_host->ImeSetComposition(toString16(ev->text()),
- std::vector<ui::ImeTextSpan>(),
- gfx::Range::InvalidRange(),
- gfx::Range::InvalidRange().start(),
- gfx::Range::InvalidRange().end());
- m_host->ImeFinishComposingText(false);
+ m_host->ImeCommitText(toString16(ev->text()),
+ std::vector<ui::ImeTextSpan>(),
+ gfx::Range::InvalidRange(),
+ 0);
+ m_receivedEmptyImeEvent = false;
m_imeInProgress = false;
}
return;
@@ -1365,54 +1363,54 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
}
}
- auto setCompositionString = [&](const QString &compositionString){
- m_host->ImeSetComposition(toString16(compositionString),
- underlines,
- replacementRange,
- selectionRange.start(),
- selectionRange.end());
- };
-
- if (!commitString.isEmpty() || replacementLength > 0) {
- setCompositionString(commitString);
- m_host->ImeFinishComposingText(false);
-
- // We might get a commit string and a pre-edit string in a single event, which means
- // we need to confirm the怀last composition, and start a new composition.
- if (!preeditString.isEmpty()) {
- setCompositionString(preeditString);
- m_imeInProgress = true;
- } else {
- m_imeInProgress = false;
- }
- m_receivedEmptyImeText = commitString.isEmpty();
- } else if (!preeditString.isEmpty()) {
- setCompositionString(preeditString);
- m_imeInProgress = true;
- m_receivedEmptyImeText = false;
- } else {
- // There are so-far two known cases, when an empty QInputMethodEvent is received.
- // First one happens when backspace is used to remove the last character in the pre-edit
- // string, thus signaling the end of the composition.
- // The second one happens (on Windows) when a Korean char gets composed, but instead of
- // the event having a commit string, both strings are empty, and the actual char is received
- // as a QKeyEvent after the QInputMethodEvent is processed.
- // In lieu of the second case, we can't simply cancel the composition on an empty event,
- // and then add the Korean char when QKeyEvent is received, because that leads to text
- // flickering in the textarea (or any other element).
- // Instead we postpone the processing of the empty QInputMethodEvent by posting it
- // to the same focused object, and cancelling the composition on the next event loop tick.
- if (!m_receivedEmptyImeText && m_imeInProgress && !hasSelection) {
- m_receivedEmptyImeText = true;
+ // There are so-far two known cases, when an empty QInputMethodEvent is received.
+ // First one happens when backspace is used to remove the last character in the pre-edit
+ // string, thus signaling the end of the composition.
+ // The second one happens (on Windows) when a Korean char gets composed, but instead of
+ // the event having a commit string, both strings are empty, and the actual char is received
+ // as a QKeyEvent after the QInputMethodEvent is processed.
+ // In lieu of the second case, we can't simply cancel the composition on an empty event,
+ // and then add the Korean char when QKeyEvent is received, because that leads to text
+ // flickering in the textarea (or any other element).
+ // Instead we postpone the processing of the empty QInputMethodEvent by posting it
+ // to the same focused object, and cancelling the composition on the next event loop tick.
+ if (commitString.isEmpty() && preeditString.isEmpty() && replacementLength == 0) {
+ if (!m_receivedEmptyImeEvent && m_imeInProgress && !hasSelection) {
+ m_receivedEmptyImeEvent = true;
QInputMethodEvent *eventCopy = new QInputMethodEvent(*ev);
QGuiApplication::postEvent(qApp->focusObject(), eventCopy);
} else {
- m_receivedEmptyImeText = false;
+ m_receivedEmptyImeEvent = false;
if (m_imeInProgress) {
m_imeInProgress = false;
m_host->ImeCancelComposition();
}
}
+
+ return;
+ }
+
+ m_receivedEmptyImeEvent = false;
+
+ // Finish compostion: insert or erase text.
+ if (!commitString.isEmpty() || replacementLength > 0) {
+ m_host->ImeCommitText(toString16(commitString),
+ underlines,
+ replacementRange,
+ 0);
+ m_imeInProgress = false;
+ }
+
+ // Update or start new composition.
+ // Be aware of that, we might get a commit string and a pre-edit string in a single event and
+ // this means a new composition.
+ if (!preeditString.isEmpty()) {
+ m_host->ImeSetComposition(toString16(preeditString),
+ underlines,
+ replacementRange,
+ selectionRange.start(),
+ selectionRange.end());
+ m_imeInProgress = true;
}
}
diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h
index db68e5232..2a1485510 100644
--- a/src/core/render_widget_host_view_qt.h
+++ b/src/core/render_widget_host_view_qt.h
@@ -253,7 +253,7 @@ private:
viz::mojom::CompositorFrameSinkClient *m_rendererCompositorFrameSink;
bool m_imeInProgress;
- bool m_receivedEmptyImeText;
+ bool m_receivedEmptyImeEvent;
QPoint m_previousMousePosition;
bool m_initPending;
diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
index 207836bef..24e581870 100644
--- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
+++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
@@ -173,6 +173,7 @@ private Q_SLOTS:
void imeCompositionQueryEvent_data();
void imeCompositionQueryEvent();
void newlineInTextarea();
+ void imeJSInputEvents();
void mouseLeave();
@@ -2249,6 +2250,141 @@ void tst_QWebEngineView::newlineInTextarea()
QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("\n\nthird line"));
}
+void tst_QWebEngineView::imeJSInputEvents()
+{
+ QWebEngineView view;
+ view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
+ view.show();
+
+ auto logLines = [&view]() -> QStringList {
+ return evaluateJavaScriptSync(view.page(), "log.textContent").toString().split("\n").filter(QRegExp(".+"));
+ };
+
+ QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool)));
+ view.page()->setHtml("<html>"
+ "<head><script>"
+ " var input, log;"
+ " function verboseEvent(ev) {"
+ " log.textContent += ev + ' ' + ev.type + ' ' + ev.data + '\\n';"
+ " }"
+ " function clear(ev) {"
+ " log.textContent = '';"
+ " input.textContent = '';"
+ " }"
+ " function init() {"
+ " input = document.getElementById('input');"
+ " log = document.getElementById('log');"
+ " events = [ 'textInput', 'beforeinput', 'input', 'compositionstart', 'compositionupdate', 'compositionend' ];"
+ " for (var e in events)"
+ " input.addEventListener(events[e], verboseEvent);"
+ " }"
+ "</script></head>"
+ "<body onload='init()'>"
+ " <div id='input' contenteditable='true' style='border-style: solid;'></div>"
+ " <pre id='log'></pre>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+
+ evaluateJavaScriptSync(view.page(), "document.getElementById('input').focus()");
+ QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input"));
+
+ // 1. Commit text (this is how dead keys work on Linux).
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ event.setCommitString("commit");
+ QApplication::sendEvent(view.focusProxy(), &event);
+ qApp->processEvents();
+ }
+
+ // Simply committing text should not trigger any JS composition event.
+ QTRY_COMPARE(logLines().count(), 3);
+ QCOMPARE(logLines()[0], "[object InputEvent] beforeinput commit");
+ QCOMPARE(logLines()[1], "[object TextEvent] textInput commit");
+ QCOMPARE(logLines()[2], "[object InputEvent] input commit");
+
+ evaluateJavaScriptSync(view.page(), "clear()");
+ QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty());
+
+ // 2. Start composition then commit text (this is how dead keys work on macOS).
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("preedit", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ qApp->processEvents();
+ }
+
+ QTRY_COMPARE(logLines().count(), 4);
+ QCOMPARE(logLines()[0], "[object CompositionEvent] compositionstart ");
+ QCOMPARE(logLines()[1], "[object InputEvent] beforeinput preedit");
+ QCOMPARE(logLines()[2], "[object CompositionEvent] compositionupdate preedit");
+ QCOMPARE(logLines()[3], "[object InputEvent] input preedit");
+
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ event.setCommitString("commit");
+ QApplication::sendEvent(view.focusProxy(), &event);
+ qApp->processEvents();
+ }
+
+ QTRY_COMPARE(logLines().count(), 9);
+ QCOMPARE(logLines()[4], "[object InputEvent] beforeinput commit");
+ QCOMPARE(logLines()[5], "[object CompositionEvent] compositionupdate commit");
+ QCOMPARE(logLines()[6], "[object TextEvent] textInput commit");
+ QCOMPARE(logLines()[7], "[object InputEvent] input commit");
+ QCOMPARE(logLines()[8], "[object CompositionEvent] compositionend commit");
+
+ evaluateJavaScriptSync(view.page(), "clear()");
+ QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty());
+
+ // 3. Start composition then cancel it with an empty IME event.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("preedit", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ qApp->processEvents();
+ }
+
+ QTRY_COMPARE(logLines().count(), 4);
+ QCOMPARE(logLines()[0], "[object CompositionEvent] compositionstart ");
+ QCOMPARE(logLines()[1], "[object InputEvent] beforeinput preedit");
+ QCOMPARE(logLines()[2], "[object CompositionEvent] compositionupdate preedit");
+ QCOMPARE(logLines()[3], "[object InputEvent] input preedit");
+
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ qApp->processEvents();
+ }
+
+ QTRY_COMPARE(logLines().count(), 9);
+ QCOMPARE(logLines()[4], "[object InputEvent] beforeinput ");
+ QCOMPARE(logLines()[5], "[object CompositionEvent] compositionupdate ");
+ QCOMPARE(logLines()[6], "[object TextEvent] textInput ");
+ QCOMPARE(logLines()[7], "[object InputEvent] input null");
+ QCOMPARE(logLines()[8], "[object CompositionEvent] compositionend ");
+
+ evaluateJavaScriptSync(view.page(), "clear()");
+ QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty());
+
+ // 4. Send empty IME event.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ qApp->processEvents();
+ }
+
+ // No JS event is expected.
+ QTest::qWait(100);
+ QVERIFY(logLines().isEmpty());
+
+ evaluateJavaScriptSync(view.page(), "clear()");
+ QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty());
+}
+
void tst_QWebEngineView::imeCompositionQueryEvent_data()
{
QTest::addColumn<QString>("receiverObjectName");