From 18b86842bf5169cf89e5328c64e5ef67276ece6f Mon Sep 17 00:00:00 2001 From: Peter Varga Date: Tue, 9 Jun 2020 08:41:17 +0200 Subject: Refactor RenderWidgetHostViewQt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make RenderWidgetHostViewQtDelegateClient class instantiable and remove its implementation from RenderWidgetHostViewQt. Change-Id: Idcad3a805defe8b910f418b91f14808b6dbf083b Reviewed-by: Jüri Valdmann --- src/core/core_chromium.pri | 2 + src/core/render_widget_host_view_qt.cpp | 984 +-------------------- src/core/render_widget_host_view_qt.h | 164 +--- src/core/render_widget_host_view_qt_delegate.h | 28 +- .../render_widget_host_view_qt_delegate_client.cpp | 973 ++++++++++++++++++++ .../render_widget_host_view_qt_delegate_client.h | 166 ++++ src/core/type_conversion.cpp | 36 + src/core/type_conversion.h | 3 + src/core/web_contents_adapter.cpp | 1 + src/core/web_contents_view_qt.cpp | 6 +- 10 files changed, 1275 insertions(+), 1088 deletions(-) create mode 100644 src/core/render_widget_host_view_qt_delegate_client.cpp create mode 100644 src/core/render_widget_host_view_qt_delegate_client.h (limited to 'src/core') diff --git a/src/core/core_chromium.pri b/src/core/core_chromium.pri index 4292f56d0..ad766ad77 100644 --- a/src/core/core_chromium.pri +++ b/src/core/core_chromium.pri @@ -108,6 +108,7 @@ SOURCES = \ register_protocol_handler_request_controller_impl.cpp \ render_view_context_menu_qt.cpp \ render_widget_host_view_qt.cpp \ + render_widget_host_view_qt_delegate_client.cpp \ renderer/content_renderer_client_qt.cpp \ renderer/content_settings_observer_qt.cpp \ renderer/render_frame_observer_qt.cpp \ @@ -214,6 +215,7 @@ HEADERS = \ render_view_context_menu_qt.h \ render_widget_host_view_qt.h \ render_widget_host_view_qt_delegate.h \ + render_widget_host_view_qt_delegate_client.h \ renderer/content_renderer_client_qt.h \ renderer/content_settings_observer_qt.h \ renderer/render_frame_observer_qt.h \ diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 95ec20857..27229df81 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -44,9 +44,8 @@ #include "common/qt_messages.h" #include "qtwebenginecoreglobal_p.h" #include "render_widget_host_view_qt_delegate.h" -#include "touch_handle_drawable_client.h" +#include "render_widget_host_view_qt_delegate_client.h" #include "touch_selection_controller_client_qt.h" -#include "touch_selection_menu_controller.h" #include "type_conversion.h" #include "web_contents_adapter.h" #include "web_contents_adapter_client.h" @@ -57,7 +56,6 @@ #include "components/viz/common/frame_sinks/begin_frame_source.h" #include "components/viz/common/surfaces/frame_sink_id_allocator.h" #include "components/viz/host/host_frame_sink_manager.h" -#include "content/browser/compositor/surface_utils.h" #include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/renderer_host/input/synthetic_gesture_target.h" @@ -73,9 +71,7 @@ #include "ui/events/event.h" #include "ui/events/gesture_detection/gesture_configuration.h" #include "ui/events/gesture_detection/gesture_provider_config_helper.h" -#include "ui/events/gesture_detection/motion_event.h" #include "ui/gfx/image/image_skia.h" -#include "ui/touch_selection/touch_selection_controller.h" #if defined(USE_OZONE) #include "ui/base/clipboard/scoped_clipboard_writer.h" @@ -87,23 +83,10 @@ #include "ui/base/resource/resource_bundle.h" #endif -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include #include -#include -#include -#include #include -#include namespace QtWebEngineCore { @@ -126,50 +109,6 @@ static inline ui::LatencyInfo CreateLatencyInfo(const blink::WebInputEvent& even return latency_info; } -static inline Qt::InputMethodHints toQtInputMethodHints(ui::TextInputType inputType) -{ - switch (inputType) { - case ui::TEXT_INPUT_TYPE_TEXT: - return Qt::ImhPreferLowercase; - case ui::TEXT_INPUT_TYPE_SEARCH: - return Qt::ImhPreferLowercase | Qt::ImhNoAutoUppercase; - case ui::TEXT_INPUT_TYPE_PASSWORD: - return Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText; - case ui::TEXT_INPUT_TYPE_EMAIL: - return Qt::ImhEmailCharactersOnly; - case ui::TEXT_INPUT_TYPE_NUMBER: - return Qt::ImhFormattedNumbersOnly; - case ui::TEXT_INPUT_TYPE_TELEPHONE: - return Qt::ImhDialableCharactersOnly; - case ui::TEXT_INPUT_TYPE_URL: - return Qt::ImhUrlCharactersOnly | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase; - case ui::TEXT_INPUT_TYPE_DATE_TIME: - case ui::TEXT_INPUT_TYPE_DATE_TIME_LOCAL: - case ui::TEXT_INPUT_TYPE_DATE_TIME_FIELD: - return Qt::ImhDate | Qt::ImhTime; - case ui::TEXT_INPUT_TYPE_DATE: - case ui::TEXT_INPUT_TYPE_MONTH: - case ui::TEXT_INPUT_TYPE_WEEK: - return Qt::ImhDate; - case ui::TEXT_INPUT_TYPE_TIME: - return Qt::ImhTime; - case ui::TEXT_INPUT_TYPE_TEXT_AREA: - case ui::TEXT_INPUT_TYPE_CONTENT_EDITABLE: - return Qt::ImhMultiLine | Qt::ImhPreferLowercase; - default: - return Qt::ImhNone; - } -} - -static inline int firstAvailableId(const QMap &map) -{ - ui::BitSet32 usedIds; - QMap::const_iterator end = map.end(); - for (QMap::const_iterator it = map.begin(); it != end; ++it) - usedIds.mark_bit(it.value()); - return usedIds.first_unmarked_bit(); -} - static inline ui::GestureProvider::Config QtGestureProviderConfig() { ui::GestureProvider::Config config = ui::GetGestureProviderConfig(ui::GestureProviderConfigType::CURRENT_PLATFORM); // Causes an assert in CreateWebGestureEventFromGestureEventData and we don't need them in Qt. @@ -179,126 +118,16 @@ static inline ui::GestureProvider::Config QtGestureProviderConfig() { return config; } -static inline bool compareTouchPoints(const QTouchEvent::TouchPoint &lhs, const QTouchEvent::TouchPoint &rhs) -{ - // TouchPointPressed < TouchPointMoved < TouchPointReleased - return lhs.state() < rhs.state(); -} - -static inline bool isCommonTextEditShortcut(const QKeyEvent *ke) -{ - return QInputControl::isCommonTextEditShortcut(ke); -} - -static uint32_t s_eventId = 0; -class MotionEventQt : public ui::MotionEvent { -public: - MotionEventQt(const QList &touchPoints, const base::TimeTicks &eventTime, Action action, const Qt::KeyboardModifiers modifiers, int index = -1) - : touchPoints(touchPoints) - , eventTime(eventTime) - , action(action) - , eventId(++s_eventId) - , flags(flagsFromModifiers(modifiers)) - , index(index) - { - // ACTION_DOWN and ACTION_UP must be accesssed through pointer_index 0 - Q_ASSERT((action != Action::DOWN && action != Action::UP) || index == 0); - } - - uint32_t GetUniqueEventId() const override { return eventId; } - Action GetAction() const override { return action; } - int GetActionIndex() const override { return index; } - size_t GetPointerCount() const override { return touchPoints.size(); } - int GetPointerId(size_t pointer_index) const override { return touchPoints.at(pointer_index).id(); } - float GetX(size_t pointer_index) const override { return touchPoints.at(pointer_index).pos().x(); } - float GetY(size_t pointer_index) const override { return touchPoints.at(pointer_index).pos().y(); } - float GetRawX(size_t pointer_index) const override { return touchPoints.at(pointer_index).screenPos().x(); } - float GetRawY(size_t pointer_index) const override { return touchPoints.at(pointer_index).screenPos().y(); } - float GetTouchMajor(size_t pointer_index) const override - { - QSizeF diams = touchPoints.at(pointer_index).ellipseDiameters(); - return std::max(diams.height(), diams.width()); - } - float GetTouchMinor(size_t pointer_index) const override - { - QSizeF diams = touchPoints.at(pointer_index).ellipseDiameters(); - return std::min(diams.height(), diams.width()); - } - float GetOrientation(size_t pointer_index) const override - { - return 0; - } - int GetFlags() const override { return flags; } - float GetPressure(size_t pointer_index) const override { return touchPoints.at(pointer_index).pressure(); } - float GetTiltX(size_t pointer_index) const override { return 0; } - float GetTiltY(size_t pointer_index) const override { return 0; } - float GetTwist(size_t) const override { return 0; } - float GetTangentialPressure(size_t) const override { return 0; } - base::TimeTicks GetEventTime() const override { return eventTime; } - - size_t GetHistorySize() const override { return 0; } - base::TimeTicks GetHistoricalEventTime(size_t historical_index) const override { return base::TimeTicks(); } - float GetHistoricalTouchMajor(size_t pointer_index, size_t historical_index) const override { return 0; } - float GetHistoricalX(size_t pointer_index, size_t historical_index) const override { return 0; } - float GetHistoricalY(size_t pointer_index, size_t historical_index) const override { return 0; } - ToolType GetToolType(size_t pointer_index) const override { - return (touchPoints.at(pointer_index).flags() & QTouchEvent::TouchPoint::InfoFlag::Pen) ? ui::MotionEvent::ToolType::STYLUS - : ui::MotionEvent::ToolType::FINGER; - } - int GetButtonState() const override { return 0; } - -private: - QList touchPoints; - base::TimeTicks eventTime; - Action action; - const uint32_t eventId; - int flags; - int index; -}; - -static content::ScreenInfo screenInfoFromQScreen(QScreen *screen) -{ - content::ScreenInfo r; - if (screen) { - r.device_scale_factor = screen->devicePixelRatio(); - r.depth_per_component = 8; - r.depth = screen->depth(); - r.is_monochrome = (r.depth == 1); - r.rect = toGfx(screen->geometry()); - r.available_rect = toGfx(screen->availableGeometry()); - } else { - r.device_scale_factor = qGuiApp->devicePixelRatio(); - } - return r; -} - RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget) : content::RenderWidgetHostViewBase::RenderWidgetHostViewBase(widget) , m_taskRunner(base::ThreadTaskRunnerHandle::Get()) , m_gestureProvider(QtGestureProviderConfig(), this) - , m_sendMotionActionDown(false) - , m_touchMotionStarted(false) - , m_visible(false) - , m_loadVisuallyCommittedState(NotCommitted) - , m_adapterClient(0) - , m_imeInProgress(false) - , m_receivedEmptyImeEvent(false) - , m_isMouseLocked(false) - , m_imState(0) - , m_anchorPositionWithinSelection(-1) - , m_cursorPositionWithinSelection(-1) - , m_cursorPosition(0) - , m_emptyPreviousSelection(true) - , m_wheelAckPending(false) - , m_mouseWheelPhaseHandler(this) , m_frameSinkId(host()->GetFrameSinkId()) + , m_delegateClient(new RenderWidgetHostViewQtDelegateClient(this)) { if (GetTextInputManager()) GetTextInputManager()->AddObserver(this); - const QPlatformInputContext *context = QGuiApplicationPrivate::platformIntegration()->inputContext(); - m_imeHasHiddenTextCapability = context && context->hasCapability(QPlatformInputContext::HiddenTextCapability); - m_rootLayer.reset(new ui::Layer(ui::LAYER_SOLID_COLOR)); m_rootLayer->SetColor(SK_ColorTRANSPARENT); @@ -359,7 +188,7 @@ void RenderWidgetHostViewQt::setDelegate(RenderWidgetHostViewQtDelegate* delegat m_deferredShow = false; Show(); } - visualPropertiesChanged(); + delegateClient()->visualPropertiesChanged(); } void RenderWidgetHostViewQt::setAdapterClient(WebContentsAdapterClient *adapterClient) @@ -393,7 +222,7 @@ void RenderWidgetHostViewQt::SetSize(const gfx::Size &sizeInDips) void RenderWidgetHostViewQt::SetBounds(const gfx::Rect &windowRectInDips) { - DCHECK(IsPopup()); + DCHECK(isPopup()); m_delegate->move(toQt(windowRectInDips.origin())); m_delegate->resize(windowRectInDips.width(), windowRectInDips.height()); } @@ -430,7 +259,7 @@ content::BrowserAccessibilityManager* RenderWidgetHostViewQt::CreateBrowserAcces // Set focus to the associated View component. void RenderWidgetHostViewQt::Focus() { - if (!IsPopup()) + if (!isPopup()) m_delegate->setKeyboardFocus(); host()->Focus(); } @@ -480,7 +309,7 @@ bool RenderWidgetHostViewQt::IsShowing() // Retrieve the bounds of the View, in screen coordinates. gfx::Rect RenderWidgetHostViewQt::GetViewBounds() { - return m_viewRectInDips; + return toGfx(delegateClient()->viewRectInDips()); } void RenderWidgetHostViewQt::UpdateBackgroundColor() @@ -502,7 +331,7 @@ void RenderWidgetHostViewQt::UpdateBackgroundColor() // Return value indicates whether the mouse is locked successfully or not. bool RenderWidgetHostViewQt::LockMouse(bool) { - m_previousMousePosition = QCursor::pos(); + delegateClient()->resetPreviousMousePosition(); m_delegate->lockMouse(); m_isMouseLocked = true; qApp->setOverrideCursor(Qt::BlankCursor); @@ -736,7 +565,7 @@ void RenderWidgetHostViewQt::GetScreenInfo(content::ScreenInfo *results) gfx::Rect RenderWidgetHostViewQt::GetBoundsInRootWindow() { - return m_windowRectInDips; + return toGfx(delegateClient()->windowRectInDips()); } void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view, bool did_update_state) @@ -758,16 +587,18 @@ void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputMana #else m_delegate->setInputMethodHints(toQtInputMethodHints(getTextInputType()) | Qt::ImhNoPredictiveText); #endif - m_surroundingText = toQt(state->value); + QString surroundingText = toQt(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); + surroundingText.remove(state->composition_start, + state->composition_end - state->composition_start); + delegateClient()->setSurroundingText(surroundingText); // In case of text selection, the update is expected in RenderWidgetHostViewQt::selectionChanged(). if (GetSelectedText().empty()) { // At this point it is unknown whether the text input state has been updated due to a text selection. // Keep the cursor position updated for cursor movements too. - m_cursorPosition = state->selection_start; + delegateClient()->setCursorPosition(state->selection_start); m_delegate->inputMethodStateChanged(type != ui::TEXT_INPUT_TYPE_NONE, type == ui::TEXT_INPUT_TYPE_PASSWORD); } @@ -777,14 +608,14 @@ void RenderWidgetHostViewQt::OnUpdateTextInputStateCalled(content::TextInputMana } // Ignore selection change triggered by ime composition unless it clears an actual text selection - if (state->composition_start != -1 && m_emptyPreviousSelection) { + if (state->composition_start != -1 && delegateClient()->isPreviousSelectionEmpty()) { m_imState = 0; return; } m_imState |= ImStateFlags::TextInputStateUpdated; if (m_imState == ImStateFlags::AllFlags) - selectionChanged(); + delegateClient()->selectionChanged(); } void RenderWidgetHostViewQt::OnSelectionBoundsChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) @@ -795,7 +626,7 @@ void RenderWidgetHostViewQt::OnSelectionBoundsChanged(content::TextInputManager m_imState |= ImStateFlags::TextSelectionBoundsUpdated; if (m_imState == ImStateFlags::AllFlags || (m_imState == ImStateFlags::TextSelectionFlags && getTextInputType() == ui::TEXT_INPUT_TYPE_NONE)) { - selectionChanged(); + delegateClient()->selectionChanged(); } } @@ -819,80 +650,8 @@ void RenderWidgetHostViewQt::OnTextSelectionChanged(content::TextInputManager *t m_imState |= ImStateFlags::TextSelectionUpdated; if (m_imState == ImStateFlags::AllFlags || (m_imState == ImStateFlags::TextSelectionFlags && getTextInputType() == ui::TEXT_INPUT_TYPE_NONE)) { - selectionChanged(); - } -} - -void RenderWidgetHostViewQt::selectionChanged() -{ - // Reset input manager state - m_imState = 0; - ui::TextInputType type = getTextInputType(); - - // Handle text selection out of an input field - if (type == ui::TEXT_INPUT_TYPE_NONE) { - if (GetSelectedText().empty() && m_emptyPreviousSelection) - return; - - // Reset position values to emit selectionChanged signal when clearing text selection - // by clicking into an input field. These values are intended to be used by inputMethodQuery - // so they are not expected to be valid when selection is out of an input field. - m_anchorPositionWithinSelection = -1; - m_cursorPositionWithinSelection = -1; - - m_emptyPreviousSelection = GetSelectedText().empty(); - m_adapterClient->selectionChanged(); - return; + delegateClient()->selectionChanged(); } - - if (GetSelectedText().empty()) { - // RenderWidgetHostViewQt::OnUpdateTextInputStateCalled() does not update the cursor position - // if the selection is cleared because TextInputState changes before the TextSelection change. - Q_ASSERT(text_input_manager_->GetTextInputState()); - m_cursorPosition = text_input_manager_->GetTextInputState()->selection_start; - m_delegate->inputMethodStateChanged(true /*editorVisible*/, type == ui::TEXT_INPUT_TYPE_PASSWORD); - - m_anchorPositionWithinSelection = m_cursorPosition; - m_cursorPositionWithinSelection = m_cursorPosition; - - if (!m_emptyPreviousSelection) { - m_emptyPreviousSelection = true; - m_adapterClient->selectionChanged(); - } - - return; - } - - const content::TextInputManager::TextSelection *selection = text_input_manager_->GetTextSelection(); - if (!selection) - return; - - if (!selection->range().IsValid()) - return; - - int newAnchorPositionWithinSelection = 0; - int 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 (!selection->selected_text().empty()) - m_cursorPosition = newCursorPositionWithinSelection; - - m_emptyPreviousSelection = selection->selected_text().empty(); - m_delegate->inputMethodStateChanged(true /*editorVisible*/, type == ui::TEXT_INPUT_TYPE_PASSWORD); - m_adapterClient->selectionChanged(); } void RenderWidgetHostViewQt::OnGestureEvent(const ui::GestureEventData& gesture) @@ -993,8 +752,7 @@ void RenderWidgetHostViewQt::notifyShown() m_delegatedFrameHost->AttachToCompositor(m_uiCompositor.get()); m_delegatedFrameHost->WasShown(GetLocalSurfaceIdAllocation().local_surface_id(), - m_viewRectInDips.size(), - base::nullopt); + toGfx(delegateClient()->viewRectInDips().size()), base::nullopt); } void RenderWidgetHostViewQt::notifyHidden() @@ -1007,202 +765,6 @@ void RenderWidgetHostViewQt::notifyHidden() m_delegatedFrameHost->DetachFromCompositor(); } -void RenderWidgetHostViewQt::visualPropertiesChanged() -{ - if (!m_delegate) - return; - - gfx::Rect oldViewRect = m_viewRectInDips; - m_viewRectInDips = toGfx(m_delegate->viewGeometry().toAlignedRect()); - - gfx::Rect oldWindowRect = m_windowRectInDips; - m_windowRectInDips = toGfx(m_delegate->windowGeometry()); - - QWindow *window = m_delegate->window(); - content::ScreenInfo oldScreenInfo = m_screenInfo; - m_screenInfo = screenInfoFromQScreen(window ? window->screen() : nullptr); - - if (m_viewRectInDips != oldViewRect || m_windowRectInDips != oldWindowRect) - host()->SendScreenRects(); - - if (m_viewRectInDips.size() != oldViewRect.size() || m_screenInfo != oldScreenInfo) - synchronizeVisualProperties(base::nullopt); -} - -bool RenderWidgetHostViewQt::forwardEvent(QEvent *event) -{ - Q_ASSERT(host()->GetView()); - - switch (event->type()) { - case QEvent::ShortcutOverride: { - QKeyEvent *keyEvent = static_cast(event); - - auto acceptKeyOutOfInputField = [](QKeyEvent *keyEvent) -> bool { -#ifdef Q_OS_MACOS - // Check if a shortcut is registered for this key sequence. - QKeySequence sequence = QKeySequence ( - (keyEvent->modifiers() | keyEvent->key()) & - ~(Qt::KeypadModifier | Qt::GroupSwitchModifier)); - if (QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(sequence)) - return false; - - // The following shortcuts are handled out of input field too but - // disabled on macOS to let the blinking menu handling to the - // embedder application (see kKeyboardCodeKeyDownEntries in - // third_party/WebKit/Source/core/editing/EditingBehavior.cpp). - // Let them pass on macOS to generate the corresponding edit command. - return keyEvent->matches(QKeySequence::Copy) - || keyEvent->matches(QKeySequence::Paste) - || keyEvent->matches(QKeySequence::Cut) - || keyEvent->matches(QKeySequence::SelectAll); -#else - return false; -#endif - }; - - if (!inputMethodQuery(Qt::ImEnabled).toBool() && !(inputMethodQuery(Qt::ImHints).toInt() & Qt::ImhHiddenText) && !acceptKeyOutOfInputField(keyEvent)) - return false; - - Q_ASSERT(m_editCommand.empty()); - if (WebEventFactory::getEditCommand(keyEvent, &m_editCommand) - || isCommonTextEditShortcut(keyEvent)) { - event->accept(); - return true; - } - - return false; - } - case QEvent::MouseButtonPress: - Focus(); - Q_FALLTHROUGH(); - case QEvent::MouseButtonRelease: - case QEvent::MouseMove: - // Skip second MouseMove event when a window is being adopted, so that Chromium - // can properly handle further move events. - // Also make sure the adapter client exists to prevent a null pointer dereference, - // because it's possible for a QWebEnginePagePrivate (adapter) instance to be destroyed, - // and then the OS (observed on Windows) might still send mouse move events to a still - // existing popup RWHVQDW instance. - if (m_adapterClient && m_adapterClient->isBeingAdopted()) - return false; - handleMouseEvent(static_cast(event)); - break; - case QEvent::KeyPress: - case QEvent::KeyRelease: - handleKeyEvent(static_cast(event)); - break; - case QEvent::Wheel: - handleWheelEvent(static_cast(event)); - break; - case QEvent::TouchBegin: - Focus(); - Q_FALLTHROUGH(); - case QEvent::TouchUpdate: - case QEvent::TouchEnd: - case QEvent::TouchCancel: - handleTouchEvent(static_cast(event)); - break; -#if QT_CONFIG(tabletevent) - case QEvent::TabletPress: - Focus(); - Q_FALLTHROUGH(); - case QEvent::TabletRelease: - case QEvent::TabletMove: - handleTabletEvent(static_cast(event)); - break; -#endif -#ifndef QT_NO_GESTURES - case QEvent::NativeGesture: - handleGestureEvent(static_cast(event)); - break; -#endif // QT_NO_GESTURES - case QEvent::HoverMove: - handleHoverEvent(static_cast(event)); - break; - case QEvent::FocusIn: - case QEvent::FocusOut: - handleFocusEvent(static_cast(event)); - break; - case QEvent::InputMethod: - handleInputMethodEvent(static_cast(event)); - break; - case QEvent::InputMethodQuery: - handleInputMethodQueryEvent(static_cast(event)); - break; - case QEvent::Leave: -#ifdef Q_OS_WIN - if (m_mouseButtonPressed > 0) - return false; -#endif - case QEvent::HoverLeave: - host()->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(event)); - break; - default: - return false; - } - return true; -} - -QVariant RenderWidgetHostViewQt::inputMethodQuery(Qt::InputMethodQuery query) -{ - switch (query) { - case Qt::ImEnabled: { - ui::TextInputType type = getTextInputType(); - bool editorVisible = type != ui::TEXT_INPUT_TYPE_NONE; - // IME manager should disable composition on input fields with ImhHiddenText hint if supported - if (m_imeHasHiddenTextCapability) - return QVariant(editorVisible); - - bool passwordInput = type == ui::TEXT_INPUT_TYPE_PASSWORD; - return QVariant(editorVisible && !passwordInput); - } - case Qt::ImFont: - // TODO: Implement this - return QVariant(); - case Qt::ImCursorRectangle: { - if (text_input_manager_) { - if (auto *region = text_input_manager_->GetSelectionRegion()) { - if (region->focus.GetHeight() > 0) { - gfx::Rect caretRect = gfx::RectBetweenSelectionBounds(region->anchor, region->focus); - if (caretRect.width() == 0) - caretRect.set_width(1); // IME API on Windows expects a width > 0 - return toQt(caretRect); - } - } - } - return QVariant(); - } - case Qt::ImCursorPosition: - return m_cursorPosition; - case Qt::ImAnchorPosition: - return GetSelectedText().empty() ? m_cursorPosition : m_anchorPositionWithinSelection; - case Qt::ImSurroundingText: - return m_surroundingText; - case Qt::ImCurrentSelection: - return toQt(GetSelectedText()); - case Qt::ImMaximumTextLength: - // TODO: Implement this - return QVariant(); // No limit. - case Qt::ImHints: -#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) - return int(toQtInputMethodHints(getTextInputType()) | Qt::ImhNoPredictiveText | Qt::ImhNoTextHandles | Qt::ImhNoEditMenu); -#else - return int(toQtInputMethodHints(getTextInputType()) | Qt::ImhNoPredictiveText); -#endif - default: - return QVariant(); - } -} - -void RenderWidgetHostViewQt::closePopup() -{ - // We notify the popup to be closed by telling it that it lost focus. WebKit does the rest - // (hiding the widget and automatic memory cleanup via - // RenderWidget::CloseWidgetSoon() -> RenderWidgetHostImpl::ShutdownAndDestroyWidget(true). - host()->SetActive(false); - host()->LostFocus(); -} - void RenderWidgetHostViewQt::ProcessAckedTouchEvent(const content::TouchEventWithLatencyInfo &touch, content::InputEventAckState ack_result) { Q_UNUSED(touch); const bool eventConsumed = ack_result == content::INPUT_EVENT_ACK_STATE_CONSUMED; @@ -1221,277 +783,46 @@ void RenderWidgetHostViewQt::processMotionEvent(const ui::MotionEvent &motionEve host()->ForwardTouchEventWithLatencyInfo(touchEvent, CreateLatencyInfo(touchEvent)); } -QList RenderWidgetHostViewQt::mapTouchPointIds(const QList &inputPoints) -{ - QList outputPoints = inputPoints; - for (int i = 0; i < outputPoints.size(); ++i) { - QTouchEvent::TouchPoint &point = outputPoints[i]; - - int qtId = point.id(); - QMap::const_iterator it = m_touchIdMapping.find(qtId); - if (it == m_touchIdMapping.end()) - it = m_touchIdMapping.insert(qtId, firstAvailableId(m_touchIdMapping)); - point.setId(it.value()); - - if (point.state() == Qt::TouchPointReleased) - m_touchIdMapping.remove(qtId); - } - - return outputPoints; -} - -bool RenderWidgetHostViewQt::IsPopup() const +bool RenderWidgetHostViewQt::isPopup() const { return widget_type_ == content::WidgetType::kPopup; } -void RenderWidgetHostViewQt::handleMouseEvent(QMouseEvent* event) -{ - if (event->type() == QEvent::MouseButtonPress) - m_mouseButtonPressed++; - if (event->type() == QEvent::MouseButtonRelease) - m_mouseButtonPressed--; - - // Don't forward mouse events synthesized by the system, which are caused by genuine touch - // events. Chromium would then process for e.g. a mouse click handler twice, once due to the - // system synthesized mouse event, and another time due to a touch-to-gesture-to-mouse - // transformation done by Chromium. - if (event->source() == Qt::MouseEventSynthesizedBySystem) - return; - handlePointerEvent(event); -} - -void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) -{ - if (IsMouseLocked() && ev->key() == Qt::Key_Escape && ev->type() == QEvent::KeyRelease) - UnlockMouse(); - - if (m_receivedEmptyImeEvent) { - // IME composition was not finished with a valid commit string. - // We're getting the composition result in a key event. - if (ev->key() != 0) { - // The key event is not a result of an IME composition. Cancel IME. - host()->ImeCancelComposition(); - m_receivedEmptyImeEvent = false; - } else { - if (ev->type() == QEvent::KeyRelease) { - host()->ImeCommitText(toString16(ev->text()), - std::vector(), - gfx::Range::InvalidRange(), - 0); - m_receivedEmptyImeEvent = false; - m_imeInProgress = false; - } - return; - } - } - - // Ignore autorepeating KeyRelease events so that the generated web events - // conform to the spec, which requires autorepeat to result in a sequence of - // keypress events and only one final keyup event: - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Auto-repeat_handling - // https://w3c.github.io/uievents/#dom-keyboardevent-repeat - if (ev->type() == QEvent::KeyRelease && ev->isAutoRepeat()) - return; - - content::NativeWebKeyboardEvent webEvent = WebEventFactory::toWebKeyboardEvent(ev); - if (webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && !m_editCommand.empty()) { - ui::LatencyInfo latency; - latency.set_source_event_type(ui::SourceEventType::KEY_PRESS); - content::EditCommands commands; - commands.emplace_back(m_editCommand, ""); - m_editCommand.clear(); - host()->ForwardKeyboardEventWithCommands(webEvent, latency, &commands, nullptr); - return; - } - - bool keyDownTextInsertion = webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && webEvent.text[0]; - webEvent.skip_in_browser = keyDownTextInsertion; - host()->ForwardKeyboardEvent(webEvent); - - if (keyDownTextInsertion) { - // Blink won't consume the RawKeyDown, but rather the Char event in this case. - // The RawKeyDown is skipped on the way back (see above). - // The same os_event will be set on both NativeWebKeyboardEvents. - webEvent.skip_in_browser = false; - webEvent.SetType(blink::WebInputEvent::kChar); - host()->ForwardKeyboardEvent(webEvent); - } -} - -void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) +bool RenderWidgetHostViewQt::updateScreenInfo() { - // Reset input manager state - m_imState = 0; - - if (!host()) - return; - - QString commitString = ev->commitString(); - QString preeditString = ev->preeditString(); - - int cursorPositionInPreeditString = -1; - gfx::Range selectionRange = gfx::Range::InvalidRange(); - - const QList &attributes = ev->attributes(); - std::vector underlines; - bool hasSelection = false; - - for (const auto &attribute : attributes) { - switch (attribute.type) { - case QInputMethodEvent::TextFormat: { - if (preeditString.isEmpty()) - break; - - int start = qMin(attribute.start, (attribute.start + attribute.length)); - int end = qMax(attribute.start, (attribute.start + attribute.length)); - - // Blink does not support negative position values. Adjust start and end positions - // to non-negative values. - if (start < 0) { - start = 0; - end = qMax(0, start + end); - } - - underlines.push_back(ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, start, end, ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT)); - - QTextCharFormat format = qvariant_cast(attribute.value).toCharFormat(); - if (format.underlineStyle() != QTextCharFormat::NoUnderline) - underlines.back().underline_color = toSk(format.underlineColor()); - - break; - } - case QInputMethodEvent::Cursor: - // Always set the position of the cursor, even if it's marked invisible by Qt, otherwise - // there is no way the user will know which part of the composition string will be - // changed, when performing an IME-specific action (like selecting a different word - // suggestion). - 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; - default: - break; - } - } - - 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::mojom::FrameInputHandler *frameInputHandler = getFrameInputHandler(); - if (frameInputHandler) - frameInputHandler->SetEditableSelectionOffsets(selectionRange.start(), selectionRange.end()); - } - - int replacementLength = ev->replacementLength(); - gfx::Range replacementRange = gfx::Range::InvalidRange(); - - if (replacementLength > 0) - { - int replacementStart = ev->replacementStart() < 0 ? m_cursorPosition + ev->replacementStart() : ev->replacementStart(); - if (replacementStart >= 0 && replacementStart < m_surroundingText.length()) - replacementRange = gfx::Range(replacementStart, replacementStart + replacementLength); - } - - // There are so-far two known cases, when an empty QInputMethodEvent is received. - // First one happens when backspace is used to remove the last character in the pre-edit - // string, thus signaling the end of the composition. - // The second one happens (on Windows) when a Korean char gets composed, but instead of - // the event having a commit string, both strings are empty, and the actual char is received - // as a QKeyEvent after the QInputMethodEvent is processed. - // In lieu of the second case, we can't simply cancel the composition on an empty event, - // and then add the Korean char when QKeyEvent is received, because that leads to text - // flickering in the textarea (or any other element). - // Instead we postpone the processing of the empty QInputMethodEvent by posting it - // to the same focused object, and cancelling the composition on the next event loop tick. - if (commitString.isEmpty() && preeditString.isEmpty() && replacementLength == 0) { - if (!m_receivedEmptyImeEvent && m_imeInProgress && !hasSelection) { - m_receivedEmptyImeEvent = true; - QInputMethodEvent *eventCopy = new QInputMethodEvent(*ev); - QGuiApplication::postEvent(qApp->focusObject(), eventCopy); - } else { - m_receivedEmptyImeEvent = false; - if (m_imeInProgress) { - m_imeInProgress = false; - host()->ImeCancelComposition(); - } - } - - return; - } - - m_receivedEmptyImeEvent = false; - - // Finish compostion: insert or erase text. - if (!commitString.isEmpty() || replacementLength > 0) { - host()->ImeCommitText(toString16(commitString), - underlines, - replacementRange, - 0); - m_imeInProgress = false; - } + content::ScreenInfo oldScreenInfo = m_screenInfo; + QScreen *screen = m_delegate->window() ? m_delegate->window()->screen() : nullptr; - // Update or start new composition. - // Be aware of that, we might get a commit string and a pre-edit string in a single event and - // this means a new composition. - if (!preeditString.isEmpty()) { - host()->ImeSetComposition(toString16(preeditString), - underlines, - replacementRange, - selectionRange.start(), - selectionRange.end()); - m_imeInProgress = true; + if (screen) { + m_screenInfo.device_scale_factor = screen->devicePixelRatio(); + m_screenInfo.depth_per_component = 8; + m_screenInfo.depth = screen->depth(); + m_screenInfo.is_monochrome = (m_screenInfo.depth == 1); + m_screenInfo.rect = toGfx(screen->geometry()); + m_screenInfo.available_rect = toGfx(screen->availableGeometry()); + } else { + m_screenInfo.device_scale_factor = qGuiApp->devicePixelRatio(); } -} -void RenderWidgetHostViewQt::handleInputMethodQueryEvent(QInputMethodQueryEvent *ev) -{ - Qt::InputMethodQueries queries = ev->queries(); - for (uint i = 0; i < 32; ++i) { - Qt::InputMethodQuery query = (Qt::InputMethodQuery)(int)(queries & (1<setValue(query, v); - } - } - ev->accept(); + return (m_screenInfo != oldScreenInfo); } -void RenderWidgetHostViewQt::handleWheelEvent(QWheelEvent *ev) +void RenderWidgetHostViewQt::handleWheelEvent(QWheelEvent *event) { if (!m_wheelAckPending) { Q_ASSERT(m_pendingWheelEvents.isEmpty()); - blink::WebMouseWheelEvent webEvent = WebEventFactory::toWebWheelEvent(ev); + blink::WebMouseWheelEvent webEvent = WebEventFactory::toWebWheelEvent(event); m_wheelAckPending = (webEvent.phase != blink::WebMouseWheelEvent::kPhaseEnded); - m_mouseWheelPhaseHandler.AddPhaseIfNeededAndScheduleEndEvent(webEvent, false); + GetMouseWheelPhaseHandler()->AddPhaseIfNeededAndScheduleEndEvent(webEvent, false); host()->ForwardWheelEvent(webEvent); return; } if (!m_pendingWheelEvents.isEmpty()) { // Try to combine with this wheel event with the last pending one. - if (WebEventFactory::coalesceWebWheelEvent(m_pendingWheelEvents.last(), ev)) + if (WebEventFactory::coalesceWebWheelEvent(m_pendingWheelEvents.last(), event)) return; } - m_pendingWheelEvents.append(WebEventFactory::toWebWheelEvent(ev)); + m_pendingWheelEvents.append(WebEventFactory::toWebWheelEvent(event)); } void RenderWidgetHostViewQt::WheelEventAck(const blink::WebMouseWheelEvent &event, content::InputEventAckState /*ack_result*/) @@ -1528,247 +859,6 @@ content::MouseWheelPhaseHandler *RenderWidgetHostViewQt::GetMouseWheelPhaseHandl return &m_mouseWheelPhaseHandler; } -void RenderWidgetHostViewQt::clearPreviousTouchMotionState() -{ - m_previousTouchPoints.clear(); - m_touchMotionStarted = false; -} - -#ifndef QT_NO_GESTURES -void RenderWidgetHostViewQt::handleGestureEvent(QNativeGestureEvent *ev) -{ - const Qt::NativeGestureType type = ev->gestureType(); - // These are the only supported gestures by Chromium so far. - if (type == Qt::ZoomNativeGesture || type == Qt::SmartZoomNativeGesture) { - host()->ForwardGestureEvent(WebEventFactory::toWebGestureEvent(ev)); - } -} -#endif - -void RenderWidgetHostViewQt::handleTouchEvent(QTouchEvent *ev) -{ - // On macOS instead of handling touch events, we use the OS provided QNativeGestureEvents. -#ifdef Q_OS_MACOS - if (ev->spontaneous()) { - return; - } else { - VLOG(1) - << "Sending simulated touch events to Chromium does not work properly on macOS. " - "Consider using QNativeGestureEvents or QMouseEvents."; - } -#endif - - // Chromium expects the touch event timestamps to be comparable to base::TimeTicks::Now(). - // Most importantly we also have to preserve the relative time distance between events. - // Calculate a delta between event timestamps and Now() on the first received event, and - // apply this delta to all successive events. This delta is most likely smaller than it - // should by calculating it here but this will hopefully cause less than one frame of delay. - base::TimeTicks eventTimestamp = base::TimeTicks() + base::TimeDelta::FromMilliseconds(ev->timestamp()); - if (m_eventsToNowDelta == base::TimeDelta()) - m_eventsToNowDelta = base::TimeTicks::Now() - eventTimestamp; - eventTimestamp += m_eventsToNowDelta; - - QList touchPoints = mapTouchPointIds(ev->touchPoints()); - // Make sure that ACTION_POINTER_DOWN is delivered before ACTION_MOVE, - // and ACTION_MOVE before ACTION_POINTER_UP. - std::sort(touchPoints.begin(), touchPoints.end(), compareTouchPoints); - - // Check first if the touch event should be routed to the selectionController - if (!touchPoints.isEmpty()) { - ui::MotionEvent::Action action; - switch (touchPoints[0].state()) { - case Qt::TouchPointPressed: - action = ui::MotionEvent::Action::DOWN; - break; - case Qt::TouchPointMoved: - action = ui::MotionEvent::Action::MOVE; - break; - case Qt::TouchPointReleased: - action = ui::MotionEvent::Action::UP; - break; - default: - action = ui::MotionEvent::Action::NONE; - break; - } - - MotionEventQt motionEvent(touchPoints, eventTimestamp, action, ev->modifiers(), 0); - if (m_touchSelectionController->WillHandleTouchEvent(motionEvent)) { - m_previousTouchPoints = touchPoints; - ev->accept(); - return; - } - } else { - // An empty touchPoints always corresponds to a TouchCancel event. - // We can't forward touch cancellations without a previously processed touch event, - // as Chromium expects the previous touchPoints for Action::CANCEL. - // If both are empty that means the TouchCancel was sent without an ongoing touch, - // so there's nothing to cancel anyway. - touchPoints = m_previousTouchPoints; - if (touchPoints.isEmpty()) - return; - - MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL, ev->modifiers()); - if (m_touchSelectionController->WillHandleTouchEvent(cancelEvent)) { - m_previousTouchPoints.clear(); - ev->accept(); - return; - } - } - - switch (ev->type()) { - case QEvent::TouchBegin: - m_sendMotionActionDown = true; - m_touchMotionStarted = true; - m_touchSelectionControllerClient->onTouchDown(); - break; - case QEvent::TouchUpdate: - m_touchMotionStarted = true; - break; - case QEvent::TouchCancel: - { - // Only process TouchCancel events received following a TouchBegin or TouchUpdate event - if (m_touchMotionStarted) { - MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL, ev->modifiers()); - processMotionEvent(cancelEvent); - } - - clearPreviousTouchMotionState(); - return; - } - case QEvent::TouchEnd: - clearPreviousTouchMotionState(); - m_touchSelectionControllerClient->onTouchUp(); - break; - default: - break; - } - - if (m_imeInProgress && ev->type() == QEvent::TouchBegin) { - m_imeInProgress = false; - // Tell input method to commit the pre-edit string entered so far, and finish the - // composition operation. -#ifdef Q_OS_WIN - // Yes the function name is counter-intuitive, but commit isn't actually implemented - // by the Windows QPA, and reset does exactly what is necessary in this case. - qApp->inputMethod()->reset(); -#else - qApp->inputMethod()->commit(); -#endif - } - - for (int i = 0; i < touchPoints.size(); ++i) { - ui::MotionEvent::Action action; - switch (touchPoints[i].state()) { - case Qt::TouchPointPressed: - if (m_sendMotionActionDown) { - action = ui::MotionEvent::Action::DOWN; - m_sendMotionActionDown = false; - } else { - action = ui::MotionEvent::Action::POINTER_DOWN; - } - break; - case Qt::TouchPointMoved: - action = ui::MotionEvent::Action::MOVE; - break; - case Qt::TouchPointReleased: - action = touchPoints.size() > 1 ? ui::MotionEvent::Action::POINTER_UP : - ui::MotionEvent::Action::UP; - break; - default: - // Ignore Qt::TouchPointStationary - continue; - } - - MotionEventQt motionEvent(touchPoints, eventTimestamp, action, ev->modifiers(), i); - processMotionEvent(motionEvent); - } - - m_previousTouchPoints = touchPoints; -} - -#if QT_CONFIG(tabletevent) -void RenderWidgetHostViewQt::handleTabletEvent(QTabletEvent *event) -{ - handlePointerEvent(event); -} -#endif - -template -void RenderWidgetHostViewQt::handlePointerEvent(T *event) -{ - // Currently WebMouseEvent is a subclass of WebPointerProperties, so basically - // tablet events are mouse events with extra properties. - blink::WebMouseEvent webEvent = WebEventFactory::toWebMouseEvent(event); - if ((webEvent.GetType() == blink::WebInputEvent::kMouseDown || webEvent.GetType() == blink::WebInputEvent::kMouseUp) - && webEvent.button == blink::WebMouseEvent::Button::kNoButton) { - // Blink can only handle the 3 main mouse-buttons and may assert when processing mouse-down for no button. - LOG(INFO) << "Unhandled mouse button"; - return; - } - - if (webEvent.GetType() == blink::WebInputEvent::kMouseDown) { - if (event->button() != m_clickHelper.lastPressButton - || (event->timestamp() - m_clickHelper.lastPressTimestamp > static_cast(qGuiApp->styleHints()->mouseDoubleClickInterval())) - || (event->pos() - m_clickHelper.lastPressPosition).manhattanLength() > qGuiApp->styleHints()->startDragDistance() - || m_clickHelper.clickCounter >= 3) - m_clickHelper.clickCounter = 0; - - m_clickHelper.lastPressTimestamp = event->timestamp(); - webEvent.click_count = ++m_clickHelper.clickCounter; - m_clickHelper.lastPressButton = event->button(); - m_clickHelper.lastPressPosition = QPointF(event->pos()).toPoint(); - } - - webEvent.movement_x = event->globalX() - m_previousMousePosition.x(); - webEvent.movement_y = event->globalY() - m_previousMousePosition.y(); - - if (IsMouseLocked()) - QCursor::setPos(m_previousMousePosition); - else - m_previousMousePosition = event->globalPos(); - - if (m_imeInProgress && webEvent.GetType() == blink::WebInputEvent::kMouseDown) { - m_imeInProgress = false; - // Tell input method to commit the pre-edit string entered so far, and finish the - // composition operation. -#ifdef Q_OS_WIN - // Yes the function name is counter-intuitive, but commit isn't actually implemented - // by the Windows QPA, and reset does exactly what is necessary in this case. - qApp->inputMethod()->reset(); -#else - qApp->inputMethod()->commit(); -#endif - } - - host()->ForwardMouseEvent(webEvent); -} - -void RenderWidgetHostViewQt::handleHoverEvent(QHoverEvent *ev) -{ - host()->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(ev)); -} - -void RenderWidgetHostViewQt::handleFocusEvent(QFocusEvent *ev) -{ - if (ev->gotFocus()) { - host()->GotFocus(); - host()->SetActive(true); - content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(host()); - Q_ASSERT(viewHost); - if (ev->reason() == Qt::TabFocusReason) - viewHost->SetInitialFocus(false); - else if (ev->reason() == Qt::BacktabFocusReason) - viewHost->SetInitialFocus(true); - ev->accept(); - - m_adapterClient->webContentsAdapter()->handlePendingMouseLockPermission(); - } else if (ev->lostFocus()) { - host()->SetActive(false); - host()->LostFocus(); - ev->accept(); - } -} - void RenderWidgetHostViewQt::SetNeedsBeginFrames(bool needs_begin_frames) { // Not used with viz diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h index 139a4ebfe..42c44b007 100644 --- a/src/core/render_widget_host_view_qt.h +++ b/src/core/render_widget_host_view_qt.h @@ -52,15 +52,8 @@ #include "content/browser/renderer_host/input/mouse_wheel_phase_handler.h" #include "content/browser/renderer_host/render_widget_host_view_base.h" #include "content/browser/renderer_host/text_input_manager.h" -#include "content/public/browser/render_process_host_observer.h" -#include "gpu/ipc/common/gpu_messages.h" #include "ui/events/gesture_detection/filtered_gesture_provider.h" -#include -#include -#include -#include - QT_BEGIN_NAMESPACE class QAccessibleInterface; QT_END_NAMESPACE @@ -79,30 +72,13 @@ class TouchSelectionController; namespace QtWebEngineCore { -class TouchHandleDrawableClient; +class RenderWidgetHostViewQtDelegateClient; class TouchSelectionControllerClientQt; -class TouchSelectionMenuController; - -struct MultipleMouseClickHelper -{ - QPoint lastPressPosition; - Qt::MouseButton lastPressButton; - int clickCounter; - ulong lastPressTimestamp; - - MultipleMouseClickHelper() - : lastPressPosition(QPoint()) - , lastPressButton(Qt::NoButton) - , clickCounter(0) - , lastPressTimestamp(0) - { - } -}; +class WebContentsAdapterClient; class RenderWidgetHostViewQt : public content::RenderWidgetHostViewBase , public ui::GestureProviderClient - , public RenderWidgetHostViewQtDelegateClient , public base::SupportsWeakPtr , public content::TextInputManager::Observer , public DisplayConsumer @@ -121,6 +97,7 @@ public: void setDelegate(RenderWidgetHostViewQtDelegate *delegate); WebContentsAdapterClient *adapterClient() { return m_adapterClient; } void setAdapterClient(WebContentsAdapterClient *adapterClient); + RenderWidgetHostViewQtDelegateClient *delegateClient() const { return m_delegateClient.get(); } void InitAsChild(gfx::NativeView) override; void InitAsPopup(content::RenderWidgetHostView*, const gfx::Rect&) override; @@ -175,146 +152,101 @@ public: void DidStopFlinging() override; std::unique_ptr CreateSyntheticGestureTarget() override; ui::Compositor *GetCompositor() override; +#if defined(OS_MACOSX) + void SetActive(bool active) override { QT_NOT_YET_IMPLEMENTED } + void SpeakSelection() override { QT_NOT_YET_IMPLEMENTED } + void ShowDefinitionForSelection() override { QT_NOT_YET_IMPLEMENTED } +#endif // defined(OS_MACOSX) // Overridden from ui::GestureProviderClient. void OnGestureEvent(const ui::GestureEventData& gesture) override; - // Overridden from RenderWidgetHostViewQtDelegateClient. - QSGNode *updatePaintNode(QSGNode *) override; - void notifyShown() override; - void notifyHidden() override; - void visualPropertiesChanged() override; - bool forwardEvent(QEvent *) override; - QVariant inputMethodQuery(Qt::InputMethodQuery query) override; - void closePopup() override; - // Overridden from content::TextInputManager::Observer void OnUpdateTextInputStateCalled(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view, bool did_update_state) override; void OnSelectionBoundsChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) override; void OnTextSelectionChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) override; - void handleMouseEvent(QMouseEvent*); - void handleKeyEvent(QKeyEvent*); - void handleWheelEvent(QWheelEvent*); - void handleTouchEvent(QTouchEvent*); -#if QT_CONFIG(tabletevent) - void handleTabletEvent(QTabletEvent *ev); -#endif -#ifndef QT_NO_GESTURES - void handleGestureEvent(QNativeGestureEvent *); -#endif - void handleHoverEvent(QHoverEvent*); - void handleFocusEvent(QFocusEvent*); - void handleInputMethodEvent(QInputMethodEvent*); - void handleInputMethodQueryEvent(QInputMethodQueryEvent*); - - template void handlePointerEvent(T*); - -#if defined(OS_MACOSX) - void SetActive(bool active) override { QT_NOT_YET_IMPLEMENTED } - void SpeakSelection() override { QT_NOT_YET_IMPLEMENTED } - void ShowDefinitionForSelection() override { QT_NOT_YET_IMPLEMENTED } -#endif // defined(OS_MACOSX) - // Overridden from content::BrowserAccessibilityDelegate content::BrowserAccessibilityManager* CreateBrowserAccessibilityManager(content::BrowserAccessibilityDelegate* delegate, bool for_root_frame) override; - // Called from WebContentsDelegateQt - void OnDidFirstVisuallyNonEmptyPaint(); - // Overridden from content::RenderFrameMetadataProvider::Observer void OnRenderFrameMetadataChangedAfterActivation() override; // Overridden from DisplayConsumer void scheduleUpdate() override; + // Called from RenderWidgetHostViewQtDelegateClient. + QSGNode *updatePaintNode(QSGNode *); + void notifyShown(); + void notifyHidden(); + bool updateScreenInfo(); + void handleWheelEvent(QWheelEvent *); + void processMotionEvent(const ui::MotionEvent &motionEvent); + void resetInputManagerState() { m_imState = 0; } + + // Called from WebContentsDelegateQt. + void OnDidFirstVisuallyNonEmptyPaint(); + + // Called from WebContentsAdapter. gfx::SizeF lastContentsSize() const { return m_lastContentsSize; } gfx::Vector2dF lastScrollOffset() const { return m_lastScrollOffset; } + ui::TextInputType getTextInputType() const; + content::mojom::FrameInputHandler *getFrameInputHandler(); + ui::TouchSelectionController *getTouchSelectionController() const { return m_touchSelectionController.get(); } TouchSelectionControllerClientQt *getTouchSelectionControllerClient() const { return m_touchSelectionControllerClient.get(); } - content::mojom::FrameInputHandler *getFrameInputHandler(); - ui::TextInputType getTextInputType() const; + + void synchronizeVisualProperties( + const base::Optional &childSurfaceId); private: friend class DelegatedFrameHostClientQt; - void processMotionEvent(const ui::MotionEvent &motionEvent); - void clearPreviousTouchMotionState(); - QList mapTouchPointIds(const QList &inputPoints); - - bool IsPopup() const; - - void selectionChanged(); + bool isPopup() const; content::RenderFrameHost *getFocusedFrameHost(); - - void synchronizeVisualProperties(const base::Optional &childSurfaceId); - void callUpdate(); - // Geometry of the view in screen DIPs. - gfx::Rect m_viewRectInDips; - // Geometry of the window, including frame, in screen DIPs. - gfx::Rect m_windowRectInDips; - content::ScreenInfo m_screenInfo; - scoped_refptr m_taskRunner; - ui::FilteredGestureProvider m_gestureProvider; - base::TimeDelta m_eventsToNowDelta; - bool m_sendMotionActionDown; - bool m_touchMotionStarted; - QMap m_touchIdMapping; - QList m_previousTouchPoints; + viz::FrameSinkId m_frameSinkId; + std::unique_ptr m_delegateClient; std::unique_ptr m_delegate; + QMetaObject::Connection m_adapterClientDestroyedConnection; + WebContentsAdapterClient *m_adapterClient = nullptr; - bool m_visible; + bool m_isMouseLocked = false; + bool m_visible = false; bool m_deferredShow = false; - DelegatedFrameHostClientQt m_delegatedFrameHostClient{this}; + gfx::Vector2dF m_lastScrollOffset; + gfx::SizeF m_lastContentsSize; + DelegatedFrameHostClientQt m_delegatedFrameHostClient { this }; + LoadVisuallyCommittedState m_loadVisuallyCommittedState = NotCommitted; + + // VIZ + content::ScreenInfo m_screenInfo; std::unique_ptr m_delegatedFrameHost; std::unique_ptr m_rootLayer; std::unique_ptr m_uiCompositor; scoped_refptr m_displayFrameSink; - LoadVisuallyCommittedState m_loadVisuallyCommittedState; - - QMetaObject::Connection m_adapterClientDestroyedConnection; - WebContentsAdapterClient *m_adapterClient; - MultipleMouseClickHelper m_clickHelper; - - bool m_imeInProgress; - bool m_receivedEmptyImeEvent; - QPoint m_previousMousePosition; - bool m_isMouseLocked; - - gfx::Vector2dF m_lastScrollOffset; - gfx::SizeF m_lastContentsSize; viz::ParentLocalSurfaceIdAllocator m_dfhLocalSurfaceIdAllocator; viz::ParentLocalSurfaceIdAllocator m_uiCompositorLocalSurfaceIdAllocator; - uint m_imState; - int m_anchorPositionWithinSelection; - int m_cursorPositionWithinSelection; - uint m_cursorPosition; - bool m_emptyPreviousSelection; - QString m_surroundingText; - - bool m_imeHasHiddenTextCapability; + // IME + uint m_imState = 0; - bool m_wheelAckPending; + // Wheel + bool m_wheelAckPending = false; QList m_pendingWheelEvents; - content::MouseWheelPhaseHandler m_mouseWheelPhaseHandler; - viz::FrameSinkId m_frameSinkId; - - std::string m_editCommand; + content::MouseWheelPhaseHandler m_mouseWheelPhaseHandler { this }; + // TouchSelection std::unique_ptr m_touchSelectionControllerClient; std::unique_ptr m_touchSelectionController; gfx::SelectionBound m_selectionStart; gfx::SelectionBound m_selectionEnd; - base::WeakPtrFactory m_weakPtrFactory{this}; - - uint m_mouseButtonPressed = 0; + base::WeakPtrFactory m_weakPtrFactory { this }; }; } // namespace QtWebEngineCore diff --git a/src/core/render_widget_host_view_qt_delegate.h b/src/core/render_widget_host_view_qt_delegate.h index 46f1802a6..0afe258f1 100644 --- a/src/core/render_widget_host_view_qt_delegate.h +++ b/src/core/render_widget_host_view_qt_delegate.h @@ -53,39 +53,23 @@ #include "qtwebenginecoreglobal_p.h" -#include -#include +#include +#include +#include +#include QT_BEGIN_NAMESPACE -class QEvent; -class QInputMethodEvent; +class QSGImageNode; class QSGLayer; -class QSGNode; class QSGRectangleNode; class QSGTexture; -class QVariant; class QWheelEvent; - -class QSGImageNode; +class QWindow; QT_END_NAMESPACE namespace QtWebEngineCore { -class WebContentsAdapterClient; - -class Q_WEBENGINECORE_PRIVATE_EXPORT RenderWidgetHostViewQtDelegateClient { -public: - virtual ~RenderWidgetHostViewQtDelegateClient() { } - virtual QSGNode *updatePaintNode(QSGNode *) = 0; - virtual void notifyShown() = 0; - virtual void notifyHidden() = 0; - virtual void visualPropertiesChanged() = 0; - virtual bool forwardEvent(QEvent *) = 0; - virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) = 0; - virtual void closePopup() = 0; -}; - class Q_WEBENGINECORE_PRIVATE_EXPORT RenderWidgetHostViewQtDelegate { public: virtual ~RenderWidgetHostViewQtDelegate() { } diff --git a/src/core/render_widget_host_view_qt_delegate_client.cpp b/src/core/render_widget_host_view_qt_delegate_client.cpp new file mode 100644 index 000000000..d7674fd13 --- /dev/null +++ b/src/core/render_widget_host_view_qt_delegate_client.cpp @@ -0,0 +1,973 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "render_widget_host_view_qt_delegate_client.h" + +#include "render_widget_host_view_qt.h" +#include "touch_selection_controller_client_qt.h" +#include "type_conversion.h" +#include "web_contents_adapter.h" +#include "web_contents_adapter_client.h" +#include "web_event_factory.h" + +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "ui/touch_selection/touch_selection_controller.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace QtWebEngineCore { + +static inline int firstAvailableId(const QMap &map) +{ + ui::BitSet32 usedIds; + QMap::const_iterator end = map.end(); + for (QMap::const_iterator it = map.begin(); it != end; ++it) + usedIds.mark_bit(it.value()); + return usedIds.first_unmarked_bit(); +} + +static QList +mapTouchPointIds(const QList &inputPoints) +{ + static QMap touchIdMapping; + QList outputPoints = inputPoints; + for (int i = 0; i < outputPoints.size(); ++i) { + QTouchEvent::TouchPoint &point = outputPoints[i]; + + int qtId = point.id(); + QMap::const_iterator it = touchIdMapping.find(qtId); + if (it == touchIdMapping.end()) + it = touchIdMapping.insert(qtId, firstAvailableId(touchIdMapping)); + point.setId(it.value()); + + if (point.state() == Qt::TouchPointReleased) + touchIdMapping.remove(qtId); + } + + return outputPoints; +} + +static inline bool compareTouchPoints(const QTouchEvent::TouchPoint &lhs, + const QTouchEvent::TouchPoint &rhs) +{ + // TouchPointPressed < TouchPointMoved < TouchPointReleased + return lhs.state() < rhs.state(); +} + +static uint32_t s_eventId = 0; +class MotionEventQt : public ui::MotionEvent +{ +public: + MotionEventQt(const QList &touchPoints, + const base::TimeTicks &eventTime, Action action, + const Qt::KeyboardModifiers modifiers, int index = -1) + : touchPoints(touchPoints) + , eventTime(eventTime) + , action(action) + , eventId(++s_eventId) + , flags(flagsFromModifiers(modifiers)) + , index(index) + { + // ACTION_DOWN and ACTION_UP must be accesssed through pointer_index 0 + Q_ASSERT((action != Action::DOWN && action != Action::UP) || index == 0); + } + + uint32_t GetUniqueEventId() const override { return eventId; } + Action GetAction() const override { return action; } + int GetActionIndex() const override { return index; } + size_t GetPointerCount() const override { return touchPoints.size(); } + int GetPointerId(size_t pointer_index) const override + { + return touchPoints.at(pointer_index).id(); + } + float GetX(size_t pointer_index) const override + { + return touchPoints.at(pointer_index).pos().x(); + } + float GetY(size_t pointer_index) const override + { + return touchPoints.at(pointer_index).pos().y(); + } + float GetRawX(size_t pointer_index) const override + { + return touchPoints.at(pointer_index).screenPos().x(); + } + float GetRawY(size_t pointer_index) const override + { + return touchPoints.at(pointer_index).screenPos().y(); + } + float GetTouchMajor(size_t pointer_index) const override + { + QSizeF diams = touchPoints.at(pointer_index).ellipseDiameters(); + return std::max(diams.height(), diams.width()); + } + float GetTouchMinor(size_t pointer_index) const override + { + QSizeF diams = touchPoints.at(pointer_index).ellipseDiameters(); + return std::min(diams.height(), diams.width()); + } + float GetOrientation(size_t pointer_index) const override { return 0; } + int GetFlags() const override { return flags; } + float GetPressure(size_t pointer_index) const override + { + return touchPoints.at(pointer_index).pressure(); + } + float GetTiltX(size_t pointer_index) const override { return 0; } + float GetTiltY(size_t pointer_index) const override { return 0; } + float GetTwist(size_t) const override { return 0; } + float GetTangentialPressure(size_t) const override { return 0; } + base::TimeTicks GetEventTime() const override { return eventTime; } + + size_t GetHistorySize() const override { return 0; } + base::TimeTicks GetHistoricalEventTime(size_t historical_index) const override + { + return base::TimeTicks(); + } + float GetHistoricalTouchMajor(size_t pointer_index, size_t historical_index) const override + { + return 0; + } + float GetHistoricalX(size_t pointer_index, size_t historical_index) const override { return 0; } + float GetHistoricalY(size_t pointer_index, size_t historical_index) const override { return 0; } + ToolType GetToolType(size_t pointer_index) const override + { + return (touchPoints.at(pointer_index).flags() & QTouchEvent::TouchPoint::InfoFlag::Pen) + ? ui::MotionEvent::ToolType::STYLUS + : ui::MotionEvent::ToolType::FINGER; + } + int GetButtonState() const override { return 0; } + +private: + QList touchPoints; + base::TimeTicks eventTime; + Action action; + const uint32_t eventId; + int flags; + int index; +}; + +RenderWidgetHostViewQtDelegateClient::RenderWidgetHostViewQtDelegateClient( + RenderWidgetHostViewQt *rwhv) + : m_rwhv(rwhv) +{ + Q_ASSERT(rwhv); + + const QPlatformInputContext *context = + QGuiApplicationPrivate::platformIntegration()->inputContext(); + m_imeHasHiddenTextCapability = + context && context->hasCapability(QPlatformInputContext::HiddenTextCapability); +} + +QSGNode *RenderWidgetHostViewQtDelegateClient::updatePaintNode(QSGNode *oldNode) +{ + return m_rwhv->updatePaintNode(oldNode); +} + +void RenderWidgetHostViewQtDelegateClient::notifyShown() +{ + m_rwhv->notifyShown(); +} + +void RenderWidgetHostViewQtDelegateClient::notifyHidden() +{ + m_rwhv->notifyHidden(); +} + +void RenderWidgetHostViewQtDelegateClient::visualPropertiesChanged() +{ + RenderWidgetHostViewQtDelegate *delegate = m_rwhv->delegate(); + if (!delegate) + return; + + QRect oldViewRect = m_viewRectInDips; + m_viewRectInDips = delegate->viewGeometry().toAlignedRect(); + + QRect oldWindowRect = m_windowRectInDips; + m_windowRectInDips = delegate->windowGeometry(); + + bool screenInfoChanged = m_rwhv->updateScreenInfo(); + + if (m_viewRectInDips != oldViewRect || m_windowRectInDips != oldWindowRect) + m_rwhv->host()->SendScreenRects(); + + if (m_viewRectInDips.size() != oldViewRect.size() || screenInfoChanged) + m_rwhv->synchronizeVisualProperties(base::nullopt); +} + +bool RenderWidgetHostViewQtDelegateClient::forwardEvent(QEvent *event) +{ + Q_ASSERT(m_rwhv->host()->GetView()); + + switch (event->type()) { + case QEvent::ShortcutOverride: { + QKeyEvent *keyEvent = static_cast(event); + + auto acceptKeyOutOfInputField = [](QKeyEvent *keyEvent) -> bool { +#ifdef Q_OS_MACOS + // Check if a shortcut is registered for this key sequence. + QKeySequence sequence = QKeySequence((keyEvent->modifiers() | keyEvent->key()) + & ~(Qt::KeypadModifier | Qt::GroupSwitchModifier)); + if (QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(sequence)) + return false; + + // The following shortcuts are handled out of input field too but + // disabled on macOS to let the blinking menu handling to the + // embedder application (see kKeyboardCodeKeyDownEntries in + // third_party/WebKit/Source/core/editing/EditingBehavior.cpp). + // Let them pass on macOS to generate the corresponding edit command. + return keyEvent->matches(QKeySequence::Copy) || keyEvent->matches(QKeySequence::Paste) + || keyEvent->matches(QKeySequence::Cut) + || keyEvent->matches(QKeySequence::SelectAll); +#else + return false; +#endif + }; + + if (!inputMethodQuery(Qt::ImEnabled).toBool() + && !(inputMethodQuery(Qt::ImHints).toInt() & Qt::ImhHiddenText) + && !acceptKeyOutOfInputField(keyEvent)) + return false; + + Q_ASSERT(m_editCommand.empty()); + if (WebEventFactory::getEditCommand(keyEvent, &m_editCommand) + || QInputControl::isCommonTextEditShortcut(keyEvent)) { + event->accept(); + return true; + } + + return false; + } + case QEvent::MouseButtonPress: + m_rwhv->Focus(); + Q_FALLTHROUGH(); + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + // Skip second MouseMove event when a window is being adopted, so that Chromium + // can properly handle further move events. + // Also make sure the adapter client exists to prevent a null pointer dereference, + // because it's possible for a QWebEnginePagePrivate (adapter) instance to be destroyed, + // and then the OS (observed on Windows) might still send mouse move events to a still + // existing popup RWHVQDW instance. + if (m_rwhv->adapterClient() && m_rwhv->adapterClient()->isBeingAdopted()) + return false; + handleMouseEvent(static_cast(event)); + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: + handleKeyEvent(static_cast(event)); + break; + case QEvent::Wheel: + m_rwhv->handleWheelEvent(static_cast(event)); + break; + case QEvent::TouchBegin: + m_rwhv->Focus(); + Q_FALLTHROUGH(); + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::TouchCancel: + handleTouchEvent(static_cast(event)); + break; +#if QT_CONFIG(tabletevent) + case QEvent::TabletPress: + m_rwhv->Focus(); + Q_FALLTHROUGH(); + case QEvent::TabletRelease: + case QEvent::TabletMove: + handleTabletEvent(static_cast(event)); + break; +#endif +#if QT_CONFIG(gestures) + case QEvent::NativeGesture: + handleGestureEvent(static_cast(event)); + break; +#endif + case QEvent::HoverMove: + handleHoverEvent(static_cast(event)); + break; + case QEvent::FocusIn: + case QEvent::FocusOut: + handleFocusEvent(static_cast(event)); + break; + case QEvent::InputMethod: + handleInputMethodEvent(static_cast(event)); + break; + case QEvent::InputMethodQuery: + handleInputMethodQueryEvent(static_cast(event)); + break; + case QEvent::Leave: +#ifdef Q_OS_WIN + if (m_mouseButtonPressed > 0) + return false; +#endif + case QEvent::HoverLeave: + m_rwhv->host()->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(event)); + break; + default: + return false; + } + return true; +} + +QVariant RenderWidgetHostViewQtDelegateClient::inputMethodQuery(Qt::InputMethodQuery query) +{ + switch (query) { + case Qt::ImEnabled: { + ui::TextInputType type = m_rwhv->getTextInputType(); + bool editorVisible = type != ui::TEXT_INPUT_TYPE_NONE; + // IME manager should disable composition on input fields with ImhHiddenText hint if + // supported + if (m_imeHasHiddenTextCapability) + return QVariant(editorVisible); + + bool passwordInput = type == ui::TEXT_INPUT_TYPE_PASSWORD; + return QVariant(editorVisible && !passwordInput); + } + case Qt::ImFont: + // TODO: Implement this + return QVariant(); + case Qt::ImCursorRectangle: { + if (m_rwhv->GetTextInputManager()) { + if (auto *region = m_rwhv->GetTextInputManager()->GetSelectionRegion()) { + if (region->focus.GetHeight() > 0) { + gfx::Rect caretRect = + gfx::RectBetweenSelectionBounds(region->anchor, region->focus); + if (caretRect.width() == 0) + caretRect.set_width(1); // IME API on Windows expects a width > 0 + return toQt(caretRect); + } + } + } + return QVariant(); + } + case Qt::ImCursorPosition: + return m_cursorPosition; + case Qt::ImAnchorPosition: + return m_rwhv->GetSelectedText().empty() ? m_cursorPosition + : m_anchorPositionWithinSelection; + case Qt::ImSurroundingText: + return m_surroundingText; + case Qt::ImCurrentSelection: + return toQt(m_rwhv->GetSelectedText()); + case Qt::ImMaximumTextLength: + // TODO: Implement this + return QVariant(); // No limit. + case Qt::ImHints: +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + return int(toQtInputMethodHints(m_rwhv->getTextInputType()) | Qt::ImhNoPredictiveText + | Qt::ImhNoTextHandles | Qt::ImhNoEditMenu); +#else + return int(toQtInputMethodHints(m_rwhv->getTextInputType()) | Qt::ImhNoPredictiveText); +#endif + default: + return QVariant(); + } +} + +void RenderWidgetHostViewQtDelegateClient::closePopup() +{ + // We notify the popup to be closed by telling it that it lost focus. WebKit does the rest + // (hiding the widget and automatic memory cleanup via + // RenderWidget::CloseWidgetSoon() -> RenderWidgetHostImpl::ShutdownAndDestroyWidget(true). + m_rwhv->host()->SetActive(false); + m_rwhv->host()->LostFocus(); +} + +template +void RenderWidgetHostViewQtDelegateClient::handlePointerEvent(T *event) +{ + // Currently WebMouseEvent is a subclass of WebPointerProperties, so basically + // tablet events are mouse events with extra properties. + blink::WebMouseEvent webEvent = WebEventFactory::toWebMouseEvent(event); + if ((webEvent.GetType() == blink::WebInputEvent::kMouseDown + || webEvent.GetType() == blink::WebInputEvent::kMouseUp) + && webEvent.button == blink::WebMouseEvent::Button::kNoButton) { + // Blink can only handle the 3 main mouse-buttons and may assert when processing mouse-down + // for no button. + LOG(INFO) << "Unhandled mouse button"; + return; + } + + if (webEvent.GetType() == blink::WebInputEvent::kMouseDown) { + if (event->button() != m_clickHelper.lastPressButton + || (event->timestamp() - m_clickHelper.lastPressTimestamp + > static_cast(qGuiApp->styleHints()->mouseDoubleClickInterval())) + || (event->pos() - m_clickHelper.lastPressPosition).manhattanLength() + > qGuiApp->styleHints()->startDragDistance() + || m_clickHelper.clickCounter >= 3) + m_clickHelper.clickCounter = 0; + + m_clickHelper.lastPressTimestamp = event->timestamp(); + webEvent.click_count = ++m_clickHelper.clickCounter; + m_clickHelper.lastPressButton = event->button(); + m_clickHelper.lastPressPosition = QPointF(event->pos()).toPoint(); + } + + webEvent.movement_x = event->globalX() - m_previousMousePosition.x(); + webEvent.movement_y = event->globalY() - m_previousMousePosition.y(); + + if (m_rwhv->IsMouseLocked()) + QCursor::setPos(m_previousMousePosition); + else + m_previousMousePosition = event->globalPos(); + + if (m_imeInProgress && webEvent.GetType() == blink::WebInputEvent::kMouseDown) { + m_imeInProgress = false; + // Tell input method to commit the pre-edit string entered so far, and finish the + // composition operation. +#ifdef Q_OS_WIN + // Yes the function name is counter-intuitive, but commit isn't actually implemented + // by the Windows QPA, and reset does exactly what is necessary in this case. + qApp->inputMethod()->reset(); +#else + qApp->inputMethod()->commit(); +#endif + } + + m_rwhv->host()->ForwardMouseEvent(webEvent); +} + +void RenderWidgetHostViewQtDelegateClient::handleMouseEvent(QMouseEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress) + m_mouseButtonPressed++; + if (event->type() == QEvent::MouseButtonRelease) + m_mouseButtonPressed--; + + // Don't forward mouse events synthesized by the system, which are caused by genuine touch + // events. Chromium would then process for e.g. a mouse click handler twice, once due to the + // system synthesized mouse event, and another time due to a touch-to-gesture-to-mouse + // transformation done by Chromium. + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; + handlePointerEvent(event); +} + +void RenderWidgetHostViewQtDelegateClient::handleKeyEvent(QKeyEvent *event) +{ + if (m_rwhv->IsMouseLocked() && event->key() == Qt::Key_Escape + && event->type() == QEvent::KeyRelease) + m_rwhv->UnlockMouse(); + + if (m_receivedEmptyImeEvent) { + // IME composition was not finished with a valid commit string. + // We're getting the composition result in a key event. + if (event->key() != 0) { + // The key event is not a result of an IME composition. Cancel IME. + m_rwhv->host()->ImeCancelComposition(); + m_receivedEmptyImeEvent = false; + } else { + if (event->type() == QEvent::KeyRelease) { + m_rwhv->host()->ImeCommitText(toString16(event->text()), + std::vector(), + gfx::Range::InvalidRange(), 0); + m_receivedEmptyImeEvent = false; + m_imeInProgress = false; + } + return; + } + } + + // Ignore autorepeating KeyRelease events so that the generated web events + // conform to the spec, which requires autorepeat to result in a sequence of + // keypress events and only one final keyup event: + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Auto-repeat_handling + // https://w3c.github.io/uievents/#dom-keyboardevent-repeat + if (event->type() == QEvent::KeyRelease && event->isAutoRepeat()) + return; + + content::NativeWebKeyboardEvent webEvent = WebEventFactory::toWebKeyboardEvent(event); + if (webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && !m_editCommand.empty()) { + ui::LatencyInfo latency; + latency.set_source_event_type(ui::SourceEventType::KEY_PRESS); + content::EditCommands commands; + commands.emplace_back(m_editCommand, ""); + m_editCommand.clear(); + m_rwhv->host()->ForwardKeyboardEventWithCommands(webEvent, latency, &commands, nullptr); + return; + } + + bool keyDownTextInsertion = + webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && webEvent.text[0]; + webEvent.skip_in_browser = keyDownTextInsertion; + m_rwhv->host()->ForwardKeyboardEvent(webEvent); + + if (keyDownTextInsertion) { + // Blink won't consume the RawKeyDown, but rather the Char event in this case. + // The RawKeyDown is skipped on the way back (see above). + // The same os_event will be set on both NativeWebKeyboardEvents. + webEvent.skip_in_browser = false; + webEvent.SetType(blink::WebInputEvent::kChar); + m_rwhv->host()->ForwardKeyboardEvent(webEvent); + } +} + +void RenderWidgetHostViewQtDelegateClient::handleTouchEvent(QTouchEvent *event) +{ + // On macOS instead of handling touch events, we use the OS provided QNativeGestureEvents. +#ifdef Q_OS_MACOS + if (event->spontaneous()) { + return; + } else { + VLOG(1) << "Sending simulated touch events to Chromium does not work properly on macOS. " + "Consider using QNativeGestureEvents or QMouseEvents."; + } +#endif + + // Chromium expects the touch event timestamps to be comparable to base::TimeTicks::Now(). + // Most importantly we also have to preserve the relative time distance between events. + // Calculate a delta between event timestamps and Now() on the first received event, and + // apply this delta to all successive events. This delta is most likely smaller than it + // should by calculating it here but this will hopefully cause less than one frame of delay. + base::TimeTicks eventTimestamp = + base::TimeTicks() + base::TimeDelta::FromMilliseconds(event->timestamp()); + static base::TimeDelta eventsToNowDelta = base::TimeTicks::Now() - eventTimestamp; + eventTimestamp += eventsToNowDelta; + + QList touchPoints = mapTouchPointIds(event->touchPoints()); + // Make sure that ACTION_POINTER_DOWN is delivered before ACTION_MOVE, + // and ACTION_MOVE before ACTION_POINTER_UP. + std::sort(touchPoints.begin(), touchPoints.end(), compareTouchPoints); + + // Check first if the touch event should be routed to the selectionController + if (!touchPoints.isEmpty()) { + ui::MotionEvent::Action action; + switch (touchPoints[0].state()) { + case Qt::TouchPointPressed: + action = ui::MotionEvent::Action::DOWN; + break; + case Qt::TouchPointMoved: + action = ui::MotionEvent::Action::MOVE; + break; + case Qt::TouchPointReleased: + action = ui::MotionEvent::Action::UP; + break; + default: + action = ui::MotionEvent::Action::NONE; + break; + } + + MotionEventQt motionEvent(touchPoints, eventTimestamp, action, event->modifiers(), 0); + if (m_rwhv->getTouchSelectionController()->WillHandleTouchEvent(motionEvent)) { + m_previousTouchPoints = touchPoints; + event->accept(); + return; + } + } else { + // An empty touchPoints always corresponds to a TouchCancel event. + // We can't forward touch cancellations without a previously processed touch event, + // as Chromium expects the previous touchPoints for Action::CANCEL. + // If both are empty that means the TouchCancel was sent without an ongoing touch, + // so there's nothing to cancel anyway. + touchPoints = m_previousTouchPoints; + if (touchPoints.isEmpty()) + return; + + MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL, + event->modifiers()); + if (m_rwhv->getTouchSelectionController()->WillHandleTouchEvent(cancelEvent)) { + m_previousTouchPoints.clear(); + event->accept(); + return; + } + } + + switch (event->type()) { + case QEvent::TouchBegin: + m_sendMotionActionDown = true; + m_touchMotionStarted = true; + m_rwhv->getTouchSelectionControllerClient()->onTouchDown(); + break; + case QEvent::TouchUpdate: + m_touchMotionStarted = true; + break; + case QEvent::TouchCancel: { + // Only process TouchCancel events received following a TouchBegin or TouchUpdate event + if (m_touchMotionStarted) { + MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL, + event->modifiers()); + m_rwhv->processMotionEvent(cancelEvent); + } + + clearPreviousTouchMotionState(); + return; + } + case QEvent::TouchEnd: + clearPreviousTouchMotionState(); + m_rwhv->getTouchSelectionControllerClient()->onTouchUp(); + break; + default: + break; + } + + if (m_imeInProgress && event->type() == QEvent::TouchBegin) { + m_imeInProgress = false; + // Tell input method to commit the pre-edit string entered so far, and finish the + // composition operation. +#ifdef Q_OS_WIN + // Yes the function name is counter-intuitive, but commit isn't actually implemented + // by the Windows QPA, and reset does exactly what is necessary in this case. + qApp->inputMethod()->reset(); +#else + qApp->inputMethod()->commit(); +#endif + } + + for (int i = 0; i < touchPoints.size(); ++i) { + ui::MotionEvent::Action action; + switch (touchPoints[i].state()) { + case Qt::TouchPointPressed: + if (m_sendMotionActionDown) { + action = ui::MotionEvent::Action::DOWN; + m_sendMotionActionDown = false; + } else { + action = ui::MotionEvent::Action::POINTER_DOWN; + } + break; + case Qt::TouchPointMoved: + action = ui::MotionEvent::Action::MOVE; + break; + case Qt::TouchPointReleased: + action = touchPoints.size() > 1 ? ui::MotionEvent::Action::POINTER_UP + : ui::MotionEvent::Action::UP; + break; + default: + // Ignore Qt::TouchPointStationary + continue; + } + + MotionEventQt motionEvent(touchPoints, eventTimestamp, action, event->modifiers(), i); + m_rwhv->processMotionEvent(motionEvent); + } + + m_previousTouchPoints = touchPoints; +} + +#if QT_CONFIG(tabletevent) +void RenderWidgetHostViewQtDelegateClient::handleTabletEvent(QTabletEvent *event) +{ + handlePointerEvent(event); +} +#endif + +#if QT_CONFIG(gestures) +void RenderWidgetHostViewQtDelegateClient::handleGestureEvent(QNativeGestureEvent *event) +{ + const Qt::NativeGestureType type = event->gestureType(); + // These are the only supported gestures by Chromium so far. + if (type == Qt::ZoomNativeGesture || type == Qt::SmartZoomNativeGesture) { + m_rwhv->host()->ForwardGestureEvent(WebEventFactory::toWebGestureEvent(event)); + } +} +#endif + +void RenderWidgetHostViewQtDelegateClient::handleHoverEvent(QHoverEvent *event) +{ + m_rwhv->host()->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(event)); +} + +void RenderWidgetHostViewQtDelegateClient::handleFocusEvent(QFocusEvent *event) +{ + if (event->gotFocus()) { + m_rwhv->host()->GotFocus(); + m_rwhv->host()->SetActive(true); + content::RenderViewHostImpl *rvh = content::RenderViewHostImpl::From(m_rwhv->host()); + Q_ASSERT(rvh); + if (event->reason() == Qt::TabFocusReason) + rvh->SetInitialFocus(false); + else if (event->reason() == Qt::BacktabFocusReason) + rvh->SetInitialFocus(true); + event->accept(); + + m_rwhv->adapterClient()->webContentsAdapter()->handlePendingMouseLockPermission(); + } else if (event->lostFocus()) { + m_rwhv->host()->SetActive(false); + m_rwhv->host()->LostFocus(); + event->accept(); + } +} + +void RenderWidgetHostViewQtDelegateClient::handleInputMethodEvent(QInputMethodEvent *event) +{ + m_rwhv->resetInputManagerState(); + + if (!m_rwhv->host()) + return; + + QString commitString = event->commitString(); + QString preeditString = event->preeditString(); + + int cursorPositionInPreeditString = -1; + gfx::Range selectionRange = gfx::Range::InvalidRange(); + + const QList &attributes = event->attributes(); + std::vector underlines; + bool hasSelection = false; + + for (const auto &attribute : attributes) { + switch (attribute.type) { + case QInputMethodEvent::TextFormat: { + if (preeditString.isEmpty()) + break; + + int start = qMin(attribute.start, (attribute.start + attribute.length)); + int end = qMax(attribute.start, (attribute.start + attribute.length)); + + // Blink does not support negative position values. Adjust start and end positions + // to non-negative values. + if (start < 0) { + start = 0; + end = qMax(0, start + end); + } + + underlines.push_back(ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, start, end, + ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT)); + + QTextCharFormat format = qvariant_cast(attribute.value).toCharFormat(); + if (format.underlineStyle() != QTextCharFormat::NoUnderline) + underlines.back().underline_color = toSk(format.underlineColor()); + + break; + } + case QInputMethodEvent::Cursor: + // Always set the position of the cursor, even if it's marked invisible by Qt, otherwise + // there is no way the user will know which part of the composition string will be + // changed, when performing an IME-specific action (like selecting a different word + // suggestion). + 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; + default: + break; + } + } + + 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::mojom::FrameInputHandler *frameInputHandler = m_rwhv->getFrameInputHandler(); + if (frameInputHandler) + frameInputHandler->SetEditableSelectionOffsets(selectionRange.start(), + selectionRange.end()); + } + + int replacementLength = event->replacementLength(); + gfx::Range replacementRange = gfx::Range::InvalidRange(); + + if (replacementLength > 0) { + int replacementStart = event->replacementStart() < 0 + ? m_cursorPosition + event->replacementStart() + : event->replacementStart(); + if (replacementStart >= 0 && replacementStart < m_surroundingText.length()) + replacementRange = gfx::Range(replacementStart, replacementStart + replacementLength); + } + + // There are so-far two known cases, when an empty QInputMethodEvent is received. + // First one happens when backspace is used to remove the last character in the pre-edit + // string, thus signaling the end of the composition. + // The second one happens (on Windows) when a Korean char gets composed, but instead of + // the event having a commit string, both strings are empty, and the actual char is received + // as a QKeyEvent after the QInputMethodEvent is processed. + // In lieu of the second case, we can't simply cancel the composition on an empty event, + // and then add the Korean char when QKeyEvent is received, because that leads to text + // flickering in the textarea (or any other element). + // Instead we postpone the processing of the empty QInputMethodEvent by posting it + // to the same focused object, and cancelling the composition on the next event loop tick. + if (commitString.isEmpty() && preeditString.isEmpty() && replacementLength == 0) { + if (!m_receivedEmptyImeEvent && m_imeInProgress && !hasSelection) { + m_receivedEmptyImeEvent = true; + QInputMethodEvent *eventCopy = new QInputMethodEvent(*event); + QGuiApplication::postEvent(qApp->focusObject(), eventCopy); + } else { + m_receivedEmptyImeEvent = false; + if (m_imeInProgress) { + m_imeInProgress = false; + m_rwhv->host()->ImeCancelComposition(); + } + } + + return; + } + + m_receivedEmptyImeEvent = false; + + // Finish compostion: insert or erase text. + if (!commitString.isEmpty() || replacementLength > 0) { + m_rwhv->host()->ImeCommitText(toString16(commitString), underlines, replacementRange, 0); + m_imeInProgress = false; + } + + // Update or start new composition. + // Be aware of that, we might get a commit string and a pre-edit string in a single event and + // this means a new composition. + if (!preeditString.isEmpty()) { + m_rwhv->host()->ImeSetComposition(toString16(preeditString), underlines, replacementRange, + selectionRange.start(), selectionRange.end()); + m_imeInProgress = true; + } +} + +void RenderWidgetHostViewQtDelegateClient::handleInputMethodQueryEvent( + QInputMethodQueryEvent *event) +{ + Qt::InputMethodQueries queries = event->queries(); + for (uint i = 0; i < 32; ++i) { + Qt::InputMethodQuery query = (Qt::InputMethodQuery)(int)(queries & (1 << i)); + if (query) { + QVariant v = inputMethodQuery(query); + event->setValue(query, v); + } + } + event->accept(); +} + +void RenderWidgetHostViewQtDelegateClient::clearPreviousTouchMotionState() +{ + m_previousTouchPoints.clear(); + m_touchMotionStarted = false; +} + +void RenderWidgetHostViewQtDelegateClient::selectionChanged() +{ + m_rwhv->resetInputManagerState(); + ui::TextInputType type = m_rwhv->getTextInputType(); + content::TextInputManager *text_input_manager = m_rwhv->GetTextInputManager(); + + // Handle text selection out of an input field + if (type == ui::TEXT_INPUT_TYPE_NONE) { + if (m_rwhv->GetSelectedText().empty() && m_emptyPreviousSelection) + return; + + // Reset position values to emit selectionChanged signal when clearing text selection + // by clicking into an input field. These values are intended to be used by inputMethodQuery + // so they are not expected to be valid when selection is out of an input field. + m_anchorPositionWithinSelection = -1; + m_cursorPositionWithinSelection = -1; + + m_emptyPreviousSelection = m_rwhv->GetSelectedText().empty(); + m_rwhv->adapterClient()->selectionChanged(); + return; + } + + if (m_rwhv->GetSelectedText().empty()) { + // RenderWidgetHostViewQt::OnUpdateTextInputStateCalled() does not update the cursor + // position if the selection is cleared because TextInputState changes before the + // TextSelection change. + Q_ASSERT(text_input_manager->GetTextInputState()); + m_cursorPosition = text_input_manager->GetTextInputState()->selection_start; + m_rwhv->delegate()->inputMethodStateChanged(true /*editorVisible*/, + type == ui::TEXT_INPUT_TYPE_PASSWORD); + + m_anchorPositionWithinSelection = m_cursorPosition; + m_cursorPositionWithinSelection = m_cursorPosition; + + if (!m_emptyPreviousSelection) { + m_emptyPreviousSelection = true; + m_rwhv->adapterClient()->selectionChanged(); + } + + return; + } + + const content::TextInputManager::TextSelection *selection = + text_input_manager->GetTextSelection(); + if (!selection) + return; + + if (!selection->range().IsValid()) + return; + + int newAnchorPositionWithinSelection = 0; + int 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 (!selection->selected_text().empty()) + m_cursorPosition = newCursorPositionWithinSelection; + + m_emptyPreviousSelection = selection->selected_text().empty(); + m_rwhv->delegate()->inputMethodStateChanged(true /*editorVisible*/, + type == ui::TEXT_INPUT_TYPE_PASSWORD); + m_rwhv->adapterClient()->selectionChanged(); +} + +} // namespace QtWebEngineCore diff --git a/src/core/render_widget_host_view_qt_delegate_client.h b/src/core/render_widget_host_view_qt_delegate_client.h new file mode 100644 index 000000000..bb14fe951 --- /dev/null +++ b/src/core/render_widget_host_view_qt_delegate_client.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef RENDER_WIDGET_HOST_VIEW_QT_DELEGATE_CLIENT_H +#define RENDER_WIDGET_HOST_VIEW_QT_DELEGATE_CLIENT_H + +#include "qtwebenginecoreglobal_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QEvent; +class QSGNode; +class QVariant; + +class QMouseEvent; +class QKeyEvent; +class QTabletEvent; +class QNativeGestureEvent; +class QHoverEvent; +class QFocusEvent; +class QInputMethodEvent; +class QInputMethodQueryEvent; +QT_END_NAMESPACE + +namespace QtWebEngineCore { + +class RenderWidgetHostViewQt; + +struct MultipleMouseClickHelper +{ + QPoint lastPressPosition; + Qt::MouseButton lastPressButton = Qt::NoButton; + int clickCounter = 0; + ulong lastPressTimestamp = 0; +}; + +class Q_WEBENGINECORE_PRIVATE_EXPORT RenderWidgetHostViewQtDelegateClient +{ +public: + RenderWidgetHostViewQtDelegateClient(RenderWidgetHostViewQt *rwhv); + + QSGNode *updatePaintNode(QSGNode *); + void notifyShown(); + void notifyHidden(); + void visualPropertiesChanged(); + bool forwardEvent(QEvent *); + QVariant inputMethodQuery(Qt::InputMethodQuery query); + void closePopup(); + +private: + friend class RenderWidgetHostViewQt; + + template + void handlePointerEvent(T *); + void handleMouseEvent(QMouseEvent *); + void handleKeyEvent(QKeyEvent *); + void handleTouchEvent(QTouchEvent *); +#if QT_CONFIG(tabletevent) + void handleTabletEvent(QTabletEvent *); +#endif +#if QT_CONFIG(gestures) + void handleGestureEvent(QNativeGestureEvent *); +#endif + void handleHoverEvent(QHoverEvent *); + void handleFocusEvent(QFocusEvent *); + void handleInputMethodEvent(QInputMethodEvent *); + void handleInputMethodQueryEvent(QInputMethodQueryEvent *); + + QRect viewRectInDips() const { return m_viewRectInDips; } + QRect windowRectInDips() const { return m_windowRectInDips; } + + // Mouse + void resetPreviousMousePosition() { m_previousMousePosition = QCursor::pos(); } + + // Touch + void clearPreviousTouchMotionState(); + + // IME + void selectionChanged(); + void setCursorPosition(uint pos) { m_cursorPosition = pos; } + void setSurroundingText(const QString &text) { m_surroundingText = text; } + bool isPreviousSelectionEmpty() const { return m_emptyPreviousSelection; } + + RenderWidgetHostViewQt *m_rwhv; + + // Mouse + bool m_imeHasHiddenTextCapability; + uint m_mouseButtonPressed = 0; + QPoint m_previousMousePosition; + MultipleMouseClickHelper m_clickHelper; + + // Key + std::string m_editCommand; + + // Touch + QList m_previousTouchPoints; + bool m_touchMotionStarted = false; + bool m_sendMotionActionDown = false; + + // IME + bool m_receivedEmptyImeEvent = false; + bool m_imeInProgress = false; + bool m_emptyPreviousSelection = true; + uint m_cursorPosition = 0; + int m_anchorPositionWithinSelection = -1; + int m_cursorPositionWithinSelection = -1; + QString m_surroundingText; + + // Geometry of the view in screen DIPs. + QRect m_viewRectInDips; + // Geometry of the window, including frame, in screen DIPs. + QRect m_windowRectInDips; +}; + +} // namespace QtWebEngineCore + +#endif // RENDER_WIDGET_HOST_VIEW_QT_DELEGATE_CLIENT_H diff --git a/src/core/type_conversion.cpp b/src/core/type_conversion.cpp index 403125784..de507f836 100644 --- a/src/core/type_conversion.cpp +++ b/src/core/type_conversion.cpp @@ -296,4 +296,40 @@ QList toCertificateChain(net::X509Certificate *certificate) return chain; } +Qt::InputMethodHints toQtInputMethodHints(ui::TextInputType inputType) +{ + switch (inputType) { + case ui::TEXT_INPUT_TYPE_TEXT: + return Qt::ImhPreferLowercase; + case ui::TEXT_INPUT_TYPE_SEARCH: + return Qt::ImhPreferLowercase | Qt::ImhNoAutoUppercase; + case ui::TEXT_INPUT_TYPE_PASSWORD: + return Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase + | Qt::ImhHiddenText; + case ui::TEXT_INPUT_TYPE_EMAIL: + return Qt::ImhEmailCharactersOnly; + case ui::TEXT_INPUT_TYPE_NUMBER: + return Qt::ImhFormattedNumbersOnly; + case ui::TEXT_INPUT_TYPE_TELEPHONE: + return Qt::ImhDialableCharactersOnly; + case ui::TEXT_INPUT_TYPE_URL: + return Qt::ImhUrlCharactersOnly | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase; + case ui::TEXT_INPUT_TYPE_DATE_TIME: + case ui::TEXT_INPUT_TYPE_DATE_TIME_LOCAL: + case ui::TEXT_INPUT_TYPE_DATE_TIME_FIELD: + return Qt::ImhDate | Qt::ImhTime; + case ui::TEXT_INPUT_TYPE_DATE: + case ui::TEXT_INPUT_TYPE_MONTH: + case ui::TEXT_INPUT_TYPE_WEEK: + return Qt::ImhDate; + case ui::TEXT_INPUT_TYPE_TIME: + return Qt::ImhTime; + case ui::TEXT_INPUT_TYPE_TEXT_AREA: + case ui::TEXT_INPUT_TYPE_CONTENT_EDITABLE: + return Qt::ImhMultiLine | Qt::ImhPreferLowercase; + default: + return Qt::ImhNone; + } +} + } // namespace QtWebEngineCore diff --git a/src/core/type_conversion.h b/src/core/type_conversion.h index 2275ae82e..9c193e69e 100644 --- a/src/core/type_conversion.h +++ b/src/core/type_conversion.h @@ -59,6 +59,7 @@ #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkPixelRef.h" #include "third_party/skia/include/core/SkMatrix44.h" +#include "ui/base/ime/text_input_type.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect_f.h" #include "url/gurl.h" @@ -298,6 +299,8 @@ FaviconInfo toFaviconInfo(const content::FaviconURL &); QList toCertificateChain(net::X509Certificate *certificate); +Qt::InputMethodHints toQtInputMethodHints(ui::TextInputType inputType); + } // namespace QtWebEngineCore #endif // TYPE_CONVERSION_H diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index ef482ef08..e88c80bc8 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -115,6 +115,7 @@ #include #include #include +#include #include // Can't include headers as qaccessible.h conflicts with Chromium headers. diff --git a/src/core/web_contents_view_qt.cpp b/src/core/web_contents_view_qt.cpp index 7f6bfe34f..d1c5012eb 100644 --- a/src/core/web_contents_view_qt.cpp +++ b/src/core/web_contents_view_qt.cpp @@ -73,7 +73,7 @@ void WebContentsViewQt::setFactoryClient(WebContentsAdapterClient* client) // Check if a RWHV was created before the pre-initialization. if (auto view = static_cast(m_webContents->GetRenderWidgetHostView())) { - view->setDelegate(m_factoryClient->CreateRenderWidgetHostViewQtDelegate(view)); + view->setDelegate(m_factoryClient->CreateRenderWidgetHostViewQtDelegate(view->delegateClient())); } } @@ -94,7 +94,7 @@ content::RenderWidgetHostViewBase* WebContentsViewQt::CreateViewForWidget(conten RenderWidgetHostViewQt *view = new RenderWidgetHostViewQt(render_widget_host); if (m_factoryClient) { - view->setDelegate(m_factoryClient->CreateRenderWidgetHostViewQtDelegate(view)); + view->setDelegate(m_factoryClient->CreateRenderWidgetHostViewQtDelegate(view->delegateClient())); if (m_client) view->setAdapterClient(m_client); } @@ -107,7 +107,7 @@ content::RenderWidgetHostViewBase* WebContentsViewQt::CreateViewForChildWidget(c RenderWidgetHostViewQt *view = new RenderWidgetHostViewQt(render_widget_host); Q_ASSERT(m_client); - view->setDelegate(m_client->CreateRenderWidgetHostViewQtDelegateForPopup(view)); + view->setDelegate(m_client->CreateRenderWidgetHostViewQtDelegateForPopup(view->delegateClient())); view->setAdapterClient(m_client); return view; -- cgit v1.2.3