From 9b64bf0874b9e9323d6eadad2a8023c888f25182 Mon Sep 17 00:00:00 2001 From: Mikolaj Boc Date: Fri, 20 Jan 2023 15:12:38 +0100 Subject: [PATCH] Handle the drop event in the wasm window element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop events are now handled in the wasm window element, which allows the browser to select the drop target automatically. This also fixes the case where drop data transfer finishes reading when a window has already been closed and destroyed - the cancellation flag is now owned by window so it gets invalidated as soon as window is gone. The code has also been structured with a new DragEvent passthrough. Fixes: QTBUG-109581 Change-Id: Ie3eb7446e2181fd540517f39397e8b35f111d009 Reviewed-by: MikoĊ‚aj Boc --- src/plugins/platforms/wasm/CMakeLists.txt | 1 - .../platforms/wasm/qwasmcompositor.cpp | 8 -- src/plugins/platforms/wasm/qwasmdrag.cpp | 78 ------------ src/plugins/platforms/wasm/qwasmdrag.h | 17 --- src/plugins/platforms/wasm/qwasmevent.cpp | 113 ++++++++++++++---- src/plugins/platforms/wasm/qwasmevent.h | 43 ++++++- .../platforms/wasm/qwasmintegration.cpp | 6 - src/plugins/platforms/wasm/qwasmintegration.h | 4 - src/plugins/platforms/wasm/qwasmwindow.cpp | 30 +++++ src/plugins/platforms/wasm/qwasmwindow.h | 13 ++ 10 files changed, 176 insertions(+), 137 deletions(-) delete mode 100644 src/plugins/platforms/wasm/qwasmdrag.cpp delete mode 100644 src/plugins/platforms/wasm/qwasmdrag.h diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index 8902597001c..ade727423cd 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -37,7 +37,6 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmwindowclientarea.cpp qwasmwindowclientarea.h qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasminputcontext.cpp qwasminputcontext.h - qwasmdrag.cpp qwasmdrag.h qwasmwindowstack.cpp qwasmwindowstack.h DEFINES QT_EGL_NO_X11 diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp index 70938b323bc..0700d66a559 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.cpp +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -83,9 +83,6 @@ void QWasmCompositor::deregisterEventHandlers() emscripten_set_touchend_callback(screenElementSelector.constData(), 0, 0, NULL); emscripten_set_touchmove_callback(screenElementSelector.constData(), 0, 0, NULL); emscripten_set_touchcancel_callback(screenElementSelector.constData(), 0, 0, NULL); - - screen()->element().call("removeEventListener", std::string("drop"), - val::module_property("qtDrop"), val(true)); } void QWasmCompositor::destroy() @@ -123,11 +120,6 @@ void QWasmCompositor::initEventHandlers() &touchCallback); emscripten_set_touchcancel_callback(screenElementSelector.constData(), (void *)this, UseCapture, &touchCallback); - - screen()->element().call("addEventListener", std::string("drop"), - val::module_property("qtDrop"), val(true)); - screen()->element().set("data-qtdropcontext", // ? unique - emscripten::val(quintptr(reinterpret_cast(screen())))); } void QWasmCompositor::addWindow(QWasmWindow *window) diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp deleted file mode 100644 index 144c30e0fb4..00000000000 --- a/src/plugins/platforms/wasm/qwasmdrag.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -#include "qwasmdrag.h" - -#include "qwasmdom.h" -#include "qwasmeventtranslator.h" -#include -#include - -#include -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace { -Qt::DropAction parseDropActions(emscripten::val event) -{ - const std::string dEffect = event["dataTransfer"]["dropEffect"].as(); - - if (dEffect == "copy") - return Qt::CopyAction; - else if (dEffect == "move") - return Qt::MoveAction; - else if (dEffect == "link") - return Qt::LinkAction; - return Qt::IgnoreAction; -} -} // namespace - -void dropEvent(emscripten::val event) -{ - // someone dropped a file into the browser window - // event is dataTransfer object - // if drop event from outside browser, we do not get any mouse release, maybe mouse move - // after the drop event - event.call("preventDefault"); // prevent browser from handling drop event - - static std::shared_ptr readDataCancellation = nullptr; - readDataCancellation = qstdweb::readDataTransfer( - event["dataTransfer"], - [](QByteArray fileContent) { - QImage image; - image.loadFromData(fileContent, nullptr); - return image; - }, - [wasmScreen = reinterpret_cast( - event["target"]["data-qtdropcontext"].as()), - event](std::unique_ptr data) { - const auto mouseDropPoint = QPoint(event["x"].as(), event["y"].as()); - const auto button = MouseEvent::buttonFromWeb(event["button"].as()); - const Qt::KeyboardModifiers modifiers = KeyboardModifier::getForEvent(event); - const auto dropAction = parseDropActions(event); - auto *window = wasmScreen->topLevelAt(mouseDropPoint); - - QWindowSystemInterface::handleDrag(window, data.get(), mouseDropPoint, dropAction, - button, modifiers); - - // drag drop - QWindowSystemInterface::handleDrop(window, data.get(), mouseDropPoint, dropAction, - button, modifiers); - - // drag leave - QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, {}, - {}); - }); -} - -EMSCRIPTEN_BINDINGS(drop_module) -{ - function("qtDrop", &dropEvent); -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h deleted file mode 100644 index 624e9e3db60..00000000000 --- a/src/plugins/platforms/wasm/qwasmdrag.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -#ifndef QWASMDRAG_H -#define QWASMDRAG_H - -#include - -#include - -QT_BEGIN_NAMESPACE - -void dropEvent(emscripten::val event); - -QT_END_NAMESPACE - -#endif // QWASMDRAG_H diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp index 19730c2c685..b6568e13fa1 100644 --- a/src/plugins/platforms/wasm/qwasmevent.cpp +++ b/src/plugins/platforms/wasm/qwasmevent.cpp @@ -16,10 +16,63 @@ QFlags getForEvent( } } // namespace KeyboardModifier +Event::Event(EventType type, emscripten::val target) : type(type), target(target) { } + +Event::~Event() = default; + +Event::Event(const Event &other) = default; + +Event::Event(Event &&other) = default; + +Event &Event::operator=(const Event &other) = default; + +Event &Event::operator=(Event &&other) = default; + +MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event["target"]) +{ + mouseButton = MouseEvent::buttonFromWeb(event["button"].as()); + mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as()); + // The current button state (event.buttons) may be out of sync for some PointerDown + // events where the "down" state is very brief, for example taps on Apple trackpads. + // Qt expects that the current button state is in sync with the event, so we sync + // it up here. + if (type == EventType::PointerDown) + mouseButtons |= mouseButton; + localPoint = QPoint(event["offsetX"].as(), event["offsetY"].as()); + pointInPage = QPoint(event["pageX"].as(), event["pageY"].as()); + pointInViewport = QPoint(event["clientX"].as(), event["clientY"].as()); + modifiers = KeyboardModifier::getForEvent(event); +} + +MouseEvent::~MouseEvent() = default; + +MouseEvent::MouseEvent(const MouseEvent &other) = default; + +MouseEvent::MouseEvent(MouseEvent &&other) = default; + +MouseEvent &MouseEvent::operator=(const MouseEvent &other) = default; + +MouseEvent &MouseEvent::operator=(MouseEvent &&other) = default; + +PointerEvent::PointerEvent(EventType type, emscripten::val event) : MouseEvent(type, event) +{ + pointerId = event["pointerId"].as(); + pointerType = event["pointerType"].as() == "mouse" ? PointerType::Mouse + : PointerType::Other; +} + +PointerEvent::~PointerEvent() = default; + +PointerEvent::PointerEvent(const PointerEvent &other) = default; + +PointerEvent::PointerEvent(PointerEvent &&other) = default; + +PointerEvent &PointerEvent::operator=(const PointerEvent &other) = default; + +PointerEvent &PointerEvent::operator=(PointerEvent &&other) = default; + std::optional PointerEvent::fromWeb(emscripten::val event) { - PointerEvent ret; - const auto eventType = ([&event]() -> std::optional { const auto eventTypeString = event["type"].as(); @@ -38,27 +91,47 @@ std::optional PointerEvent::fromWeb(emscripten::val event) if (!eventType) return std::nullopt; - ret.type = *eventType; - ret.target = event["target"]; - ret.pointerType = event["pointerType"].as() == "mouse" ? - PointerType::Mouse : PointerType::Other; - ret.mouseButton = MouseEvent::buttonFromWeb(event["button"].as()); - ret.mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as()); + return PointerEvent(*eventType, event); +} - // The current button state (event.buttons) may be out of sync for some PointerDown - // events where the "down" state is very brief, for example taps on Apple trackpads. - // Qt expects that the current button state is in sync with the event, so we sync - // it up here. - if (*eventType == EventType::PointerDown) - ret.mouseButtons |= ret.mouseButton; +DragEvent::DragEvent(EventType type, emscripten::val event) + : MouseEvent(type, event), dataTransfer(event["dataTransfer"]) +{ + dropAction = ([event]() { + const std::string effect = event["dataTransfer"]["dropEffect"].as(); - ret.localPoint = QPoint(event["offsetX"].as(), event["offsetY"].as()); - ret.pointInPage = QPoint(event["pageX"].as(), event["pageY"].as()); - ret.pointInViewport = QPoint(event["clientX"].as(), event["clientY"].as()); - ret.pointerId = event["pointerId"].as(); - ret.modifiers = KeyboardModifier::getForEvent(event); + if (effect == "copy") + return Qt::CopyAction; + else if (effect == "move") + return Qt::MoveAction; + else if (effect == "link") + return Qt::LinkAction; + return Qt::IgnoreAction; + })(); +} - return ret; +DragEvent::~DragEvent() = default; + +DragEvent::DragEvent(const DragEvent &other) = default; + +DragEvent::DragEvent(DragEvent &&other) = default; + +DragEvent &DragEvent::operator=(const DragEvent &other) = default; + +DragEvent &DragEvent::operator=(DragEvent &&other) = default; + +std::optional DragEvent::fromWeb(emscripten::val event) +{ + const auto eventType = ([&event]() -> std::optional { + const auto eventTypeString = event["type"].as(); + + if (eventTypeString == "drop") + return EventType::Drop; + return std::nullopt; + })(); + if (!eventType) + return std::nullopt; + return DragEvent(*eventType, event); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h index f9d6411d60d..215d06bae51 100644 --- a/src/plugins/platforms/wasm/qwasmevent.h +++ b/src/plugins/platforms/wasm/qwasmevent.h @@ -18,6 +18,7 @@ QT_BEGIN_NAMESPACE enum class EventType { + Drop, PointerDown, PointerMove, PointerUp, @@ -107,13 +108,20 @@ QFlags getForEvent( } // namespace KeyboardModifier -struct Q_CORE_EXPORT Event +struct Event { EventType type; emscripten::val target = emscripten::val::undefined(); + + Event(EventType type, emscripten::val target); + ~Event(); + Event(const Event &other); + Event(Event &&other); + Event &operator=(const Event &other); + Event &operator=(Event &&other); }; -struct Q_CORE_EXPORT MouseEvent : public Event +struct MouseEvent : public Event { QPoint localPoint; QPoint pointInPage; @@ -122,6 +130,13 @@ struct Q_CORE_EXPORT MouseEvent : public Event Qt::MouseButtons mouseButtons; QFlags modifiers; + MouseEvent(EventType type, emscripten::val webEvent); + ~MouseEvent(); + MouseEvent(const MouseEvent &other); + MouseEvent(MouseEvent &&other); + MouseEvent &operator=(const MouseEvent &other); + MouseEvent &operator=(MouseEvent &&other); + static constexpr Qt::MouseButton buttonFromWeb(int webButton) { switch (webButton) { case 0: @@ -158,14 +173,36 @@ struct Q_CORE_EXPORT MouseEvent : public Event } }; -struct Q_CORE_EXPORT PointerEvent : public MouseEvent +struct PointerEvent : public MouseEvent { static std::optional fromWeb(emscripten::val webEvent); + PointerEvent(EventType type, emscripten::val webEvent); + ~PointerEvent(); + PointerEvent(const PointerEvent &other); + PointerEvent(PointerEvent &&other); + PointerEvent &operator=(const PointerEvent &other); + PointerEvent &operator=(PointerEvent &&other); + PointerType pointerType; int pointerId; }; +struct DragEvent : public MouseEvent +{ + static std::optional fromWeb(emscripten::val webEvent); + + DragEvent(EventType type, emscripten::val webEvent); + ~DragEvent(); + DragEvent(const DragEvent &other); + DragEvent(DragEvent &&other); + DragEvent &operator=(const DragEvent &other); + DragEvent &operator=(DragEvent &&other); + + Qt::DropAction dropAction; + emscripten::val dataTransfer; +}; + QT_END_NAMESPACE #endif // QWASMEVENT_H diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp index 090218a118e..b92e4566686 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.cpp +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -80,12 +80,6 @@ QWasmIntegration::QWasmIntegration() m_clipboard(new QWasmClipboard), m_accessibility(new QWasmAccessibility) { - // Temporary measure to make dropEvent appear in the library. EMSCRIPTEN_KEEPALIVE does not - // work, nor does a Q_CONSTRUCTOR_FUNCTION in qwasmdrag.cpp. - volatile bool foolEmcc = false; - if (foolEmcc) - dropEvent(emscripten::val::undefined()); - s_instance = this; touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as(); diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h index 8468c4c9e11..decf25009e6 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.h +++ b/src/plugins/platforms/wasm/qwasmintegration.h @@ -12,10 +12,6 @@ #include #include -#if QT_CONFIG(draganddrop) -# include "qwasmdrag.h" -#endif - #include #include diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index a8f577cfeae..3735c7dff4f 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -84,6 +84,12 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt std::make_unique(m_qtWindow, "pointerenter", callback); m_pointerLeaveCallback = std::make_unique(m_qtWindow, "pointerleave", callback); + + m_dropCallback = std::make_unique( + m_qtWindow, "drop", [this](emscripten::val event) { + if (processDrop(*DragEvent::fromWeb(event))) + event.call("preventDefault"); + }); } QWasmWindow::~QWasmWindow() @@ -418,6 +424,30 @@ bool QWasmWindow::processPointer(const PointerEvent &event) return false; } +bool QWasmWindow::processDrop(const DragEvent &event) +{ + m_dropDataReadCancellationFlag = qstdweb::readDataTransfer( + event.dataTransfer, + [](QByteArray fileContent) { + QImage image; + image.loadFromData(fileContent, nullptr); + return image; + }, + [this, event](std::unique_ptr data) { + QWindowSystemInterface::handleDrag(window(), data.get(), event.pointInPage, + event.dropAction, event.mouseButton, + event.modifiers); + + QWindowSystemInterface::handleDrop(window(), data.get(), event.pointInPage, + event.dropAction, event.mouseButton, + event.modifiers); + + QWindowSystemInterface::handleDrag(window(), nullptr, QPoint(), Qt::IgnoreAction, + {}, {}); + }); + return true; +} + QRect QWasmWindow::normalGeometry() const { return m_normalGeometry; diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 5651495c741..906a7a1daf3 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -18,13 +18,21 @@ #include +#include + QT_BEGIN_NAMESPACE +namespace qstdweb { +struct CancellationFlag; +} + namespace qstdweb { class EventCallback; } class ClientArea; +struct DragEvent; +struct PointerEvent; class QWasmWindow final : public QPlatformWindow { @@ -84,6 +92,7 @@ private: void applyWindowState(); bool processPointer(const PointerEvent &event); + bool processDrop(const DragEvent &event); QWindow *m_window = nullptr; QWasmCompositor *m_compositor = nullptr; @@ -105,6 +114,8 @@ private: std::unique_ptr m_pointerEnterCallback; std::unique_ptr m_pointerMoveCallback; + std::unique_ptr m_dropCallback; + Qt::WindowStates m_state = Qt::WindowNoState; Qt::WindowStates m_previousWindowState = Qt::WindowNoState; @@ -120,6 +131,8 @@ private: friend class QWasmCompositor; friend class QWasmEventTranslator; bool windowIsPopupType(Qt::WindowFlags flags) const; + + std::shared_ptr m_dropDataReadCancellationFlag; }; QT_END_NAMESPACE