summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Varga <pvarga@inf.u-szeged.hu>2016-11-29 13:10:21 +0100
committerPeter Varga <pvarga@inf.u-szeged.hu>2017-04-12 15:02:36 +0000
commit78417958b7172ec4968088fc3a908a219ed848f6 (patch)
tree33ddd78db29b945fe7673c12b2757ac0bcc59d13
parentab82c1d1adcc79e56d8287ea322ab039dedd8b32 (diff)
Fix text selection and input method query
Instruct the render process to change the text selection if it was requested via an input method event. Raise the selectionChanged() signal when all the corresponding input method properties are set. Moreover, add back the remaining input method widget auto tests. The updated tests are moved to the QWebEngineView tests since the corresponding APIs (inputMethodQuery() and input event handling) are now available via the QWebEngineView's focus proxy (aka RWHV). Task-number: QTBUG-55766 Change-Id: Ia0022d5f38b31dd59b084ff42e4abc2780ae90ec Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r--src/core/render_widget_host_view_qt.cpp237
-rw-r--r--src/core/render_widget_host_view_qt.h22
-rw-r--r--tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp565
-rw-r--r--tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp385
4 files changed, 590 insertions, 619 deletions
diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp
index f96329139..619577d93 100644
--- a/src/core/render_widget_host_view_qt.cpp
+++ b/src/core/render_widget_host_view_qt.cpp
@@ -54,9 +54,10 @@
#include "base/command_line.h"
#include "cc/output/direct_renderer.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
+#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
-#include "content/browser/renderer_host/text_input_manager.h"
#include "content/common/cursors/webcursor.h"
+#include "content/common/input_messages.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
@@ -92,6 +93,13 @@
namespace QtWebEngineCore {
+enum ImStateFlags {
+ TextInputStateUpdated = 1 << 0,
+ TextSelectionUpdated = 1 << 1,
+ TextSelectionBoundsUpdated = 1 << 2,
+ AllFlags = TextInputStateUpdated | TextSelectionUpdated | TextSelectionBoundsUpdated
+};
+
static inline ui::LatencyInfo CreateLatencyInfo(const blink::WebInputEvent& event) {
ui::LatencyInfo latency_info;
// The latency number should only be added if the timestamp is valid.
@@ -238,13 +246,17 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost* widget
, m_needsDelegatedFrameAck(false)
, m_loadVisuallyCommittedState(NotCommitted)
, m_adapterClient(0)
- , m_currentInputType(ui::TEXT_INPUT_TYPE_NONE)
, m_imeInProgress(false)
, m_receivedEmptyImeText(false)
, m_initPending(false)
, m_beginFrameSource(nullptr)
, m_needsBeginFrames(false)
, m_addedFrameObserver(false)
+ , m_imState(0)
+ , m_anchorPositionWithinSelection(0)
+ , m_cursorPositionWithinSelection(0)
+ , m_cursorPosition(0)
+ , m_emptyPreviousSelection(true)
{
m_host->SetView(this);
#ifndef QT_NO_ACCESSIBILITY
@@ -255,6 +267,9 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost* widget
auto* task_runner = base::ThreadTaskRunnerHandle::Get().get();
m_beginFrameSource.reset(new cc::DelayBasedBeginFrameSource(
base::MakeUnique<cc::DelayBasedTimeSource>(task_runner)));
+
+ if (GetTextInputManager())
+ GetTextInputManager()->AddObserver(this);
}
RenderWidgetHostViewQt::~RenderWidgetHostViewQt()
@@ -263,6 +278,9 @@ RenderWidgetHostViewQt::~RenderWidgetHostViewQt()
#ifndef QT_NO_ACCESSIBILITY
QAccessible::removeActivationObserver(this);
#endif // QT_NO_ACCESSIBILITY
+
+ if (text_input_manager_)
+ text_input_manager_->RemoveObserver(this);
}
void RenderWidgetHostViewQt::setDelegate(RenderWidgetHostViewQtDelegate* delegate)
@@ -576,15 +594,6 @@ void RenderWidgetHostViewQt::SetIsLoading(bool)
// We use WebContentsDelegateQt::LoadingStateChanged to notify about loading state.
}
-void RenderWidgetHostViewQt::TextInputStateChanged(const content::TextInputState &params)
-{
- m_currentInputType = params.type;
- m_delegate->inputMethodStateChanged(params.type != ui::TEXT_INPUT_TYPE_NONE);
- m_delegate->setInputMethodHints(toQtInputMethodHints(params.type));
-
- m_surroundingText = QString::fromStdString(params.value);
-}
-
void RenderWidgetHostViewQt::ImeCancelComposition()
{
qApp->inputMethod()->reset();
@@ -707,16 +716,114 @@ void RenderWidgetHostViewQt::ClearCompositorFrame()
{
}
-void RenderWidgetHostViewQt::SelectionChanged(const base::string16 &text, size_t offset, const gfx::Range &range)
+void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view, bool did_update_state)
{
- content::RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
- m_adapterClient->selectionChanged();
+ Q_UNUSED(text_input_manager);
+ Q_UNUSED(updated_view);
+ Q_UNUSED(did_update_state);
+
+ ui::TextInputType type = getTextInputType();
+ m_delegate->inputMethodStateChanged(type != ui::TEXT_INPUT_TYPE_NONE);
+ m_delegate->setInputMethodHints(toQtInputMethodHints(type));
+
+ const content::TextInputState *state = text_input_manager_->GetTextInputState();
+ if (!state)
+ return;
+
+ if (GetSelectedText().empty())
+ m_cursorPosition = state->selection_start;
+
+ m_surroundingText = QString::fromStdString(state->value);
+
+ // Remove IME composition text from the surrounding text
+ if (state->composition_start != -1 && state->composition_end != -1)
+ m_surroundingText.remove(state->composition_start, state->composition_end - state->composition_start);
+
+ if (m_imState & ImStateFlags::TextInputStateUpdated) {
+ m_imState = ImStateFlags::TextInputStateUpdated;
+ return;
+ }
+
+ // Ignore selection change triggered by ime composition unless it clears an actual text selection
+ if (state->composition_start != -1 && m_emptyPreviousSelection) {
+ m_imState = 0;
+ return;
+ }
+
+ m_imState |= ImStateFlags::TextInputStateUpdated;
+ if (m_imState == ImStateFlags::AllFlags)
+ selectionChanged();
+}
+
+void RenderWidgetHostViewQt::OnSelectionBoundsChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view)
+{
+ Q_UNUSED(text_input_manager);
+ Q_UNUSED(updated_view);
+
+ m_imState |= ImStateFlags::TextSelectionBoundsUpdated;
+ if (m_imState == ImStateFlags::AllFlags)
+ selectionChanged();
+}
+
+void RenderWidgetHostViewQt::OnTextSelectionChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view)
+{
+ Q_UNUSED(text_input_manager);
+ Q_UNUSED(updated_view);
#if defined(USE_X11)
- // Set the CLIPBOARD_TYPE_SELECTION to the ui::Clipboard.
- ui::ScopedClipboardWriter clipboard_writer(ui::CLIPBOARD_TYPE_SELECTION);
- clipboard_writer.WriteText(text);
-#endif
+ if (!GetSelectedText().empty()) {
+ // Set the CLIPBOARD_TYPE_SELECTION to the ui::Clipboard.
+ ui::ScopedClipboardWriter clipboard_writer(ui::CLIPBOARD_TYPE_SELECTION);
+ clipboard_writer.WriteText(GetSelectedText());
+ }
+#endif // defined(USE_X11)
+
+ m_imState |= ImStateFlags::TextSelectionUpdated;
+ if (m_imState == ImStateFlags::AllFlags)
+ selectionChanged();
+}
+
+void RenderWidgetHostViewQt::selectionChanged()
+{
+ // Reset input manager state
+ m_imState = 0;
+
+ const content::TextInputManager::TextSelection *selection = text_input_manager_->GetTextSelection();
+ if (!selection)
+ return;
+
+ if (!selection->range.IsValid())
+ return;
+
+ // Avoid duplicate empty selectionChanged() signals
+ if (GetSelectedText().empty() && m_emptyPreviousSelection) {
+ m_anchorPositionWithinSelection = m_cursorPosition;
+ m_cursorPositionWithinSelection = m_cursorPosition;
+ return;
+ }
+
+ uint newAnchorPositionWithinSelection = 0;
+ uint newCursorPositionWithinSelection = 0;
+
+ if (text_input_manager_->GetSelectionRegion()->anchor.type() == gfx::SelectionBound::RIGHT) {
+ newAnchorPositionWithinSelection = selection->range.GetMax() - selection->offset;
+ newCursorPositionWithinSelection = selection->range.GetMin() - selection->offset;
+ } else {
+ newAnchorPositionWithinSelection = selection->range.GetMin() - selection->offset;
+ newCursorPositionWithinSelection = selection->range.GetMax() - selection->offset;
+ }
+
+ if (m_anchorPositionWithinSelection == newAnchorPositionWithinSelection && m_cursorPositionWithinSelection == newCursorPositionWithinSelection)
+ return;
+
+ m_anchorPositionWithinSelection = newAnchorPositionWithinSelection;
+ m_cursorPositionWithinSelection = newCursorPositionWithinSelection;
+
+ if (!GetSelectedText().empty())
+ m_cursorPosition = newCursorPositionWithinSelection;
+
+ m_emptyPreviousSelection = GetSelectedText().empty();
+ m_adapterClient->selectionChanged();
}
void RenderWidgetHostViewQt::OnGestureEvent(const ui::GestureEventData& gesture)
@@ -820,34 +927,27 @@ QVariant RenderWidgetHostViewQt::inputMethodQuery(Qt::InputMethodQuery query)
{
switch (query) {
case Qt::ImEnabled:
- return QVariant(m_currentInputType != ui::TEXT_INPUT_TYPE_NONE);
+ return QVariant(getTextInputType() != ui::TEXT_INPUT_TYPE_NONE);
case Qt::ImFont:
+ // TODO: Implement this
return QVariant();
case Qt::ImCursorRectangle:
- // QIBusPlatformInputContext might query ImCursorRectangle before the
- // RenderWidgetHostView is created. Without an available view GetSelectionRange()
- // returns nullptr.
- if (!GetTextInputManager()->GetSelectionRegion())
+ if (!text_input_manager_ || !text_input_manager_->GetActiveWidget())
return QVariant();
- return toQt(GetTextInputManager()->GetSelectionRegion()->caret_rect);
+ return toQt(text_input_manager_->GetSelectionRegion()->caret_rect);
case Qt::ImCursorPosition:
- Q_ASSERT(GetTextInputManager()->GetSelectionRegion());
- return toQt(GetTextInputManager()->GetSelectionRegion()->focus.edge_top_rounded().x());
+ return m_cursorPosition;
case Qt::ImAnchorPosition:
- Q_ASSERT(GetTextInputManager()->GetSelectionRegion());
- return toQt(GetTextInputManager()->GetSelectionRegion()->anchor.edge_top_rounded().x());
+ return GetSelectedText().empty() ? m_cursorPosition : m_anchorPositionWithinSelection;
case Qt::ImSurroundingText:
return m_surroundingText;
- case Qt::ImCurrentSelection: {
- Q_ASSERT(GetTextInputManager()->GetTextSelection());
- base::string16 text;
- GetTextInputManager()->GetTextSelection()->GetSelectedText(&text);
- return toQt(text);
- }
+ case Qt::ImCurrentSelection:
+ return toQt(GetSelectedText());
case Qt::ImMaximumTextLength:
+ // TODO: Implement this
return QVariant(); // No limit.
case Qt::ImHints:
- return int(toQtInputMethodHints(m_currentInputType));
+ return int(toQtInputMethodHints(getTextInputType()));
default:
return QVariant();
}
@@ -1005,6 +1105,9 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev)
void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
{
+ // Reset input manager state
+ m_imState = 0;
+
if (!m_host)
return;
@@ -1016,19 +1119,9 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
const QList<QInputMethodEvent::Attribute> &attributes = ev->attributes();
std::vector<blink::WebCompositionUnderline> underlines;
- auto ensureValidSelectionRange = [&]() {
- if (!selectionRange.IsValid()) {
- // We did not receive a valid selection range, hence the range is going to mark the
- // cursor position.
- int newCursorPosition =
- (cursorPositionInPreeditString < 0) ? preeditString.length()
- : cursorPositionInPreeditString;
- selectionRange.set_start(newCursorPosition);
- selectionRange.set_end(newCursorPosition);
- }
- };
+ bool hasSelection = false;
- Q_FOREACH (const QInputMethodEvent::Attribute &attribute, attributes) {
+ for (const auto &attribute : attributes) {
switch (attribute.type) {
case QInputMethodEvent::TextFormat: {
if (preeditString.isEmpty())
@@ -1065,6 +1158,15 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
cursorPositionInPreeditString = attribute.start;
break;
case QInputMethodEvent::Selection:
+ hasSelection = true;
+
+ // Cancel IME composition
+ if (preeditString.isEmpty() && attribute.start + attribute.length == 0) {
+ selectionRange.set_start(0);
+ selectionRange.set_end(0);
+ break;
+ }
+
selectionRange.set_start(qMin(attribute.start, (attribute.start + attribute.length)));
selectionRange.set_end(qMax(attribute.start, (attribute.start + attribute.length)));
break;
@@ -1073,6 +1175,22 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
}
}
+ if (!selectionRange.IsValid()) {
+ // We did not receive a valid selection range, hence the range is going to mark the
+ // cursor position.
+ int newCursorPosition =
+ (cursorPositionInPreeditString < 0) ? preeditString.length()
+ : cursorPositionInPreeditString;
+ selectionRange.set_start(newCursorPosition);
+ selectionRange.set_end(newCursorPosition);
+ }
+
+ if (hasSelection) {
+ content::RenderFrameHost *frameHost = getFocusedFrameHost();
+ if (frameHost)
+ frameHost->Send(new InputMsg_SetEditableSelectionOffsets(frameHost->GetRoutingID(), selectionRange.start(), selectionRange.end()));
+ }
+
int replacementLength = ev->replacementLength();
gfx::Range replacementRange = gfx::Range::InvalidRange();
@@ -1089,7 +1207,6 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
}
auto setCompositionString = [&](const QString &compositionString){
- ensureValidSelectionRange();
m_host->ImeSetComposition(toString16(compositionString),
underlines,
replacementRange,
@@ -1126,7 +1243,7 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev)
// 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) {
+ if (!m_receivedEmptyImeText && m_imeInProgress && !hasSelection) {
m_receivedEmptyImeText = true;
m_imeInProgress = false;
QInputMethodEvent *eventCopy = new QInputMethodEvent(*ev);
@@ -1304,4 +1421,26 @@ void RenderWidgetHostViewQt::OnBeginFrameSourcePausedChanged(bool paused)
// begin frames.
}
+content::RenderFrameHost *RenderWidgetHostViewQt::getFocusedFrameHost()
+{
+ content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(m_host);
+ if (!viewHost)
+ return nullptr;
+
+ content::FrameTreeNode *focusedFrame = viewHost->GetDelegate()->GetFrameTree()->GetFocusedFrame();
+ if (!focusedFrame)
+ return nullptr;
+
+ return focusedFrame->current_frame_host();
+}
+
+ui::TextInputType RenderWidgetHostViewQt::getTextInputType() const
+{
+ if (text_input_manager_ && text_input_manager_->GetTextInputState())
+ return text_input_manager_->GetTextInputState()->type;
+
+ return ui::TEXT_INPUT_TYPE_NONE;
+}
+
+
} // namespace QtWebEngineCore
diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h
index 1dae96a53..78b946a60 100644
--- a/src/core/render_widget_host_view_qt.h
+++ b/src/core/render_widget_host_view_qt.h
@@ -47,6 +47,7 @@
#include "cc/resources/transferable_resource.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/browser/renderer_host/text_input_manager.h"
#include "content/common/view_messages.h"
#include "gpu/ipc/common/gpu_messages.h"
#include "ui/events/gesture_detection/filtered_gesture_provider.h"
@@ -74,6 +75,7 @@ QT_END_NAMESPACE
class WebContentsAdapterClient;
namespace content {
+class RenderFrameHost;
class RenderWidgetHostImpl;
}
@@ -104,6 +106,7 @@ class RenderWidgetHostViewQt
#ifndef QT_NO_ACCESSIBILITY
, public QAccessible::ActivationObserver
#endif // QT_NO_ACCESSIBILITY
+ , public content::TextInputManager::Observer
{
public:
enum LoadVisuallyCommittedState {
@@ -140,7 +143,6 @@ public:
virtual void UnlockMouse() Q_DECL_OVERRIDE;
virtual void UpdateCursor(const content::WebCursor&) Q_DECL_OVERRIDE;
virtual void SetIsLoading(bool) Q_DECL_OVERRIDE;
- virtual void TextInputStateChanged(const content::TextInputState& params) Q_DECL_OVERRIDE;
virtual void ImeCancelComposition() Q_DECL_OVERRIDE;
virtual void ImeCompositionRangeChanged(const gfx::Range&, const std::vector<gfx::Rect>&) Q_DECL_OVERRIDE;
virtual void RenderProcessGone(base::TerminationStatus, int) Q_DECL_OVERRIDE;
@@ -161,9 +163,6 @@ public:
virtual void UnlockCompositingSurface() Q_DECL_OVERRIDE;
virtual void SetNeedsBeginFrames(bool needs_begin_frames) Q_DECL_OVERRIDE;
- // Overridden from RenderWidgetHostViewBase.
- virtual void SelectionChanged(const base::string16 &text, size_t offset, const gfx::Range &range) Q_DECL_OVERRIDE;
-
// Overridden from ui::GestureProviderClient.
virtual void OnGestureEvent(const ui::GestureEventData& gesture) Q_DECL_OVERRIDE;
@@ -177,6 +176,11 @@ public:
virtual bool forwardEvent(QEvent *) Q_DECL_OVERRIDE;
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) Q_DECL_OVERRIDE;
+ // Overridden from content::TextInputManager::Observer
+ virtual void OnUpdateTextInputStateCalled(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view, bool did_update_state) Q_DECL_OVERRIDE;
+ virtual void OnSelectionBoundsChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) Q_DECL_OVERRIDE;
+ virtual void OnTextSelectionChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) Q_DECL_OVERRIDE;
+
// cc::BeginFrameObserverBase implementation.
bool OnBeginFrameDerivedImpl(const cc::BeginFrameArgs& args) override;
void OnBeginFrameSourcePausedChanged(bool paused) override;
@@ -220,6 +224,10 @@ private:
bool IsPopup() const;
+ void selectionChanged();
+ content::RenderFrameHost *getFocusedFrameHost();
+ ui::TextInputType getTextInputType() const;
+
content::RenderWidgetHostImpl *m_host;
ui::FilteredGestureProvider m_gestureProvider;
base::TimeDelta m_eventsToNowDelta;
@@ -239,7 +247,6 @@ private:
WebContentsAdapterClient *m_adapterClient;
MultipleMouseClickHelper m_clickHelper;
- ui::TextInputType m_currentInputType;
bool m_imeInProgress;
bool m_receivedEmptyImeText;
QPoint m_previousMousePosition;
@@ -253,6 +260,11 @@ private:
gfx::Vector2dF m_lastScrollOffset;
gfx::SizeF m_lastContentsSize;
+ uint m_imState;
+ uint m_anchorPositionWithinSelection;
+ uint m_cursorPositionWithinSelection;
+ uint m_cursorPosition;
+ bool m_emptyPreviousSelection;
QString m_surroundingText;
};
diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
index 4d073e94c..b03418aea 100644
--- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
+++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
@@ -120,8 +120,6 @@ private Q_SLOTS:
void testLocalStorageVisibility();
void testEnablePersistentStorage();
void consoleOutput();
- void inputMethods_data();
- void inputMethods();
void errorPageExtension();
void errorPageExtensionLoadFinished();
void userAgentNewlineStripping();
@@ -1631,569 +1629,6 @@ void tst_QWebEnginePage::backActionUpdate()
QVERIFY(action->isEnabled());
}
-void tst_QWebEnginePage::inputMethods_data()
-{
- QTest::addColumn<QString>("viewType");
- QTest::newRow("QWebEngineView") << "QWebEngineView";
- QTest::newRow("QGraphicsWebView") << "QGraphicsWebView";
-}
-
-#if defined(QWEBENGINEPAGE_INPUTMETHODQUERY)
-static void clickOnPage(QWebEnginePage* page, const QPoint& position)
-{
- QMouseEvent evpres(QEvent::MouseButtonPress, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
- page->event(&evpres);
- QMouseEvent evrel(QEvent::MouseButtonRelease, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
- page->event(&evrel);
-}
-#endif
-
-void tst_QWebEnginePage::inputMethods()
-{
-#if !defined(QWEBENGINEPAGE_INPUTMETHODQUERY)
- QSKIP("QWEBENGINEPAGE_INPUTMETHODQUERY");
-#else
- QFETCH(QString, viewType);
- QWebEnginePage* page = new QWebEnginePage;
- QObject* view = 0;
- QScopedPointer<QObject> container(0);
- if (viewType == "QWebEngineView") {
- QWebEngineView* wv = new QWebEngineView;
- wv->setPage(page);
- view = wv;
- container.reset(view);
- } else if (viewType == "QGraphicsWebView") {
- QGraphicsWebView* wv = new QGraphicsWebView;
- wv->setPage(page);
- view = wv;
-
- QGraphicsView* gv = new QGraphicsView;
- QGraphicsScene* scene = new QGraphicsScene(gv);
- gv->setScene(scene);
- scene->addItem(wv);
- wv->setGeometry(QRect(0, 0, 500, 500));
-
- container.reset(gv);
- } else
- QVERIFY2(false, "Unknown view type");
-
- page->settings()->setFontFamily(QWebEngineSettings::SerifFont, page->settings()->fontFamily(QWebEngineSettings::FixedFont));
- page->setHtml("<html><body>" \
- "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/><br>" \
- "<input type='password'/>" \
- "</body></html>");
- page->mainFrame()->setFocus();
-
- TestInputContext testContext;
-
- QWebEngineElementCollection inputs = page->mainFrame()->documentElement().findAll("input");
- QPoint textInputCenter = inputs.at(0).geometry().center();
-
- clickOnPage(page, textInputCenter);
-
- //ImMicroFocus
- QVariant variant = page->inputMethodQuery(Qt::ImMicroFocus);
- QVERIFY(inputs.at(0).geometry().contains(variant.toRect().topLeft()));
-
- // We assigned the serif font famility to be the same as the fixef font family.
- // Then test ImFont on a serif styled element, we should get our fixef font family.
- variant = page->inputMethodQuery(Qt::ImFont);
- QFont font = variant.value<QFont>();
- QCOMPARE(page->settings()->fontFamily(QWebEngineSettings::FixedFont), font.family());
-
- QList<QInputMethodEvent::Attribute> inputAttributes;
-
- //Insert text.
- {
- QInputMethodEvent eventText("QtWebEngine", inputAttributes);
- QSignalSpy signalSpy(page, SIGNAL(microFocusChanged()));
- page->event(&eventText);
- QCOMPARE(signalSpy.count(), 0);
- }
-
- {
- QInputMethodEvent eventText("", inputAttributes);
- eventText.setCommitString(QString("QtWebEngine"), 0, 0);
- page->event(&eventText);
- }
-
- //ImMaximumTextLength
- variant = page->inputMethodQuery(Qt::ImMaximumTextLength);
- QCOMPARE(20, variant.toInt());
-
- //Set selection
- inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 3, 2, QVariant());
- QInputMethodEvent eventSelection("",inputAttributes);
- page->event(&eventSelection);
-
- //ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- int anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 3);
-
- //ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- int cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 5);
-
- //ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- QString selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString("eb"));
-
- //Set selection with negative length
- inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 6, -5, QVariant());
- QInputMethodEvent eventSelection3("",inputAttributes);
- page->event(&eventSelection3);
-
- //ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 1);
-
- //ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 6);
-
- //ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString("tWebK"));
-
- //ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- QString value = variant.value<QString>();
- QCOMPARE(value, QString("QtWebEngine"));
-
- {
- QList<QInputMethodEvent::Attribute> attributes;
- // Clear the selection, so the next test does not clear any contents.
- QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
- attributes.append(newSelection);
- QInputMethodEvent event("composition", attributes);
- page->event(&event);
- }
-
- // A ongoing composition should not change the surrounding text before it is committed.
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- value = variant.value<QString>();
- QCOMPARE(value, QString("QtWebEngine"));
-
- // Cancel current composition first
- inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 0, QVariant());
- QInputMethodEvent eventSelection4("", inputAttributes);
- page->event(&eventSelection4);
-
- // START - Tests for Selection when the Editor is NOT in Composition mode
-
- // LEFT to RIGHT selection
- // Deselect the selection by sending MouseButtonPress events
- // This moves the current cursor to the end of the text
- clickOnPage(page, textInputCenter);
-
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event(QString(), attributes);
- event.setCommitString("XXX", 0, 0);
- page->event(&event);
- event.setCommitString(QString(), -2, 2); // Erase two characters.
- page->event(&event);
- event.setCommitString(QString(), -1, 1); // Erase one character.
- page->event(&event);
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- value = variant.value<QString>();
- QCOMPARE(value, QString("QtWebEngine"));
- }
-
- //Move to the start of the line
- page->triggerAction(QWebEnginePage::MoveToStartOfLine);
-
- QKeyEvent keyRightEventPress(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier);
- QKeyEvent keyRightEventRelease(QEvent::KeyRelease, Qt::Key_Right, Qt::NoModifier);
-
- //Move 2 characters RIGHT
- for (int j = 0; j < 2; ++j) {
- page->event(&keyRightEventPress);
- page->event(&keyRightEventRelease);
- }
-
- //Select to the end of the line
- page->triggerAction(QWebEnginePage::SelectEndOfLine);
-
- //ImAnchorPosition QtWebEngine
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 2);
-
- //ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 8);
-
- //ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString("WebKit"));
-
- //RIGHT to LEFT selection
- //Deselect the selection (this moves the current cursor to the end of the text)
- clickOnPage(page, textInputCenter);
-
- //ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 8);
-
- //ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 8);
-
- //ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- QKeyEvent keyLeftEventPress(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier);
- QKeyEvent keyLeftEventRelease(QEvent::KeyRelease, Qt::Key_Left, Qt::NoModifier);
-
- //Move 2 characters LEFT
- for (int i = 0; i < 2; ++i) {
- page->event(&keyLeftEventPress);
- page->event(&keyLeftEventRelease);
- }
-
- //Select to the start of the line
- page->triggerAction(QWebEnginePage::SelectStartOfLine);
-
- //ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 6);
-
- //ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 0);
-
- //ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString("QtWebK"));
-
- //END - Tests for Selection when the Editor is not in Composition mode
-
- page->setHtml("<html><body>" \
- "<input type='text' id='input4' value='QtWebEngine inputMethod'/>" \
- "</body></html>");
- evaluateJavaScriptSync(page, "var inputEle = document.getElementById('input4'); inputEle.focus(); inputEle.select();");
-
- // Clear the selection, also cancel the ongoing composition if there is one.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
- attributes.append(newSelection);
- QInputMethodEvent event("", attributes);
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- QString surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("QtWebEngine inputMethod"));
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 0);
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 0);
-
- // 1. Insert a character to the beginning of the line.
- // Send temporary text, which makes the editor has composition 'm'.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("m", attributes);
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("QtWebEngine inputMethod"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 0);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 0);
-
- // Send temporary text, which makes the editor has composition 'n'.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("n", attributes);
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("QtWebEngine inputMethod"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 0);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 0);
-
- // Send commit text, which makes the editor conforms composition.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("", attributes);
- event.setCommitString("o");
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oQtWebEngine inputMethod"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 1);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 1);
-
- // 2. insert a character to the middle of the line.
- // Send temporary text, which makes the editor has composition 'd'.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("d", attributes);
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oQtWebEngine inputMethod"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 1);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 1);
-
- // Send commit text, which makes the editor conforms composition.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("", attributes);
- event.setCommitString("e");
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethod"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 2);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 2);
-
- // 3. Insert a character to the end of the line.
- page->triggerAction(QWebEnginePage::MoveToEndOfLine);
-
- // Send temporary text, which makes the editor has composition 't'.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("t", attributes);
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethod"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 22);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 22);
-
- // Send commit text, which makes the editor conforms composition.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("", attributes);
- event.setCommitString("t");
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethodt"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 23);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 23);
-
- // 4. Replace the selection.
- page->triggerAction(QWebEnginePage::SelectPreviousWord);
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString("inputMethodt"));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethodt"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 11);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 23);
-
- // Send temporary text, which makes the editor has composition 'w'.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("w", attributes);
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oeQtWebEngine "));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 11);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 11);
-
- // Send commit text, which makes the editor conforms composition.
- {
- QList<QInputMethodEvent::Attribute> attributes;
- QInputMethodEvent event("", attributes);
- event.setCommitString("2");
- page->event(&event);
- }
-
- // ImCurrentSelection
- variant = page->inputMethodQuery(Qt::ImCurrentSelection);
- selectionValue = variant.value<QString>();
- QCOMPARE(selectionValue, QString(""));
-
- // ImSurroundingText
- variant = page->inputMethodQuery(Qt::ImSurroundingText);
- surroundingValue = variant.value<QString>();
- QCOMPARE(surroundingValue, QString("oeQtWebEngine 2"));
-
- // ImCursorPosition
- variant = page->inputMethodQuery(Qt::ImCursorPosition);
- cursorPosition = variant.toInt();
- QCOMPARE(cursorPosition, 12);
-
- // ImAnchorPosition
- variant = page->inputMethodQuery(Qt::ImAnchorPosition);
- anchorPosition = variant.toInt();
- QCOMPARE(anchorPosition, 12);
-#endif
-}
-
void tst_QWebEnginePage::protectBindingsRuntimeObjectsFromCollector()
{
#if !defined(QWEBENGINEPAGE_CREATEPLUGIN)
diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
index a6138c59d..82e50409d 100644
--- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
+++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
@@ -97,8 +97,11 @@ private Q_SLOTS:
void postData();
void softwareInputPanel();
+ void inputMethods();
+ void textSelection();
void hiddenText();
void emptyInputMethodEvent();
+ void imeComposition();
void newlineInTextarea();
};
@@ -1341,6 +1344,24 @@ static QPoint elementCenter(QWebEnginePage *page, const QString &id)
return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt());
}
+static QRect elementGeometry(QWebEnginePage *page, const QString &id)
+{
+ const QString jsCode(
+ "(function() {"
+ " var elem = document.getElementById('" + id + "');"
+ " var rect = elem.getBoundingClientRect();"
+ " return [rect.left, rect.top, rect.right, rect.bottom];"
+ "})()");
+ QVariantList coords = evaluateJavaScriptSync(page, jsCode).toList();
+
+ if (coords.count() != 4) {
+ qWarning("elementGeometry faield.");
+ return QRect();
+ }
+
+ return QRect(coords[0].toInt(), coords[1].toInt(), coords[2].toInt(), coords[3].toInt());
+}
+
void tst_QWebEngineView::softwareInputPanel()
{
TestInputContext testContext;
@@ -1398,6 +1419,183 @@ void tst_QWebEngineView::softwareInputPanel()
QVERIFY(!testContext.isInputPanelVisible());
}
+void tst_QWebEngineView::inputMethods()
+{
+ QWebEngineView view;
+ view.show();
+
+ QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged()));
+ QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool)));
+ view.settings()->setFontFamily(QWebEngineSettings::SerifFont, view.settings()->fontFamily(QWebEngineSettings::FixedFont));
+ view.setHtml("<html><body>"
+ " <input type='text' id='input1' style='font-family: serif' value='' maxlength='20' size='50'/>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+
+ QPoint textInputCenter = elementCenter(view.page(), "input1");
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter);
+ QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1"));
+
+ // ImCursorRectangle
+ QVariant variant = view.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle);
+ QVERIFY(elementGeometry(view.page(), "input1").contains(variant.toRect().topLeft()));
+
+ // We assigned the serif font family to be the same as the fixed font family.
+ // Then test ImFont on a serif styled element, we should get our fixed font family.
+ variant = view.focusProxy()->inputMethodQuery(Qt::ImFont);
+ QFont font = variant.value<QFont>();
+ QEXPECT_FAIL("", "UNIMPLEMENTED: RenderWidgetHostViewQt::inputMethodQuery(Qt::ImFont)", Continue);
+ QCOMPARE(view.settings()->fontFamily(QWebEngineSettings::FixedFont), font.family());
+
+ QList<QInputMethodEvent::Attribute> inputAttributes;
+
+ // Insert text
+ {
+ QString text = QStringLiteral("QtWebEngine");
+ QInputMethodEvent eventText(text, inputAttributes);
+ QApplication::sendEvent(view.focusProxy(), &eventText);
+ QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), text);
+ QCOMPARE(selectionChangedSpy.count(), 0);
+ }
+
+ {
+ QString text = QStringLiteral("QtWebEngine");
+ QInputMethodEvent eventText("", inputAttributes);
+ eventText.setCommitString(text, 0, 0);
+ QApplication::sendEvent(view.focusProxy(), &eventText);
+ QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), text);
+ QCOMPARE(selectionChangedSpy.count(), 0);
+ }
+
+ // ImMaximumTextLength
+ QEXPECT_FAIL("", "UNIMPLEMENTED: RenderWidgetHostViewQt::inputMethodQuery(Qt::ImMaximumTextLength)", Continue);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImMaximumTextLength).toInt(), 20);
+
+ // Set selection
+ inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 3, 2, QVariant());
+ QInputMethodEvent eventSelection1("", inputAttributes);
+
+ QApplication::sendEvent(view.focusProxy(), &eventSelection1);
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 1);
+
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 3);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 5);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("eb"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine"));
+
+ // Set selection with negative length
+ inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 6, -5, QVariant());
+ QInputMethodEvent eventSelection2("", inputAttributes);
+ QApplication::sendEvent(view.focusProxy(), &eventSelection2);
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 2);
+
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 1);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 6);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("tWebE"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine"));
+
+ QList<QInputMethodEvent::Attribute> attributes;
+ // Clear the selection, so the next test does not clear any contents.
+ QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
+ attributes.append(newSelection);
+ QInputMethodEvent eventComposition("composition", attributes);
+ QApplication::sendEvent(view.focusProxy(), &eventComposition);
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 3);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+
+ // An ongoing composition should not change the surrounding text before it is committed.
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine"));
+
+ // Cancel current composition first
+ inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 0, QVariant());
+ QInputMethodEvent eventSelection3("", inputAttributes);
+ QApplication::sendEvent(view.focusProxy(), &eventSelection3);
+
+ // Cancelling composition should not clear the surrounding text
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine"));
+}
+
+void tst_QWebEngineView::textSelection()
+{
+ QWebEngineView view;
+ view.show();
+
+ QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged()));
+ QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool)));
+ view.setHtml("<html><body>"
+ " <input type='text' id='input1' value='QtWebEngine' size='50'/>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+
+ // Tests for Selection when the Editor is NOT in Composition mode
+
+ // LEFT to RIGHT selection
+ // Mouse click event moves the current cursor to the end of the text
+ QPoint textInputCenter = elementCenter(view.page(), "input1");
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter);
+ QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1"));
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11);
+ // There was no selection to be changed by the click
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event(QString(), attributes);
+ event.setCommitString("XXX", 0, 0);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngineXXX"));
+
+ event.setCommitString(QString(), -2, 2); // Erase two characters.
+ QApplication::sendEvent(view.focusProxy(), &event);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngineX"));
+
+ event.setCommitString(QString(), -1, 1); // Erase one character.
+ QApplication::sendEvent(view.focusProxy(), &event);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine"));
+
+ // Move to the start of the line
+ QTest::keyClick(view.focusProxy(), Qt::Key_Home);
+
+ // Move 2 characters RIGHT
+ for (int j = 0; j < 2; ++j)
+ QTest::keyClick(view.focusProxy(), Qt::Key_Right);
+
+ // Select to the end of the line
+ QTest::keyClick(view.focusProxy(), Qt::Key_End, Qt::ShiftModifier);
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 1);
+
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 2);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("WebEngine"));
+
+ // RIGHT to LEFT selection
+ // Deselect the selection (this moves the current cursor to the end of the text)
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter);
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 2);
+
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+
+ // Move 2 characters LEFT
+ for (int i = 0; i < 2; ++i)
+ QTest::keyClick(view.focusProxy(), Qt::Key_Left);
+
+ // Select to the start of the line
+ QTest::keyClick(view.focusProxy(), Qt::Key_Home, Qt::ShiftModifier);
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 3);
+
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 9);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("QtWebEngi"));
+}
+
void tst_QWebEngineView::hiddenText()
{
QWebEngineView view;
@@ -1451,6 +1649,193 @@ void tst_QWebEngineView::emptyInputMethodEvent()
QCOMPARE(inputValue, QString("QtWebEngine"));
}
+void tst_QWebEngineView::imeComposition()
+{
+ QWebEngineView view;
+ view.show();
+
+ QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged()));
+ QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool)));
+ view.setHtml("<html><body>"
+ " <input type='text' id='input1' value='QtWebEngine inputMethod'/>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+
+ evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();");
+ QTRY_VERIFY(!evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty());
+
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QVERIFY(selectionChangedSpy.wait(100));
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QCOMPARE(selectionChangedSpy.count(), 1);
+
+ // Clear the selection, also cancel the ongoing composition if there is one.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
+ attributes.append(newSelection);
+ QInputMethodEvent event("", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty());
+
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QVERIFY(selectionChangedSpy.wait(100));
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QCOMPARE(selectionChangedSpy.count(), 2);
+ }
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine inputMethod"));
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+
+ selectionChangedSpy.clear();
+
+
+ // 1. Insert a character to the beginning of the line.
+ // Send temporary text, which makes the editor has composition 'm'.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("m", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine inputMethod"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+ // Send temporary text, which makes the editor has composition 'n'.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("n", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine inputMethod"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+ // Send commit text, which makes the editor conforms composition.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ event.setCommitString("o");
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oQtWebEngine inputMethod"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 1);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 1);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+
+ // 2. insert a character to the middle of the line.
+ // Send temporary text, which makes the editor has composition 'd'.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("d", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oQtWebEngine inputMethod"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 1);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 1);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+ // Send commit text, which makes the editor conforms composition.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ event.setCommitString("e");
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine inputMethod"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 2);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 2);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+
+ // 3. Insert a character to the end of the line.
+ QTest::keyClick(view.focusProxy(), Qt::Key_End);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 25);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 25);
+
+ // Send temporary text, which makes the editor has composition 't'.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("t", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine inputMethod"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 25);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 25);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+ // Send commit text, which makes the editor conforms composition.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ event.setCommitString("t");
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine inputMethodt"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 26);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 26);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+ QCOMPARE(selectionChangedSpy.count(), 0);
+
+
+ // 4. Replace the selection.
+#ifndef Q_OS_MACOS
+ QTest::keyClick(view.focusProxy(), Qt::Key_Left, Qt::ShiftModifier | Qt::ControlModifier);
+#else
+ QTest::keyClick(view.focusProxy(), Qt::Key_Left, Qt::ShiftModifier | Qt::AltModifier);
+#endif
+ QVERIFY(selectionChangedSpy.wait());
+ QCOMPARE(selectionChangedSpy.count(), 1);
+
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine inputMethodt"));
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 14);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 26);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("inputMethodt"));
+
+ // Send temporary text, which makes the editor has composition 'w'.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("w", attributes);
+ QApplication::sendEvent(view.focusProxy(), &event);
+ QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty());
+ QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QString("oeQtWebEngine w"));
+
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QVERIFY(selectionChangedSpy.wait(100));
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QCOMPARE(selectionChangedSpy.count(), 2);
+ }
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine "));
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 14);
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 14);
+ QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue);
+ QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+
+ // Send commit text, which makes the editor conforms composition.
+ {
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event("", attributes);
+ event.setCommitString("2");
+ QApplication::sendEvent(view.focusProxy(), &event);
+ }
+ // There is no text selection to be changed at this point thus we can't wait for selectionChanged signal.
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine 2"));
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 15);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 15);
+ QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString(""));
+}
+
void tst_QWebEngineView::newlineInTextarea()
{
QWebEngineView view;