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
95EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; });
96EM_JS(bool, jsHaveJspi, (),
97 { return typeof Asyncify !== "undefined" && !!Asyncify.makeAsyncFunction && (!!WebAssembly.Function || !!WebAssembly.Suspending); });
98
99} // namespace
100
105
111
113{
115 return 0;
116
117 return m_arrayBuffer["byteLength"].as<uint32_t>();
118}
119
124
126{
127 return m_arrayBuffer;
128}
129
131 :m_blob(blob)
132{
133
134}
135
137{
138 auto array = emscripten::val::array();
139 array.call<void>("push", arrayBuffer.val());
140 return Blob(emscripten::val::global("Blob").new_(array));
141}
142
144{
145 return m_blob["size"].as<uint32_t>();
146}
147
158
159// Copies content from the given buffer into a Blob object
161{
162 return copyFrom(buffer, size, "application/octet-stream");
163}
164
166{
167 return Blob(m_blob.call<emscripten::val>("slice", begin, end));
168}
169
171{
175 handlers,
176 m_blob,
177 QStringLiteral("arrayBuffer"),
178 {
181 }
182 });
184 return ArrayBuffer(buffer);
185}
186
188{
189 return m_blob;
190}
191
193:m_file(file)
194{
195
196}
197
198File::~File() = default;
199
200File::File(const File &other) = default;
201
202File::File(File &&other) = default;
203
204File &File::operator=(const File &other) = default;
205
206File &File::operator=(File &&other) = default;
207
208
210{
211 return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end)));
212}
213
215{
216 return m_file["name"].as<std::string>();
217}
218
220{
221 return uint64_t(m_file["size"].as<uint53_t>());
222}
223
225{
226 return m_blob["type"].as<std::string>();
227}
228
229// Streams partial file content into the given buffer asynchronously. The completed
230// callback is called on completion.
236
237// Streams file content into the given buffer asynchronously. The completed
238// callback is called on completion.
239void File::stream(char *buffer, std::function<void()> completed) const
240{
242}
243
245{
246 return m_file["type"].as<std::string>();
247}
248
250{
251 return m_file;
252}
253
255{
257 "createObjectURL", file.file()));
258}
259
261{
262 emscripten::val::global("window")["URL"].call<void>("revokeObjectURL",
264}
265
267
269
275
276int FileList::length() const
277{
278 return m_fileList["length"].as<int>();
279}
280
282{
283 return File(m_fileList[index]);
284}
285
287{
288 return item(index);
289}
290
292{
293 return m_fileList;
294}
295
297{
298 return ArrayBuffer(m_fileReader["result"]);
299}
300
302{
303 m_fileReader.call<void>("readAsArrayBuffer", blob.m_blob);
304}
305
311
317
323
325{
326 return m_fileReader;
327}
328
329// Constructs a Uint8Array which references the given emscripten::val, which must contain a JS Unit8Array
335
336// Constructs a Uint8Array which references an ArrayBuffer
342
343// Constructs a Uint8Array which references a view into an ArrayBuffer
349
350// Constructs a Uint8Array which references an area on the heap.
356
357// Constructs a Uint8Array which allocates and references a new ArrayBuffer with the given size.
363
365{
366 return ArrayBuffer(m_uint8Array["buffer"]);
367}
368
370{
371 return m_uint8Array["length"].as<uint32_t>();
372}
373
375{
376 m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
377}
378
380{
381 // Note: using uint64_t here errors with "Cannot convert a BigInt value to a number"
382 // (see JS BigInt and Number types). Use uint32_t for now.
383 return Uint8Array(m_uint8Array.call<emscripten::val>("subarray", begin, end));
384}
385
386// Copies the Uint8Array content to a destination on the heap
387void Uint8Array::copyTo(char *destination) const
388{
389 Uint8Array(destination, length()).set(*this);
390}
391
392// Copies the Uint8Array content to a destination QByteArray
403
404// Copies the Uint8Array content to a destination on the heap
409
410// Copies content from a source on the heap to a new Uint8Array object
417
418// Copies content from a QByteArray to a new Uint8Array object
423
425{
426 return m_uint8Array;
427}
428
430{
431 return emscripten::val::global("Uint8Array");
432}
433
440
442
443//
444// When a promise settles, all attached handlers will be called in
445// the order they where added.
446//
447// In particular a finally handler will be called according to its
448// position in the call chain. Which is not necessarily at the end,
449//
450// This makes cleanup difficult. If we cleanup to early, we will remove
451// handlers before they have a chance to be called. This would be the
452// case if we add a finally handler in the Promise constructor.
453//
454// For correct cleanup it is necessary that it happens after the
455// last handler has been called.
456//
457// We choose to implement this by making sure the last handler
458// is always a finally handler.
459//
460// In this case we have multiple finally handlers, so any called
461// handler checks if it is the last handler to be called.
462// If it is, the cleanup is performed, otherwise cleanup
463// is delayed to the last handler.
464//
465// We could try to let the handlers cleanup themselves, but this
466// only works for finally handlers. A then or catch handler is not
467// necessarily called, and would not cleanup itself.
468//
469// We could try to let a (then,catch) pair cleanup both handlers,
470// under the assumption that one of them will always be called.
471// This does not work in the case that we do not have both handlers,
472// or if the then handler throws (both should be called in this case).
473//
488
503
505{
507
509 auto state = m_state;
510
511 std::function<void(emscripten::val)> func =
514
515 finallyFunc();
516
517 // See comment at top, we can only do the cleanup
518 // if we are the last handler in the handler chain
519 if (state->m_handlers.back() == *thisHandler) {
520 auto guard = state; // removeEventHandler will remove also this function
522 for (int i = 0; i < guard->m_handlers.size(); ++i) {
524 guard->m_handlers[i] = (uint32_t)(-1);
525 }
526 }
527 };
528
533 "finally",
536
537 return (*this);
538}
539
544
545// Blocks/suspends until the promise settles. The returned AwaitResult
546// carries either the value (on resolve) or the error (on reject); the
547// other field is left as emscripten::val::undefined().
549{
551 addThenFunction ([&r](emscripten::val v) { r.value = v; });
552 addCatchFunction([&r](emscripten::val e) { r.error = e; });
554 return r;
555}
556
558{
559 return m_state->m_promise;
560}
561
563{
565 "Promise::adoptPromise", "must provide at least one callback function");
566
568
569 // Registers a possibly-empty callback with suspendresumecontrol. Returns
570 // the the handler index if there was a valid callback, or nullopt.
572 if (!cb)
573 return std::nullopt;
575 };
576
577 // Register callbacks with suspendresumecontrol, so that it can
578 // resume the wasm instance when the promise resolves. The finally
579 // callback is sepecial, since we remove the event handlers there
580 // as cleanup, including the event handler for the cleanup function
581 // itself.
586
587 // 'Finally' callback which performs clean-up and calls the user-provided finally.
590
591 // Clean up event handlers
592 if (thenIndex)
594 if (catchIndex)
596
597 // Call user finally
598 if (finallyFunc)
599 finallyFunc();
600
601 // Remove the finally (this) event handler last
603 };
604
606
607 // Set handlers on the promise
608 if (thenIndex)
609 promise =
611
612 if (catchIndex)
613 promise = promise.call<emscripten::val>("catch",
615
616 promise = promise.call<emscripten::val>("finally",
618
619 if (handlers) {
620 if (thenIndex)
622 if (catchIndex)
625 }
626 return *finallyIndex;
627}
628
635
642
643// Asyncify and thread blocking: Normally, it's not possible to block the main
644// thread, except if asyncify is enabled. Secondary threads can always block.
645//
646// haveAsyncify(): returns true if the main thread can block on QEventLoop::exec(),
647// if either asyncify 1 or 2 (JSPI) is available.
648//
649// haveJspi(): returns true if asyncify 2 (JSPI) is available.
650//
651// canBlockCallingThread(): returns true if the calling thread can block on
652// QEventLoop::exec(), using either asyncify or as a seconarday thread.
654{
655 static bool HaveJspi = jsHaveJspi();
656 return HaveJspi;
657}
658
660{
661 static bool HaveAsyncify = jsHaveAsyncify() || haveJspi();
662 return HaveAsyncify;
663}
664
666{
667 return haveAsyncify() || !emscripten_is_main_runtime_thread();
668}
669
675
677{
679 return false;
680 return QIODevice::open(mode);
681}
682
684{
685 return false;
686}
687
689{
690 return m_blob.size();
691}
692
694{
695 if (pos >= size())
696 return false;
697 return QIODevice::seek(pos);
698}
699
711
713{
715}
716
718 : m_array(array)
719{
720
721}
722
723bool Uint8ArrayIODevice::open(QIODevice::OpenMode mode)
724{
725 return QIODevice::open(mode);
726}
727
729{
730 return false;
731}
732
734{
735 return m_array.length();
736}
737
738bool Uint8ArrayIODevice::seek(qint64 pos)
739{
740 if (pos >= size())
741 return false;
742 return QIODevice::seek(pos);
743}
744
745qint64 Uint8ArrayIODevice::readData(char *data, qint64 maxSize)
746{
747 uint64_t begin = QIODevice::pos();
748 uint64_t end = std::min<uint64_t>(begin + maxSize, size());
749 uint64_t size = end - begin;
750 if (size > 0)
751 m_array.subarray(begin, end).copyTo(data);
752 return size;
753}
754
755qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
756{
757 uint64_t begin = QIODevice::pos();
758 uint64_t end = std::min<uint64_t>(begin + maxSize, size());
759 uint64_t size = end - begin;
760 if (size > 0)
761 m_array.subarray(begin, end).set(Uint8Array(data, size));
762 return size;
763}
764
769
776
778{
779 if (!isOpen()) {
780 QIODevice::close();
781 return;
782 }
783
786 .thenFunc = [](emscripten::val) {
787 }
788 });
790
791 QIODevice::close();
792}
793
795{
796 return false;
797}
798
803
805{
806 bool success = false;
807
809 seekParams.set("type", std::string("seek"));
810 seekParams.set("position", static_cast<double>(pos));
814 success = true;
815 },
816 .catchFunc = [](emscripten::val) {
817 }
818 }, seekParams);
820
821 if (!success)
822 return false;
823
824 return QIODevice::seek(pos);
825}
826
831
833{
834 bool success = false;
835
840 success = true;
841 },
842 .catchFunc = [](emscripten::val) {
843 }
844 }, array.val());
846
847 if (success) {
848 qint64 newPos = pos() + size;
850 return size;
851 }
852 return -1;
853}
854
859
864
869
871{
872 return m_fileHandle["name"].as<std::string>();
873}
874
876{
877 return m_fileHandle["kind"].as<std::string>();
878}
879
881{
882 return m_fileHandle;
883}
884
889
891{
892 if (isOpen())
893 return false;
894
895 // Read mode: get the File and create a BlobIODevice
896 if (mode & QIODevice::ReadOnly) {
897 File file;
898 bool success = false;
899
903 file = File(fileVal);
904 success = true;
905 },
906 .catchFunc = [](emscripten::val) {
907 }
908 });
910
911 if (success) {
913 m_size = file.size();
914
915 if (!m_blobDevice->open(mode))
916 return false;
917 } else {
918 return false;
919 }
920 }
921
922 // Write mode: create a writable stream
923 if (mode & QIODevice::WriteOnly) {
925 bool success = false;
926
928 Promise::make(handlers, m_fileHandle.val(), QStringLiteral("createWritable"), {
931 success = true;
932 },
933 .catchFunc = [](emscripten::val) {
934 }
935 });
937
938 if (success) {
941 return false;
942 } else {
943 return false;
944 }
945 }
946
947 return QIODevice::open(mode);
948}
949
951{
952 if (!isOpen()) {
953 QIODevice::close();
954 return;
955 }
956
957 if (m_writableDevice) {
960 }
961 if (m_blobDevice) {
964 }
965
966 QIODevice::close();
967}
968
970{
971 return false;
972}
973
975{
976 return m_size;
977}
978
980{
981 if (m_blobDevice) {
982 if (!m_blobDevice->seek(pos))
983 return false;
984 }
985 if (m_writableDevice) {
987 return false;
988 }
989 return QIODevice::seek(pos);
990}
991
993{
994 if (!m_blobDevice)
995 return -1;
996
997 return m_blobDevice->read(data, maxSize);
998}
999
1001{
1002 if (!m_writableDevice)
1003 return -1;
1004
1006 if (written > 0) {
1007 qint64 newPos = pos() + written;
1008 m_size = std::max(m_size, newPos);
1009 }
1010 return written;
1011}
1012
1013} // namespace qstdweb
1014
1015QT_END_NAMESPACE
qint64 size() const override
For open random-access devices, this function returns the size of the device.
Definition qstdweb.cpp:733
bool seek(qint64 pos) override
For random-access devices, this function sets the current position to pos, returning true on success,...
Definition qstdweb.cpp:738
Uint8ArrayIODevice(Uint8Array array)
Definition qstdweb.cpp:717
bool isSequential() const override
Returns true if this device is sequential; otherwise returns false.
Definition qstdweb.cpp:728
qint64 writeData(const char *data, qint64 size) override
Writes up to maxSize bytes from data to the device.
Definition qstdweb.cpp:755
bool open(QIODevice::OpenMode mode) override
Definition qstdweb.cpp:723
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:745
bool canBlockCallingThread()
Definition qstdweb.cpp:665
double uint53_t
Definition qstdweb.cpp:46
bool haveAsyncify()
Definition qstdweb.cpp:659
bool haveJspi()
Definition qstdweb.cpp:653
static void usePotentialyUnusedSymbols()
Definition qstdweb.cpp:31