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
189
191{
192 return m_blob;
193}
194
196:m_file(file)
197{
198
199}
200
201File::~File() = default;
202
203File::File(const File &other) = default;
204
205File::File(File &&other) = default;
206
207File &File::operator=(const File &other) = default;
208
209File &File::operator=(File &&other) = default;
210
211
213{
214 return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end)));
215}
216
218{
219 return m_file["name"].as<std::string>();
220}
221
223{
224 return uint64_t(m_file["size"].as<uint53_t>());
225}
226
228{
229 return m_blob["type"].as<std::string>();
230}
231
232// Streams partial file content into the given buffer asynchronously. The completed
233// callback is called on completion.
239
240// Streams file content into the given buffer asynchronously. The completed
241// callback is called on completion.
242void File::stream(char *buffer, std::function<void()> completed) const
243{
245}
246
248{
249 return m_file["type"].as<std::string>();
250}
251
253{
254 return m_file;
255}
256
258{
260 "createObjectURL", file.file()));
261}
262
264{
265 emscripten::val::global("window")["URL"].call<void>("revokeObjectURL",
267}
268
270
272
278
279int FileList::length() const
280{
281 return m_fileList["length"].as<int>();
282}
283
285{
286 return File(m_fileList[index]);
287}
288
290{
291 return item(index);
292}
293
295{
296 return m_fileList;
297}
298
300{
301 return ArrayBuffer(m_fileReader["result"]);
302}
303
305{
306 m_fileReader.call<void>("readAsArrayBuffer", blob.m_blob);
307}
308
314
320
326
328{
329 return m_fileReader;
330}
331
332// Constructs a Uint8Array which references the given emscripten::val, which must contain a JS Unit8Array
338
339// Constructs a Uint8Array which references an ArrayBuffer
345
346// Constructs a Uint8Array which references a view into an ArrayBuffer
352
353// Constructs a Uint8Array which references an area on the heap.
359
360// Constructs a Uint8Array which allocates and references a new ArrayBuffer with the given size.
366
368{
369 return ArrayBuffer(m_uint8Array["buffer"]);
370}
371
373{
374 return m_uint8Array["length"].as<uint32_t>();
375}
376
378{
379 m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
380}
381
383{
384 // Note: using uint64_t here errors with "Cannot convert a BigInt value to a number"
385 // (see JS BigInt and Number types). Use uint32_t for now.
386 return Uint8Array(m_uint8Array.call<emscripten::val>("subarray", begin, end));
387}
388
389// Copies the Uint8Array content to a destination on the heap
390void Uint8Array::copyTo(char *destination) const
391{
392 Uint8Array(destination, length()).set(*this);
393}
394
395// Copies the Uint8Array content to a destination QByteArray
406
407// Copies the Uint8Array content to a destination on the heap
412
413// Copies content from a source on the heap to a new Uint8Array object
420
421// Copies content from a QByteArray to a new Uint8Array object
426
428{
429 return m_uint8Array;
430}
431
433{
434 return emscripten::val::global("Uint8Array");
435}
436
443
444uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks)
445{
446 Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc,
447 "Promise::adoptPromise", "must provide at least one callback function");
448
449 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
450 Q_ASSERT(suspendResume);
451
452 // Registers a possibly-empty callback with suspendresumecontrol. Returns
453 // the the handler index if there was a valid callback, or nullopt.
454 auto registerCallback = [suspendResume](std::function<void(emscripten::val)> cb) -> std::optional<uint32_t>{
455 if (!cb)
456 return std::nullopt;
457 return std::optional<uint32_t>{suspendResume->registerEventHandler(std::move(cb))};
458 };
459
460 // Register callbacks with suspendresumecontrol, so that it can
461 // resume the wasm instance when the promise resolves. The finally
462 // callback is sepecial, since we remove the event handlers there
463 // as cleanup, including the event handler for the cleanup function
464 // itself.
465 std::optional<uint32_t> thenIndex = registerCallback(std::move(callbacks.thenFunc));
466 std::optional<uint32_t> catchIndex = registerCallback(std::move(callbacks.catchFunc));
467 std::shared_ptr<uint32_t> finallyIndex = std::make_shared<uint32_t>();;
468 auto finallyFunc = callbacks.finallyFunc;
469
470 // 'Finally' callback which performs clean-up and calls the user-provided finally.
471 auto finally = [suspendResume, thenIndex, catchIndex, finallyIndex, finallyFunc](emscripten::val){
472
473 // Clean up event handlers
474 if (thenIndex)
475 suspendResume->removeEventHandler(*thenIndex);
476 if (catchIndex)
477 suspendResume->removeEventHandler(*catchIndex);
478
479 // Call user finally
480 if (finallyFunc)
481 finallyFunc();
482
483 // Remove the finally (this) event handler last
484 suspendResume->removeEventHandler(*finallyIndex);
485 };
486
487 *finallyIndex = suspendResume->registerEventHandler(std::move(finally));
488
489 // Set handlers on the promise
490 if (thenIndex)
491 promise =
492 promise.call<emscripten::val>("then", suspendResume->jsEventHandlerAt(*thenIndex));
493
494 if (catchIndex)
495 promise = promise.call<emscripten::val>("catch",
496 suspendResume->jsEventHandlerAt(*catchIndex));
497
498 promise = promise.call<emscripten::val>("finally",
499 suspendResume->jsEventHandlerAt(*finallyIndex));
500
501 return *finallyIndex;
502}
503
504void Promise::suspendExclusive(uint32_t handlerIndex)
505{
506 QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
507 Q_ASSERT(suspendResume);
508 suspendResume->suspendExclusive(handlerIndex);
509 suspendResume->sendPendingEvents();
510}
511
512void Promise::all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks)
513{
514 auto arr = emscripten::val::array(promises);
515 auto all = val::global("Promise").call<emscripten::val>("all", arr);
516 adoptPromise(all, callbacks);
517}
518
519// Asyncify and thread blocking: Normally, it's not possible to block the main
520// thread, except if asyncify is enabled. Secondary threads can always block.
521//
522// haveAsyncify(): returns true if the main thread can block on QEventLoop::exec(),
523// if either asyncify 1 or 2 (JSPI) is available.
524//
525// haveJspi(): returns true if asyncify 2 (JSPI) is available.
526//
527// canBlockCallingThread(): returns true if the calling thread can block on
528// QEventLoop::exec(), using either asyncify or as a seconarday thread.
530{
531 static bool HaveJspi = jsHaveJspi();
532 return HaveJspi;
533}
534
536{
537 static bool HaveAsyncify = jsHaveAsyncify() || haveJspi();
538 return HaveAsyncify;
539}
540
542{
543 return haveAsyncify() || !emscripten_is_main_runtime_thread();
544}
545
551
553{
555 return false;
556 return QIODevice::open(mode);
557}
558
560{
561 return false;
562}
563
565{
566 return m_blob.size();
567}
568
570{
571 if (pos >= size())
572 return false;
573 return QIODevice::seek(pos);
574}
575
587
589{
591}
592
594 : m_array(array)
595{
596
597}
598
599bool Uint8ArrayIODevice::open(QIODevice::OpenMode mode)
600{
601 return QIODevice::open(mode);
602}
603
605{
606 return false;
607}
608
610{
611 return m_array.length();
612}
613
614bool Uint8ArrayIODevice::seek(qint64 pos)
615{
616 if (pos >= size())
617 return false;
618 return QIODevice::seek(pos);
619}
620
621qint64 Uint8ArrayIODevice::readData(char *data, qint64 maxSize)
622{
623 uint64_t begin = QIODevice::pos();
624 uint64_t end = std::min<uint64_t>(begin + maxSize, size());
625 uint64_t size = end - begin;
626 if (size > 0)
627 m_array.subarray(begin, end).copyTo(data);
628 return size;
629}
630
631qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
632{
633 uint64_t begin = QIODevice::pos();
634 uint64_t end = std::min<uint64_t>(begin + maxSize, size());
635 uint64_t size = end - begin;
636 if (size > 0)
637 m_array.subarray(begin, end).set(Uint8Array(data, size));
638 return size;
639}
640
645
652
654{
655 if (!isOpen()) {
656 QIODevice::close();
657 return;
658 }
659
661 .thenFunc = [](emscripten::val) {
662 }
663 });
665
666 QIODevice::close();
667}
668
670{
671 return false;
672}
673
678
680{
681 bool success = false;
682
684 seekParams.set("type", std::string("seek"));
685 seekParams.set("position", static_cast<double>(pos));
688 success = true;
689 },
690 .catchFunc = [](emscripten::val) {
691 }
692 }, seekParams);
694
695 if (!success)
696 return false;
697
698 return QIODevice::seek(pos);
699}
700
705
707{
708 bool success = false;
709
713 success = true;
714 },
715 .catchFunc = [](emscripten::val) {
716 }
717 }, array.val());
719
720 if (success) {
721 qint64 newPos = pos() + size;
723 return size;
724 }
725 return -1;
726}
727
732
737
742
744{
745 return m_fileHandle["name"].as<std::string>();
746}
747
749{
750 return m_fileHandle["kind"].as<std::string>();
751}
752
754{
755 return m_fileHandle;
756}
757
762
764{
765 if (isOpen())
766 return false;
767
768 // Read mode: get the File and create a BlobIODevice
769 if (mode & QIODevice::ReadOnly) {
770 File file;
771 bool success = false;
772
775 file = File(fileVal);
776 success = true;
777 },
778 .catchFunc = [](emscripten::val) {
779 }
780 });
782
783 if (success) {
785 m_size = file.size();
786
787 if (!m_blobDevice->open(mode))
788 return false;
789 } else {
790 return false;
791 }
792 }
793
794 // Write mode: create a writable stream
795 if (mode & QIODevice::WriteOnly) {
797 bool success = false;
798
802 success = true;
803 },
804 .catchFunc = [](emscripten::val) {
805 }
806 });
808
809 if (success) {
812 return false;
813 } else {
814 return false;
815 }
816 }
817
818 return QIODevice::open(mode);
819}
820
822{
823 if (!isOpen()) {
824 QIODevice::close();
825 return;
826 }
827
828 if (m_writableDevice) {
831 }
832 if (m_blobDevice) {
835 }
836
837 QIODevice::close();
838}
839
841{
842 return false;
843}
844
846{
847 return m_size;
848}
849
851{
852 if (m_blobDevice) {
853 if (!m_blobDevice->seek(pos))
854 return false;
855 }
856 if (m_writableDevice) {
858 return false;
859 }
860 return QIODevice::seek(pos);
861}
862
864{
865 if (!m_blobDevice)
866 return -1;
867
868 return m_blobDevice->read(data, maxSize);
869}
870
872{
873 if (!m_writableDevice)
874 return -1;
875
877 if (written > 0) {
878 qint64 newPos = pos() + written;
880 }
881 return written;
882}
883
884} // namespace qstdweb
885
886QT_END_NAMESPACE
qint64 size() const override
For open random-access devices, this function returns the size of the device.
Definition qstdweb.cpp:609
bool seek(qint64 pos) override
For random-access devices, this function sets the current position to pos, returning true on success,...
Definition qstdweb.cpp:614
Uint8ArrayIODevice(Uint8Array array)
Definition qstdweb.cpp:593
bool isSequential() const override
Returns true if this device is sequential; otherwise returns false.
Definition qstdweb.cpp:604
qint64 writeData(const char *data, qint64 size) override
Writes up to maxSize bytes from data to the device.
Definition qstdweb.cpp:631
bool open(QIODevice::OpenMode mode) override
Definition qstdweb.cpp:599
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:621
bool canBlockCallingThread()
Definition qstdweb.cpp:541
double uint53_t
Definition qstdweb.cpp:46
bool haveAsyncify()
Definition qstdweb.cpp:535
bool haveJspi()
Definition qstdweb.cpp:529
static void usePotentialyUnusedSymbols()
Definition qstdweb.cpp:31