diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index f96329139d52876943ef45e7e5851b07668cfef4..619577d93fa3bcd3642097056c8839c533e3a620 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 ¶ms) -{ - 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 1dae96a537374800dfacf14bca76473d7781b6ea..78b946a605e74aaf0e53abb7be20ff9eb40e1cd4 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 4d073e94c63469912d13c9626cc91a8870404c0b..b03418aeab913dc538e036aebca57e73989cf2b7 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 a6138c59de5d9e92ecd0d7357b03d17d29308d17..82e50409d6f602dbd32648554cda2b5340d8c41e 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;