Skip to content

Commit

Permalink
Streamline reading of js DataTransfer object
Browse files Browse the repository at this point in the history
qwasmclipboard.cpp and qwasmdrag.cpp had the same logic that read
the js DataTransfer object implemented twice with small differences.
Use a single implementation in both.

This also introduces a clearer memory ownership model in the reader
code, and fixes a potential race condition by introducing a cancellation
flag.

Removed the useless QWasmDrag type which was in essence a SimpleDrag
and made the m_drag in QWasmIntegration a smart pointer.

Fixes: QTBUG-109626
Pick-to: 6.5
Change-Id: I5b76dd3b70ab2e5a8364d9a136c970ee8d4fae9c
Reviewed-by: Morten Johan Sørvig <[email protected]>
  • Loading branch information
mboc-qt committed Jan 18, 2023
1 parent 2e8b754 commit 16bf899
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 296 deletions.
114 changes: 114 additions & 0 deletions src/corelib/platform/wasm/qstdweb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <QtCore/qcoreapplication.h>
#include <QtCore/qfile.h>
#include <QtCore/qmimedata.h>

#include <emscripten/bind.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
Expand Down Expand Up @@ -368,6 +370,111 @@ bool jsHaveAsyncify() { return false; }

#endif

struct DataTransferReader
{
public:
using DoneCallback = std::function<void(std::unique_ptr<QMimeData>)>;

static std::shared_ptr<CancellationFlag> read(emscripten::val webDataTransfer,
std::function<QVariant(QByteArray)> imageReader,
DoneCallback onCompleted)
{
auto cancellationFlag = std::make_shared<CancellationFlag>();
(new DataTransferReader(std::move(onCompleted), std::move(imageReader), cancellationFlag))
->read(webDataTransfer);
return cancellationFlag;
}

~DataTransferReader() = default;

private:
DataTransferReader(DoneCallback onCompleted, std::function<QVariant(QByteArray)> imageReader,
std::shared_ptr<CancellationFlag> cancellationFlag)
: mimeData(std::make_unique<QMimeData>()),
imageReader(std::move(imageReader)),
onCompleted(std::move(onCompleted)),
cancellationFlag(cancellationFlag)
{
}

void read(emscripten::val webDataTransfer)
{
enum class ItemKind {
File,
String,
};

const auto items = webDataTransfer["items"];
for (int i = 0; i < items["length"].as<int>(); ++i) {
const auto item = items[i];
const auto itemKind =
item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());

switch (itemKind) {
case ItemKind::File: {
++fileCount;

qstdweb::File file(item.call<emscripten::val>("getAsFile"));

QByteArray fileContent(file.size(), Qt::Uninitialized);
file.stream(fileContent.data(), [this, itemMimeType, fileContent]() {
if (!fileContent.isEmpty()) {
if (itemMimeType.startsWith("image/")) {
mimeData->setImageData(imageReader(fileContent));
} else {
mimeData->setData(itemMimeType, fileContent.data());
}
}
++doneCount;
onFileRead();
});
break;
}
case ItemKind::String:
if (itemMimeType.contains("STRING", Qt::CaseSensitive)
|| itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
break;
}
QString a;
const QString data = QString::fromJsString(webDataTransfer.call<emscripten::val>(
"getData", emscripten::val(itemMimeType.toStdString())));

if (!data.isEmpty()) {
if (itemMimeType == "text/html")
mimeData->setHtml(data);
else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
mimeData->setText(data); // the type can be empty
else
mimeData->setData(itemMimeType, data.toLocal8Bit());
}
break;
}
}

onFileRead();
}

void onFileRead()
{
Q_ASSERT(doneCount <= fileCount);
if (doneCount < fileCount)
return;

std::unique_ptr<DataTransferReader> deleteThisLater(this);
if (!cancellationFlag.expired())
onCompleted(std::move(mimeData));
}

int fileCount = 0;
int doneCount = 0;
std::unique_ptr<QMimeData> mimeData;
std::function<QVariant(QByteArray)> imageReader;
DoneCallback onCompleted;

std::weak_ptr<CancellationFlag> cancellationFlag;
};

} // namespace

ArrayBuffer::ArrayBuffer(uint32_t size)
Expand Down Expand Up @@ -738,6 +845,13 @@ bool haveAsyncify()
return HaveAsyncify;
}

std::shared_ptr<CancellationFlag>
readDataTransfer(emscripten::val webDataTransfer, std::function<QVariant(QByteArray)> imageReader,
std::function<void(std::unique_ptr<QMimeData>)> onDone)
{
return DataTransferReader::read(webDataTransfer, std::move(imageReader), std::move(onDone));
}

} // namespace qstdweb

QT_END_NAMESPACE
21 changes: 18 additions & 3 deletions src/corelib/platform/wasm/qstdweb_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@
//

#include <private/qglobal_p.h>
#include <QtCore/qglobal.h>
#include "QtCore/qhash.h"

#include <emscripten/val.h>

#include <cstdint>
#include <functional>
#include "initializer_list"
#include <QtCore/qglobal.h>
#include "QtCore/qhash.h"
#include <initializer_list>
#include <memory>
#include <string>
#include <utility>

