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