Resolve the ownership problems in qClipboardPasteTo

A deleted mMimeData instance might be referenced if more that one file
item is present in the clipboadData. This fix uses a continuation
mechanism to only run the actual qWasmClipboardPaste when all the
data has been collected. The ownership of QMimeData is then transferred
to the global QClipboardData q_clipboardData.

Fixes: QTBUG-108841
Change-Id: I16def48d70ebbffc68462ed74ccd9ee8ee8053de
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Mikolaj Boc 2022-11-25 13:17:16 +01:00
parent effd3b4d83
commit 4673f00277

View File

@ -80,67 +80,81 @@ static void qWasmClipboardPaste(QMimeData *mData)
static void qClipboardPasteTo(val dataTransfer) static void qClipboardPasteTo(val dataTransfer)
{ {
val clipboardData = dataTransfer["clipboardData"]; enum class ItemKind {
val types = clipboardData["types"]; File,
int typesCount = types["length"].as<int>(); String,
std::string stdMimeFormat; };
QMimeData *mMimeData = new QMimeData;
for (int i = 0; i < typesCount; i++) {
stdMimeFormat = types[i].as<std::string>();
QString mimeFormat = QString::fromStdString(stdMimeFormat);
if (mimeFormat.contains("STRING", Qt::CaseSensitive) || mimeFormat.contains("TEXT", Qt::CaseSensitive))
continue;
if (mimeFormat.contains("text")) { struct Data
// also "text/plain;charset=utf-8" {
// "UTF8_STRING" "MULTIPLE" std::unique_ptr<QMimeData> data;
val mimeData = clipboardData.call<val>("getData", val(stdMimeFormat)); // as DataTransfer int fileCount = 0;
int doneCount = 0;
};
const QString qstr = QWasmString::toQString(mimeData); auto sharedData = std::make_shared<Data>();
sharedData->data = std::make_unique<QMimeData>();
if (qstr.length() > 0) { auto continuation = [sharedData]() {
if (mimeFormat.contains("text/html")) { Q_ASSERT(sharedData->doneCount <= sharedData->fileCount);
mMimeData->setHtml(qstr); if (sharedData->doneCount < sharedData->fileCount)
} else if (mimeFormat.isEmpty() || mimeFormat.contains("text/plain")) { return;
mMimeData->setText(qstr); // the type can be empty
} else { if (!sharedData->data->formats().isEmpty())
mMimeData->setData(mimeFormat, qstr.toLocal8Bit());} 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;
} }
} else { const QString data = QWasmString::toQString(
val items = clipboardData["items"]; clipboardData.call<val>("getData", val(itemMimeType.toStdString())));
int itemsCount = items["length"].as<int>(); if (!data.isEmpty()) {
// handle data if (itemMimeType == "text/html")
for (int i = 0; i < itemsCount; i++) { sharedData->data->setHtml(data);
val item = items[i]; else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
val clipboardFile = item.call<emscripten::val>("getAsFile"); // string kind is handled above sharedData->data->setText(data); // the type can be empty
if (clipboardFile.isUndefined() || item["kind"].as<std::string>() == "string" ) { else
continue; sharedData->data->setData(itemMimeType, data.toLocal8Bit());
} }
qstdweb::File file(clipboardFile); break;
mimeFormat = QString::fromStdString(file.type());
QByteArray fileContent;
fileContent.resize(file.size());
file.stream(fileContent.data(), [=]() {
if (!fileContent.isEmpty()) {
if (mimeFormat.contains("image")) {
QImage image;
image.loadFromData(fileContent, nullptr);
mMimeData->setImageData(image);
} else {
mMimeData->setData(mimeFormat,fileContent.data());
}
qWasmClipboardPaste(mMimeData);
}
});
} // next item
} }
} }
if (!mMimeData->formats().isEmpty()) continuation();
qWasmClipboardPaste(mMimeData);
} }
EMSCRIPTEN_BINDINGS(qtClipboardModule) { EMSCRIPTEN_BINDINGS(qtClipboardModule) {