QT_BEGIN_NAMESPACE

class QMimeData;

namespace qstdweb {
extern const char makeContextfulPromiseFunctionName[];

Expand Down Expand Up @@ -195,6 +202,14 @@ namespace qstdweb {
}

bool haveAsyncify();

struct CancellationFlag
{
};

std::shared_ptr<CancellationFlag>
readDataTransfer(emscripten::val webObject, std::function<QVariant(QByteArray)> imageReader,
std::function<void(std::unique_ptr<QMimeData>)> onDone);
}

QT_END_NAMESPACE
Expand Down
116 changes: 25 additions & 91 deletions src/plugins/platforms/wasm/qwasmclipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qwasmclipboard.h"
#include "qwasmdom.h"
#include "qwasmwindow.h"

#include <private/qstdweb_p.h>

#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>

#include <QCoreApplication>
#include <qpa/qwindowsysteminterface.h>
#include <QBuffer>
#include <QString>

#include <emscripten/val.h>

QT_BEGIN_NAMESPACE
using namespace emscripten;

Expand Down Expand Up @@ -67,93 +65,29 @@ static void qClipboardCopyTo(val event)
commonCopyEvent(event);
}

static void qWasmClipboardPaste(QMimeData *mData)
{
// Persist clipboard data so that the app can read it when handling the CTRL+V
QWasmIntegration::get()->clipboard()->
QPlatformClipboard::setMimeData(mData, QClipboard::Clipboard);

QWindowSystemInterface::handleKeyEvent(
0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V");
}

static void qClipboardPasteTo(val dataTransfer)
static void qClipboardPasteTo(val event)
{
enum class ItemKind {
File,
String,
};

struct Data
{
std::unique_ptr<QMimeData> data;
int fileCount = 0;
int doneCount = 0;
};

auto sharedData = std::make_shared<Data>();
sharedData->data = std::make_unique<QMimeData>();

auto continuation = [sharedData]() {
Q_ASSERT(sharedData->doneCount <= sharedData->fileCount);
if (sharedData->doneCount < sharedData->fileCount)
return;

if (!sharedData->data->formats().isEmpty())
qWasmClipboardPaste(sharedData->data.release());
};

const val clipboardData = dataTransfer["clipboardData"];
const val items = clipboardData["items"];
for (int i = 0; i < items["length"].as<int>(); ++i) {
const val item = items[i];
const auto itemKind =
item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());

switch (itemKind) {
case ItemKind::File: {
++sharedData->fileCount;

qstdweb::File file(item.call<emscripten::val>("getAsFile"));

QByteArray fileContent(file.size(), Qt::Uninitialized);
file.stream(fileContent.data(),
[continuation, itemMimeType, fileContent, sharedData]() {
if (!fileContent.isEmpty()) {
if (itemMimeType.startsWith("image/")) {
QImage image;
image.loadFromData(fileContent, nullptr);
sharedData->data->setImageData(image);
} else {
sharedData->data->setData(itemMimeType, fileContent.data());
}
}
++sharedData->doneCount;
continuation();
});
break;
}
case ItemKind::String:
if (itemMimeType.contains("STRING", Qt::CaseSensitive)
|| itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
break;
}
const QString data = QString::fromJsString(
clipboardData.call<val>("getData", val(itemMimeType.toStdString())));

if (!data.isEmpty()) {
if (itemMimeType == "text/html")
sharedData->data->setHtml(data);
else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
sharedData->data->setText(data); // the type can be empty
else
sharedData->data->setData(itemMimeType, data.toLocal8Bit());
}
break;
}
}
continuation();
event.call<void>("preventDefault"); // prevent browser from handling drop event

static std::shared_ptr<qstdweb::CancellationFlag> readDataCancellation = nullptr;
readDataCancellation = qstdweb::readDataTransfer(
event["clipboardData"],
[](QByteArray fileContent) {
QImage image;
image.loadFromData(fileContent, nullptr);
return image;
},
[event](std::unique_ptr<QMimeData> data) {
if (data->formats().isEmpty())
return;

// Persist clipboard data so that the app can read it when handling the CTRL+V
QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(
data.release(), QClipboard::Clipboard);

QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
Qt::ControlModifier, "V");
});
}

EMSCRIPTEN_BINDINGS(qtClipboardModule) {
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/platforms/wasm/qwasmdom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@

#include "qwasmdom.h"

#include <QMimeData>
#include <QtCore/qpoint.h>
#include <QtCore/qrect.h>
#include <QtGui/qimage.h>
#include <private/qstdweb_p.h>

#include <utility>

QT_BEGIN_NAMESPACE

namespace dom {

void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
{
if (flag) {
Expand All @@ -31,6 +35,7 @@ QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &po
auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
return (point + offset).toPoint();
}

} // namespace dom

QT_END_NAMESPACE
2 changes: 2 additions & 0 deletions src/plugins/platforms/wasm/qwasmdom.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include <emscripten/val.h>

#include <functional>
#include <memory>
#include <string>

QT_BEGIN_NAMESPACE
Expand Down
Loading

0 comments on commit 16bf899

Please sign in to comment.