Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmdom.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qwasmdom.h"
6
7#include <QtCore/qdebug.h>
8#include <QtCore/qdir.h>
9#include <QtCore/qfile.h>
10#include <QtCore/qpoint.h>
11#include <QtCore/qrect.h>
12#include <QtGui/qimage.h>
13#include <private/qstdweb_p.h>
14#include <private/qwasmlocalfileengine_p.h>
15#include <QtCore/qurl.h>
16#include <QtCore/QBuffer>
17
18#include <utility>
19#include <emscripten/wire.h>
20
21QT_BEGIN_NAMESPACE
22
23namespace dom {
24namespace {
25std::string dropActionToDropEffect(Qt::DropAction action)
26{
27 switch (action) {
28 case Qt::DropAction::CopyAction:
29 return "copy";
30 case Qt::DropAction::IgnoreAction:
31 return "none";
32 case Qt::DropAction::LinkAction:
33 return "link";
34 case Qt::DropAction::MoveAction:
35 case Qt::DropAction::TargetMoveAction:
36 return "move";
37 case Qt::DropAction::ActionMask:
38 Q_ASSERT(false);
39 return "";
40 }
41}
42} // namespace
43
44DataTransfer::DataTransfer(emscripten::val webDataTransfer)
45 : webDataTransfer(webDataTransfer) {
46}
47
48DataTransfer::~DataTransfer() = default;
49
50DataTransfer::DataTransfer(const DataTransfer &other) = default;
51
52DataTransfer::DataTransfer(DataTransfer &&other) = default;
53
54DataTransfer &DataTransfer::operator=(const DataTransfer &other) = default;
55
57
58void DataTransfer::setDragImage(emscripten::val element, const QPoint &hotspot)
59{
60 webDataTransfer.call<void>("setDragImage", element, emscripten::val(hotspot.x()),
61 emscripten::val(hotspot.y()));
62}
63
64void DataTransfer::setData(std::string format, std::string data)
65{
66 webDataTransfer.call<void>("setData", emscripten::val(std::move(format)),
67 emscripten::val(std::move(data)));
68}
69
70void DataTransfer::setDropAction(Qt::DropAction action)
71{
72 webDataTransfer.set("dropEffect", emscripten::val(dropActionToDropEffect(action)));
73}
74
75void DataTransfer::setDataFromMimeData(const QMimeData &mimeData)
76{
77 for (const auto &format : mimeData.formats()) {
78 if (format.startsWith("text/")) {
79 auto data = mimeData.data(format);
80 setData(format.toStdString(), QString::fromLocal8Bit(data).toStdString());
81 } else if (format == "application/x-qt-image") {
82 auto image = qvariant_cast<QImage>(mimeData.imageData());
83 QByteArray byteArray;
84 QBuffer buffer(&byteArray);
85 buffer.open(QIODevice::WriteOnly);
86 image.save(&buffer, "PNG");
87 buffer.close();
88
89 setData(format.toStdString(), "QB64" + QString::fromLocal8Bit(byteArray.toBase64()).toStdString());
90 } else {
91 auto data = mimeData.data(format);
92 auto encoded = "QB64" + QString::fromLocal8Bit(data.toBase64()).toStdString();
93 setData(format.toStdString(), std::move(encoded));
94 }
95 }
96}
97
98// Converts a DataTransfer instance to a QMimeData instance. Invokes the
99// given callback when the conversion is complete. The callback takes ownership
100// of the QMimeData.
101void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
102{
103 enum class ItemKind {
104 File,
105 String,
106 };
107
108 class MimeContext {
109
110 public:
111 MimeContext(int itemCount, std::function<void(QMimeData *)> callback)
112 :m_remainingItemCount(itemCount), m_callback(callback)
113 {
114
115 }
116
117 void deref() {
118 if (--m_remainingItemCount > 0)
119 return;
120
121 QList<QUrl> allUrls;
122 allUrls.append(mimeData->urls());
123 allUrls.append(fileUrls);
124 mimeData->setUrls(allUrls);
125
126 m_callback(mimeData);
127
128 // Delete temporary files; we expect that the user callback reads/copies
129 // file content before returning.// Fixme: tie file lifetime to lifetime of the QMimeData?
130 // Note: QWasmFileEngine files (weblocalfile://) are managed by QWasmFileEngine
131 // and are not deleted here
132 for (QUrl fileUrl: fileUrls) {
133 if (!QWasmFileEngineHandler::isWasmFileName(fileUrl.toString()))
134 QFile(fileUrl.toLocalFile()).remove();
135 }
136
137 delete this;
138 }
139
140 QMimeData *mimeData = new QMimeData();
141 QList<QUrl> fileUrls;
142
143 private:
144 int m_remainingItemCount;
145 std::function<void(QMimeData *)> m_callback;
146 };
147
148 const auto items = webDataTransfer["items"];
149 const int itemCount = items["length"].as<int>();
150 const int fileCount = webDataTransfer["files"]["length"].as<int>();
151 MimeContext *mimeContext = new MimeContext(itemCount, callback);
152
153 for (int i = 0; i < itemCount; ++i) {
154 const auto item = items[i];
155 const auto itemKind =
156 item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
157 const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
158
159 switch (itemKind) {
160 case ItemKind::File: {
161 qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));
162
163 // Add a file access url for the local file. If asyncify is available,
164 // add a QWasmFileEngine managed url. Else fall back to placing a copy
165 // of the file at /tmp on Emsripten's in-memory file system.
166 if (qstdweb::haveAsyncify()) {
167 const auto fileName = QWasmFileEngineHandler::addFile(webfile); // because QUrl::toLocalFile does not work
168 QUrl fileUrl(fileName);
169 mimeContext->fileUrls.append(fileUrl);
170
171 QString mimeFormat = QString::fromStdString(webfile.type());
172
173 if (fileCount == 1 && mimeFormat.contains("image/")) {
174 QImage image;
175 if (image.load(fileName))
176 mimeContext->mimeData->setImageData(image);
177 else
178 qWarning() << "Failed to load" << fileName;
179 }
180 mimeContext->deref();
181 } else {
182 // Limit in-memory file size to 1 GB
183 if (webfile.size() > 1e+9) {
184 qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
185 mimeContext->deref();
186 continue;
187 }
188
189 // Read file content
190 QByteArray fileContent(webfile.size(), Qt::Uninitialized);
191 webfile.stream(fileContent.data(), [=]() {
192 QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
193 qtTmpDir.mkpath(qtTmpDir.path());
194
195 QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
196 mimeContext->fileUrls.append(fileUrl);
197
198 QFile file(fileUrl.toLocalFile());
199 if (!file.open(QFile::WriteOnly)) {
200 qWarning() << "File was not opened";
201 mimeContext->deref();
202 return;
203 }
204 if (file.write(fileContent) < 0)
205 qWarning() << "Write failed";
206 file.close();
207
208 // If we get a single file, and that file is an image, then
209 // try to decode the image data. This handles the case where
210 // image data (i.e. not an image file) is pasted. The browsers
211 // will then create a fake "image.png" file which has the image
212 // data. As a side effect Qt will also decode the image for
213 // single-image-file drops, since there is no way to differentiate
214 // the fake "image.png" from a real one.
215 QString mimeFormat = QString::fromStdString(webfile.type());
216 if (fileCount == 1 && mimeFormat.contains("image/")) {
217 QImage image;
218 if (image.loadFromData(fileContent))
219 mimeContext->mimeData->setImageData(image);
220 }
221 mimeContext->deref();
222 });
223 }
224 break;
225 }
226 case ItemKind::String:
227 if (itemMimeType.contains("STRING", Qt::CaseSensitive)
228 || itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
229 mimeContext->deref();
230 break;
231 }
232 QString a;
233 QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
234 "getData", emscripten::val(itemMimeType.toStdString())));
235
236 if (!data.isEmpty()) {
237 if (itemMimeType == "text/html")
238 mimeContext->mimeData->setHtml(data);
239 else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
240 mimeContext->mimeData->setText(data); // the type can be empty
241 else if (itemMimeType.isEmpty() || itemMimeType == "text/uri-list") {
242 QList<QUrl> urls;
243 urls.append(data);
244 mimeContext->mimeData->setUrls(urls);
245 } else if (itemMimeType == "application/x-qt-image") {
246 if (data.startsWith("QB64")) {
247 data.remove(0, 4);
248 auto ba = QByteArray::fromBase64(QByteArray::fromStdString(data.toStdString()));
249 QImage image;
250 image.loadFromData(ba);
251 mimeContext->mimeData->setImageData(image);
252 }
253 } else {
254 // TODO improve encoding
255 if (data.startsWith("QB64")) {
256 data.remove(0, 4);
257 mimeContext->mimeData->setData(itemMimeType,
258 QByteArray::fromBase64(QByteArray::fromStdString(
259 data.toStdString())));
260 } else {
261 mimeContext->mimeData->setData(itemMimeType, data.toLocal8Bit());
262 }
263 }
264 }
265 mimeContext->deref();
266 break;
267 }
268 } // for items
269}
270
271// Creates a preview QMimeData which contains the MIME types but with empty
272// data. This is useful when handling native dragOver events where the
273// native DataTransfer API does not provide drag data (the data becomes
274// available on the drop event).
276{
277 auto data = new QMimeData();
278
279 QList<QUrl> uriList;
280 for (int i = 0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {
281 const auto item = webDataTransfer["items"][i];
282 if (item["kind"].as<std::string>() == "file") {
283 uriList.append(QUrl());
284 } else {
285 const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
286 data->setData(itemMimeType, QByteArray());
287 }
288 }
289 if (!uriList.isEmpty())
290 data->setUrls(uriList);
291
292 return data;
293}
294
295void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
296{
297 if (flag) {
298 element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
299 return;
300 }
301
302 element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
303}
304
305QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point)
306{
307 const auto sourceBoundingRect =
308 QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));
309 const auto targetBoundingRect =
310 QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));
311
312 const auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
313 return point + offset;
314}
315
316void drawImageToWebImageDataArray(const QImage &sourceImage, emscripten::val destinationImageData,
317 const QRect &sourceRect)
318{
319 Q_ASSERT_X(destinationImageData["constructor"]["name"].as<std::string>() == "ImageData",
320 Q_FUNC_INFO, "The destination should be an ImageData instance");
321
322 constexpr int BytesPerColor = 4;
323 if (sourceRect.width() == sourceImage.width()) {
324 // Copy a contiguous chunk of memory
325 // ...............
326 // OOOOOOOOOOOOOOO
327 // OOOOOOOOOOOOOOO -> image data
328 // OOOOOOOOOOOOOOO
329 // ...............
330 auto imageMemory = emscripten::typed_memory_view(sourceRect.width() * sourceRect.height()
331 * BytesPerColor,
332 sourceImage.constScanLine(sourceRect.y()));
333 destinationImageData["data"].call<void>(
334 "set", imageMemory, sourceRect.y() * sourceImage.width() * BytesPerColor);
335 } else {
336 // Go through the scanlines manually to set the individual lines in bulk. This is
337 // marginally less performant than the above.
338 // ...............
339 // ...OOOOOOOOO... r = 0 -> image data
340 // ...OOOOOOOOO... r = 1 -> image data
341 // ...OOOOOOOOO... r = 2 -> image data
342 // ...............
343 for (int row = 0; row < sourceRect.height(); ++row) {
344 auto scanlineMemory =
345 emscripten::typed_memory_view(sourceRect.width() * BytesPerColor,
346 sourceImage.constScanLine(row + sourceRect.y())
347 + BytesPerColor * sourceRect.x());
348 destinationImageData["data"].call<void>("set", scanlineMemory,
349 (sourceRect.y() + row) * sourceImage.width()
350 * BytesPerColor
351 + sourceRect.x() * BytesPerColor);
352 }
353 }
354}
355
356} // namespace dom
357
358QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:30
Definition qwasmdom.h:30
DataTransfer(DataTransfer &&other)
void toMimeDataWithFile(std::function< void(QMimeData *)> callback)
Definition qwasmdom.cpp:101
DataTransfer(const DataTransfer &other)
void setDataFromMimeData(const QMimeData &mimeData)
Definition qwasmdom.cpp:75
QMimeData * toMimeDataPreview()
Definition qwasmdom.cpp:275
DataTransfer & operator=(const DataTransfer &other)
DataTransfer & operator=(DataTransfer &&other)
void setData(std::string format, std::string data)
Definition qwasmdom.cpp:64
void setDropAction(Qt::DropAction dropAction)
Definition qwasmdom.cpp:70