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
qstdweb.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "qstdweb_p.h"
8
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qfile.h>
11#include <QtCore/qmimedata.h>
12
13#include <emscripten/emscripten.h>
14#include <emscripten/bind.h>
15#include <emscripten/val.h>
16#include <emscripten/html5.h>
17#include <emscripten/threading.h>
18
19#include <cstdint>
20#include <iostream>
21
22#include <unordered_map>
23
24QT_BEGIN_NAMESPACE
25
26using namespace Qt::StringLiterals;
27using emscripten::val;
28
29namespace qstdweb {
30
32{
33 // Using this adds a reference on JSEvents and specialHTMLTargets which are always exported.
34 // This hack is needed as it is currently impossible to specify a dollar sign in
35 // target_link_options. The following is impossible:
36 // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$JSEvents
37 // TODO(mikolajboc): QTBUG-108444, review this when cmake gets fixed.
38 // Volatile is to make this unoptimizable, so that the function is referenced, but is not
39 // called at runtime.
40 volatile bool doIt = false;
41 if (doIt)
42 emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
43}
44
45Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
46typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
47namespace {
48// Reads file in chunks in order to avoid holding two copies in memory at the same time
49struct ChunkedFileReader
50{
51public:
52 static void read(File file, char *buffer, uint32_t offset, uint32_t end,
53 std::function<void()> onCompleted)
54 {
55 (new ChunkedFileReader(end, std::move(onCompleted), std::move(file)))
56 ->readNextChunk(offset, buffer);
57 }
58
59private:
60 ChunkedFileReader(uint32_t end, std::function<void()> onCompleted, File file)
61 : end(end), onCompleted(std::move(onCompleted)), file(std::move(file))
62 {
63 }
64
65 void readNextChunk(uint32_t chunkBegin, char *chunkBuffer)
66 {
67 // Copy current chunk from JS memory to Wasm memory
68 qstdweb::ArrayBuffer result = fileReader.result();
69 qstdweb::Uint8Array(result).copyTo(chunkBuffer);
70
71 // Read next chunk if not at buffer end
72 const uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
73 if (nextChunkBegin == end) {
74 onCompleted();
75 delete this;
76 return;
77 }
78 char *nextChunkBuffer = chunkBuffer + result.byteLength();
79 fileReader.onLoad([this, nextChunkBegin, nextChunkBuffer](emscripten::val) {
80 readNextChunk(nextChunkBegin, nextChunkBuffer);
81 });
82 const uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
83 qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd);
84 fileReader.readAsArrayBuffer(blob);
85 }
86
87 static constexpr uint32_t chunkSize = 256 * 1024;
88
89 qstdweb::FileReader fileReader;
90 uint32_t end;
91 std::function<void()> onCompleted;
92 File file;
93};
94
95#if defined(QT_STATIC)
96
97EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; });
98EM_JS(bool, jsHaveJspi, (),
99 { return typeof Asyncify !== "undefined" && !!Asyncify.makeAsyncFunction && (!!WebAssembly.Function || !!WebAssembly.Suspending); });
100
101#else
102
103bool jsHaveAsyncify() { return false; }
104bool jsHaveJspi() { return false; }
105
106#endif
107} // namespace
108
113
119
121{
123 return 0;
124
125 return m_arrayBuffer["byteLength"].as<uint32_t>();
126}
127
132
134{
135 return m_arrayBuffer;
136}
137
139 :m_blob(blob)
140{
141
142}
143
145{
146 auto array = emscripten::val::array();
147 array.call<void>("push", arrayBuffer.val());
148 return Blob(emscripten::val::global("Blob").new_(array));
149}
150
152{
153 return m_blob["size"].as<uint32_t>();
154}
155
166
167// Copies content from the given buffer into a Blob object
169{
170 return copyFrom(buffer, size, "application/octet-stream");
171}
172
174{
175 return Blob(m_blob.call<emscripten::val>("slice", begin, end));
176}
177
179{
183 handlers,
184 m_blob,
185 QStringLiteral("arrayBuffer"),
186 {
189 }
190 });
192 return ArrayBuffer(buffer);
193}
194
196{
197 return m_blob;
198}
199
201:m_file(file)
202{
203
204}
205
206File::~File() = default;
207
208File::File(const File &other) = default;
209
210File::File(File &&other) = default;
211
212File &File::operator=(const File &other) = default;
213
214File &File::operator=(File &&other) = default;
215
216
218{
219 return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end)));
220}
221
223{
224 return m_file["name"].as<std::string>();
225}
226
228{
229 return uint64_t(m_file["size"].as<uint53_t>());
230}
231
233{
234 return m_blob["type"].as<std::string>();
235}
236
237// Streams partial file content into the given buffer asynchronously. The completed
238// callback is called on completion.
244
245// Streams file content into the given buffer asynchronously. The completed
246// callback is called on completion.
247void File::stream(char *buffer, std::function<void()> completed) const
248{
250}
251
253{
254 return m_file["type"].as<std::string>();
255}
256
258{
259 return m_file;
260}
261
263{
265 "createObjectURL", file.file()));
266}
267
269{
270 emscripten::val::global("window")["URL"].call<void>("revokeObjectURL",
272}
273
275
277
283
284int FileList::length() const
285{
286 return m_fileList["length"].as<int>();
287}
288
290{
291 return File(m_fileList[index]);
292}
293
295{
296 return item(index);
297}
298
300{
301 return m_fileList;
302}
303
305{
306 return ArrayBuffer(m_fileReader["result"]);
307}
308
310{
311 m_fileReader.call<void>("readAsArrayBuffer", blob.m_blob);
312}
313
319
325
331
333{
334 return m_fileReader;
335}
336
337// Constructs a Uint8Array which references the given emscripten::val, which must contain a JS Unit8Array
343
344// Constructs a Uint8Array which references an ArrayBuffer
350
351// Constructs a Uint8Array which references a view into an ArrayBuffer
357
358// Constructs a Uint8Array which references an area on the heap.
364
365// Constructs a Uint8Array which allocates and references a new ArrayBuffer with the given size.
371
373{
374 return ArrayBuffer(m_uint8Array["buffer"]);
375}
376
378{
379 return m_uint8Array["length"].as<uint32_t>();
380}
381
383{
384 m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
385}
386
388{
389 // Note: using uint64_t here errors with "Cannot convert a BigInt value to a number"
390 // (see JS BigInt and Number types). Use uint32_t for now.
391 return Uint8Array(m_uint8Array.call<emscripten::val>("subarray", begin, end));
392}
393
394// Copies the Uint8Array content to a destination on the heap
395void Uint8Array::copyTo(char *destination) const
396{
397 Uint8Array(destination, length()).set(*this);
398}
399
400// Copies the Uint8Array content to a destination QByteArray
411
412// Copies the Uint8Array content to a destination on the heap
417
418// Copies content from a source on the heap to a new Uint8Array object
425
426// Copies content from a QByteArray to a new Uint8Array object
431
433{
434 return m_uint8Array;
435}
436
438{
439 return emscripten::val::global("Uint8Array");
440}
441
448
450
451//
452// When a promise settles, all attached handlers will be called in
453// the order they where added.
454//
455// In particular a finally handler will be called according to its
456// position in the call chain. Which is not necessarily at the end,
457//
458// This makes cleanup difficult. If we cleanup to early, we will remove
459// handlers before they have a chance to be called. This would be the
460// case if we add a finally handler in the Promise constructor.
461//
462// For correct cleanup it is necessary that it happens after the
463// last handler has been called.
464//
465// We choose to implement this by making sure the last handler
466// is always a finally handler.
467//
468// In this case we have multiple finally handlers, so any called
469// handler checks if it is the last handler to be called.
470// If it is, the cleanup is performed, otherwise cleanup
471// is delayed to the last handler.
472//
473// We could try to let the handlers cleanup themselves, but this
474// only works for finally handlers. A then or catch handler is not
475// necessarily called, and would not cleanup itself.
476//
477// We could try to let a (then,catch) pair cleanup both handlers,
478// under the assumption that one of them will always be called.
479// This does not work in the case that we do not have both handlers,
480// or if the then handler throws (both should be called in this case).
481//
496
511
513{
515
517 auto state = m_state;
518
519 std::function<void(emscripten::val)> func =
522
523 finallyFunc();
524
525 // See comment at top, we can only do the cleanup
526 // if we are the last handler in the handler chain
527 if (state->m_handlers.back() == *thisHandler) {
528 auto guard = state; // removeEventHandler will remove also this function
530 for (int i = 0; i < guard->m_handlers.size(); ++i) {
532 guard->m_handlers[i] = (uint32_t)(-1);
533 }
534 }
535 };
536
541 "finally",
544
545 return (*this);
546}
547
552
553// Blocks/suspends until the promise settles. The returned AwaitResult
554// carries either the value (on resolve) or the error (on reject); the
555// other field is left as emscripten::val::undefined().
557{
559 addThenFunction ([&r](emscripten::val v) { r.value = v; });
560 addCatchFunction([&r](emscripten::val e) { r.error = e; });
562 return r;
563}
564
566{
567 return m_state->m_promise;
568}
569
571{
573 "Promise::adoptPromise", "must provide at least one callback function");
574
576
577 // Registers a possibly-empty callback with suspendresumecontrol. Returns
578 // the the handler index if there was a valid callback, or nullopt.
580 if (!cb)
581 return std::nullopt;
583 };
584
585 // Register callbacks with suspendresumecontrol, so that it can
586 // resume the wasm instance when the promise resolves. The finally
587 // callback is sepecial, since we remove the event handlers there
588 // as cleanup, including the event handler for the cleanup function
589 // itself.
594
595 // 'Finally' callback which performs clean-up and calls the user-provided finally.
598
599 // Clean up event handlers
600 if (thenIndex)
602 if (catchIndex)
604
605 // Call user finally
606 if (finallyFunc)
607 finallyFunc();
608
609 // Remove the finally (this) event handler last
611 };
612
614
615 // Set handlers on the promise
616 if (thenIndex)
617 promise =
619
620 if (catchIndex)
621 promise = promise.call<emscripten::val>("catch",
623
624 promise = promise.call<emscripten::val>("finally",
626
627 if (handlers) {
628 if (thenIndex)
630 if (catchIndex)
633 }
634 return *finallyIndex;
635}
636
643
650
651// Asyncify and thread blocking: Normally, it's not possible to block the main
652// thread, except if asyncify is enabled. Secondary threads can always block.
653//
654// haveAsyncify(): returns true if the main thread can block on QEventLoop::exec(),
655// if either asyncify 1 or 2 (JSPI) is available.
656//
657// haveJspi(): returns true if asyncify 2 (JSPI) is available.
658//
659// canBlockCallingThread(): returns true if the calling thread can block on
660// QEventLoop::exec(), using either asyncify or as a seconarday thread.
662{
663 static bool HaveJspi = jsHaveJspi();
664 return HaveJspi;
665}
666
668{
669 static bool HaveAsyncify = jsHaveAsyncify() || haveJspi();
670 return HaveAsyncify;
671}
672
674{
675 return haveAsyncify() || !emscripten_is_main_runtime_thread();
676}
677
683
685{
687 return false;
688 return QIODevice::open(mode);
689}
690
692{
693 return false;
694}
695
697{
698 return m_blob.size();
699}
700
702{
703 if (pos >= size())
704 return false;
705 return QIODevice::seek(pos);
706}
707
719
721{
723}
724
726 : m_array(array)
727{
728
729}
730
731bool Uint8ArrayIODevice::open(QIODevice::OpenMode mode)
732{
733 return QIODevice::open(mode);
734}
735
737{
738 return false;
739}
740
742{
743 return m_array.length();
744}
745
746bool Uint8ArrayIODevice::seek(qint64 pos)
747{
748 if (pos >= size())
749 return false;
750 return QIODevice::seek(pos);
751}
752
753qint64 Uint8ArrayIODevice::readData(char *data, qint64 maxSize)
754{
755 uint64_t begin = QIODevice::pos();
756 uint64_t end = std::min<uint64_t>(begin + maxSize, size());
757 uint64_t size = end - begin;
758 if (size > 0)
759 m_array.subarray(begin, end).copyTo(data);
760 return size;
761}
762
763qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
764{
765 uint64_t begin = QIODevice::pos();
766 uint64_t end = std::min<uint64_t>(begin + maxSize, size());
767 uint64_t size = end - begin;
768 if (size > 0)
769 m_array.subarray(begin, end).set(Uint8Array(data, size));
770 return size;
771}
772
777
784
786{
787 if (!isOpen()) {
788 QIODevice::close();
789 return;
790 }
791
794 .thenFunc = [](emscripten::val) {
795 }
796 });
798
799 QIODevice::close();
800}
801
803{
804 return false;
805}
806
811
813{
814 bool success = false;
815
817 seekParams.set("type", std::string("seek"));
818 seekParams.set("position", static_cast<double>(pos));
822 success = true;
823 },
824 .catchFunc = [](emscripten::val) {
825 }
826 }, seekParams);
828
829 if (!success)
830 return false;
831
832 return QIODevice::seek(pos);
833}
834
839
841{
842 bool success = false;
843
848 success = true;
849 },
850 .catchFunc = [](emscripten::val) {
851 }
852 }, array.val());
854
855 if (success) {
856 qint64 newPos = pos() + size;
858 return size;
859 }
860 return -1;
861}
862
867
872
877
879{
880 return m_fileHandle["name"].as<std::string>();
881}
882
884{
885 return m_fileHandle["kind"].as<std::string>();
886}
887
889{
890 return m_fileHandle;
891}
892
897
899{
900 if (isOpen())
901 return false;
902
903 // Read mode: get the File and create a BlobIODevice
904 if (mode & QIODevice::ReadOnly) {
905 File file;
906 bool success = false;
907
911 file = File(fileVal);
912 success = true;
913 },
914 .catchFunc = [](emscripten::val) {
915 }
916 });
918
919 if (success) {
921 m_size = file.size();
922
923 if (!m_blobDevice->open(mode))
924 return false;
925 } else {
926 return false;
927 }
928 }
929
930 // Write mode: create a writable stream
931 if (mode & QIODevice::WriteOnly) {
933 bool success = false;
934
936 Promise::make(handlers, m_fileHandle.val(), QStringLiteral("createWritable"), {
939 success = true;
940 },
941 .catchFunc = [](emscripten::val) {
942 }
943 });
945
946 if (success) {
949 return false;
950 } else {
951 return false;
952 }
953 }
954
955 return QIODevice::open(mode);
956}
957
959{
960 if (!isOpen()) {
961 QIODevice::close();
962 return;
963 }
964
965 if (m_writableDevice) {
968 }
969 if (m_blobDevice) {
972 }
973
974 QIODevice::close();
975}
976
978{
979 return false;
980}
981
983{
984 return m_size;
985}
986
988{
989 if (m_blobDevice) {
990 if (!m_blobDevice->seek(pos))
991 return false;
992 }
993 if (m_writableDevice) {
995 return false;
996 }
997 return QIODevice::seek(pos);
998}
999
1001{
1002 if (!m_blobDevice)
1003 return -1;
1004
1005 return m_blobDevice->read(data, maxSize);
1006}
1007
1009{
1010 if (!m_writableDevice)
1011 return -1;
1012
1014 if (written > 0) {
1015 qint64 newPos = pos() + written;
1016 m_size = std::max(m_size, newPos);
1017 }
1018 return written;
1019}
1020
1021} // namespace qstdweb
1022
1023QT_END_NAMESPACE
qint64 size() const override
For open random-access devices, this function returns the size of the device.
Definition qstdweb.cpp:741
bool seek(qint64 pos) override
For random-access devices, this function sets the current position to pos, returning true on success,...
Definition qstdweb.cpp:746
Uint8ArrayIODevice(Uint8Array array)
Definition qstdweb.cpp:725
bool isSequential() const override
Returns true if this device is sequential; otherwise returns false.
Definition qstdweb.cpp:736
qint64 writeData(const char *data, qint64 size) override
Writes up to maxSize bytes from data to the device.
Definition qstdweb.cpp:763
bool open(QIODevice::OpenMode mode) override
Definition qstdweb.cpp:731
qint64 readData(char *data, qint64 maxSize) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
Definition qstdweb.cpp:753
bool canBlockCallingThread()
Definition qstdweb.cpp:673
double uint53_t
Definition qstdweb.cpp:46
bool haveAsyncify()
Definition qstdweb.cpp:667
bool haveJspi()
Definition qstdweb.cpp:661
static void usePotentialyUnusedSymbols()
Definition qstdweb.cpp:31