Handle the drop event in the wasm window element

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 <Mikolaj.Boc@qt.io>
This commit is contained in:
Mikolaj Boc 2023-01-20 15:12:38 +01:00
parent c290e742c6
commit 9b64bf0874
10 changed files with 176 additions and 137 deletions

View File

@ -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

View File

@ -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<void>("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<void>("addEventListener", std::string("drop"),
val::module_property("qtDrop"), val(true));
screen()->element().set("data-qtdropcontext", // ? unique
emscripten::val(quintptr(reinterpret_cast<void *>(screen()))));
}
void QWasmCompositor::addWindow(QWasmWindow *window)

View File

@ -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 <qpa/qwindowsysteminterface.h>
#include <QMimeData>
#include <emscripten.h>
#include <emscripten/val.h>
#include <emscripten/bind.h>
#include <memory>
#include <string>
QT_BEGIN_NAMESPACE
namespace {
Qt::DropAction parseDropActions(emscripten::val event)
{
const std::string dEffect = event["dataTransfer"]["dropEffect"].as<std::string>();
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<void>("preventDefault"); // prevent browser from handling drop event
static std::shared_ptr<qstdweb::CancellationFlag> readDataCancellation = nullptr;
readDataCancellation = qstdweb::readDataTransfer(
event["dataTransfer"],
[](QByteArray fileContent) {
QImage image;
image.loadFromData(fileContent, nullptr);
return image;
},
[wasmScreen = reinterpret_cast<QWasmScreen *>(
event["target"]["data-qtdropcontext"].as<quintptr>()),
event](std::unique_ptr<QMimeData> data) {
const auto mouseDropPoint = QPoint(event["x"].as<int>(), event["y"].as<int>());
const auto button = MouseEvent::buttonFromWeb(event["button"].as<int>());
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

View File

@ -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 <QtCore/qtconfigmacros.h>
#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
void dropEvent(emscripten::val event);
QT_END_NAMESPACE
#endif // QWASMDRAG_H

View File

@ -16,10 +16,63 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
}
} // 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<int>());
mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
// 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<int>(), event["offsetY"].as<int>());
pointInPage = QPoint(event["pageX"].as<int>(), event["pageY"].as<int>());
pointInViewport = QPoint(event["clientX"].as<int>(), event["clientY"].as<int>());
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<int>();
pointerType = event["pointerType"].as<std::string>() == "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> PointerEvent::fromWeb(emscripten::val event)
{
PointerEvent ret;
const auto eventType = ([&event]() -> std::optional<EventType> {
const auto eventTypeString = event["type"].as<std::string>();
@ -38,27 +91,47 @@ std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
if (!eventType)
return std::nullopt;
ret.type = *eventType;
ret.target = event["target"];
ret.pointerType = event["pointerType"].as<std::string>() == "mouse" ?
PointerType::Mouse : PointerType::Other;
ret.mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
ret.mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
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<std::string>();
ret.localPoint = QPoint(event["offsetX"].as<int>(), event["offsetY"].as<int>());
ret.pointInPage = QPoint(event["pageX"].as<int>(), event["pageY"].as<int>());
ret.pointInViewport = QPoint(event["clientX"].as<int>(), event["clientY"].as<int>());
ret.pointerId = event["pointerId"].as<int>();
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> DragEvent::fromWeb(emscripten::val event)
{
const auto eventType = ([&event]() -> std::optional<EventType> {
const auto eventTypeString = event["type"].as<std::string>();
if (eventTypeString == "drop")
return EventType::Drop;
return std::nullopt;
})();
if (!eventType)
return std::nullopt;
return DragEvent(*eventType, event);
}
QT_END_NAMESPACE

View File

@ -18,6 +18,7 @@
QT_BEGIN_NAMESPACE
enum class EventType {
Drop,
PointerDown,
PointerMove,
PointerUp,
@ -107,13 +108,20 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
} // 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<Qt::KeyboardModifier> 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<PointerEvent> 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<DragEvent> 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

View File

@ -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<int>();

View File

@ -12,10 +12,6 @@
#include <qpa/qplatformscreen.h>
#include <qpa/qplatforminputcontext.h>
#if QT_CONFIG(draganddrop)
# include "qwasmdrag.h"
#endif
#include <QtCore/qhash.h>
#include <private/qsimpledrag_p.h>

View File

@ -84,6 +84,12 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", callback);
m_pointerLeaveCallback =
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", callback);
m_dropCallback = std::make_unique<qstdweb::EventCallback>(
m_qtWindow, "drop", [this](emscripten::val event) {
if (processDrop(*DragEvent::fromWeb(event)))
event.call<void>("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<QMimeData> 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;

View File

@ -18,13 +18,21 @@
#include <emscripten/val.h>
#include <memory>
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<qstdweb::EventCallback> m_pointerEnterCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
std::unique_ptr<qstdweb::EventCallback> 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<qstdweb::CancellationFlag> m_dropDataReadCancellationFlag;
};
QT_END_NAMESPACE