Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "qstdweb_p.h"
++ #include "qwasmsuspendresumecontrol_p.h"
++ #include "qwasmglobal_p.h"
++
++ #include <QtCore/qcoreapplication.h>
++ #include <QtCore/qfile.h>
++ #include <QtCore/qmimedata.h>
++
++ #include <emscripten/emscripten.h>
++ #include <emscripten/bind.h>
++ #include <emscripten/val.h>
++ #include <emscripten/html5.h>
++ #include <emscripten/threading.h>
++
++ #include <cstdint>
++ #include <iostream>
++
++ #include <unordered_map>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++ using emscripten::val;
++
++ namespace qstdweb {
++
++ static void usePotentialyUnusedSymbols()
++ {
++ // Using this adds a reference on JSEvents and specialHTMLTargets which are always exported.
++ // This hack is needed as it is currently impossible to specify a dollar sign in
++ // target_link_options. The following is impossible:
++ // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$JSEvents
++ // TODO(mikolajboc): QTBUG-108444, review this when cmake gets fixed.
++ // Volatile is to make this unoptimizable, so that the function is referenced, but is not
++ // called at runtime.
++ volatile bool doIt = false;
++ if (doIt)
++ emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
++ }
++
++ Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
++ typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
++ namespace {
++ // Reads file in chunks in order to avoid holding two copies in memory at the same time
++ struct ChunkedFileReader
++ {
++ public:
++ static void read(File file, char *buffer, uint32_t offset, uint32_t end,
++ std::function<void()> onCompleted)
++ {
++ (new ChunkedFileReader(end, std::move(onCompleted), std::move(file)))
++ ->readNextChunk(offset, buffer);
++ }
++
++ private:
++ ChunkedFileReader(uint32_t end, std::function<void()> onCompleted, File file)
++ : end(end), onCompleted(std::move(onCompleted)), file(std::move(file))
++ {
++ }
++
++ void readNextChunk(uint32_t chunkBegin, char *chunkBuffer)
++ {
++ // Copy current chunk from JS memory to Wasm memory
++ qstdweb::ArrayBuffer result = fileReader.result();
++ qstdweb::Uint8Array(result).copyTo(chunkBuffer);
++
++ // Read next chunk if not at buffer end
++ const uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
++ if (nextChunkBegin == end) {
++ onCompleted();
++ delete this;
++ return;
++ }
++ char *nextChunkBuffer = chunkBuffer + result.byteLength();
++ fileReader.onLoad([this, nextChunkBegin, nextChunkBuffer](emscripten::val) {
++ readNextChunk(nextChunkBegin, nextChunkBuffer);
++ });
++ const uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
++ qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd);
++ fileReader.readAsArrayBuffer(blob);
++ }
++
++ static constexpr uint32_t chunkSize = 256 * 1024;
++
++ qstdweb::FileReader fileReader;
++ uint32_t end;
++ std::function<void()> onCompleted;
++ File file;
++ };
++
++ EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; });
++ EM_JS(bool, jsHaveJspi, (),
++ { return typeof Asyncify !== "undefined" && !!Asyncify.makeAsyncFunction && (!!WebAssembly.Function || !!WebAssembly.Suspending); });
++
++ } // namespace
++
++ ArrayBuffer::ArrayBuffer(uint32_t size)
++ {
++ m_arrayBuffer = emscripten::val::global("ArrayBuffer").new_(size);
++ }
++
++ ArrayBuffer::ArrayBuffer(const emscripten::val &arrayBuffer)
++ :m_arrayBuffer(arrayBuffer)
++ {
++
++ }
++
++ uint32_t ArrayBuffer::byteLength() const
++ {
++ if (m_arrayBuffer.isUndefined() || m_arrayBuffer.isNull())
++ return 0;
++
++ return m_arrayBuffer["byteLength"].as<uint32_t>();
++ }
++
++ ArrayBuffer ArrayBuffer::slice(uint32_t begin, uint32_t end) const
++ {
++ return ArrayBuffer(m_arrayBuffer.call<emscripten::val>("slice", begin, end));
++ }
++
++ emscripten::val ArrayBuffer::val() const
++ {
++ return m_arrayBuffer;
++ }
++
++ Blob::Blob(const emscripten::val &blob)
++ :m_blob(blob)
++ {
++
++ }
++
++ Blob Blob::fromArrayBuffer(const ArrayBuffer &arrayBuffer)
++ {
++ auto array = emscripten::val::array();
++ array.call<void>("push", arrayBuffer.val());
++ return Blob(emscripten::val::global("Blob").new_(array));
++ }
++
++ uint32_t Blob::size() const
++ {
++ return m_blob["size"].as<uint32_t>();
++ }
++
++ Blob Blob::copyFrom(const char *buffer, uint32_t size, std::string mimeType)
++ {
++ Uint8Array contentCopy = Uint8Array::copyFrom(buffer, size);
++
++ emscripten::val contentArray = emscripten::val::array();
++ contentArray.call<void>("push", contentCopy.val());
++ emscripten::val type = emscripten::val::object();
++ type.set("type", std::move(mimeType));
++ return Blob(emscripten::val::global("Blob").new_(contentArray, type));
++ }
++
++ // Copies content from the given buffer into a Blob object
++ Blob Blob::copyFrom(const char *buffer, uint32_t size)
++ {
++ return copyFrom(buffer, size, "application/octet-stream");
++ }
++
++ Blob Blob::slice(uint32_t begin, uint32_t end) const
++ {
++ return Blob(m_blob.call<emscripten::val>("slice", begin, end));
++ }
++
++ ArrayBuffer Blob::arrayBuffer_sync() const
++ {
++ emscripten::val buffer;
++ QList<uint32_t> handlers;
++ qstdweb::Promise::make(
++ handlers,
++ m_blob,
++ QStringLiteral("arrayBuffer"),
++ {
++ .thenFunc = [&buffer](emscripten::val arrayBuffer) {
++ buffer = arrayBuffer;
++ }
++ });
++ Promise::suspendExclusive(handlers);
++ return ArrayBuffer(buffer);
++ }
++
++ emscripten::val Blob::val() const
++ {
++ return m_blob;
++ }
++
++ File::File(const emscripten::val &file)
++ :m_file(file)
++ {
++
++ }
++
++ File::~File() = default;
++
++ File::File(const File &other) = default;
++
++ File::File(File &&other) = default;
++
++ File &File::operator=(const File &other) = default;
++
++ File &File::operator=(File &&other) = default;
++
++
++ Blob File::slice(uint64_t begin, uint64_t end) const
++ {
++ return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end)));
++ }
++
++ std::string File::name() const
++ {
++ return m_file["name"].as<std::string>();
++ }
++
++ uint64_t File::size() const
++ {
++ return uint64_t(m_file["size"].as<uint53_t>());
++ }
++
++ std::string Blob::type() const
++ {
++ return m_blob["type"].as<std::string>();
++ }
++
++ // Streams partial file content into the given buffer asynchronously. The completed
++ // callback is called on completion.
++ void File::stream(uint32_t offset, uint32_t length, char *buffer,
++ std::function<void()> completed) const
++ {
++ ChunkedFileReader::read(*this, buffer, offset, offset + length, std::move(completed));
++ }
++
++ // Streams file content into the given buffer asynchronously. The completed
++ // callback is called on completion.
++ void File::stream(char *buffer, std::function<void()> completed) const
++ {
++ stream(0, size(), buffer, std::move(completed));
++ }
++
++ std::string File::type() const
++ {
++ return m_file["type"].as<std::string>();
++ }
++
++ emscripten::val File::val() const
++ {
++ return m_file;
++ }
++
++ FileUrlRegistration::FileUrlRegistration(File file)
++ {
++ m_path = QString::fromStdString(emscripten::val::global("window")["URL"].call<std::string>(
++ "createObjectURL", file.file()));
++ }
++
++ FileUrlRegistration::~FileUrlRegistration()
++ {
++ emscripten::val::global("window")["URL"].call<void>("revokeObjectURL",
++ emscripten::val(m_path.toStdString()));
++ }
++
++ FileUrlRegistration::FileUrlRegistration(FileUrlRegistration &&other) = default;
++
++ FileUrlRegistration &FileUrlRegistration::operator=(FileUrlRegistration &&other) = default;
++
++ FileList::FileList(const emscripten::val &fileList)
++ :m_fileList(fileList)
++ {
++
++ }
++
++ int FileList::length() const
++ {
++ return m_fileList["length"].as<int>();
++ }
++
++ File FileList::item(int index) const
++ {
++ return File(m_fileList[index]);
++ }
++
++ File FileList::operator[](int index) const
++ {
++ return item(index);
++ }
++
++ emscripten::val FileList::val() const
++ {
++ return m_fileList;
++ }
++
++ ArrayBuffer FileReader::result() const
++ {
++ return ArrayBuffer(m_fileReader["result"]);
++ }
++
++ void FileReader::readAsArrayBuffer(const Blob &blob) const
++ {
++ m_fileReader.call<void>("readAsArrayBuffer", blob.m_blob);
++ }
++
++ void FileReader::onLoad(const std::function<void(emscripten::val)> &onLoad)
++ {
++ m_onLoad.reset();
++ m_onLoad = std::make_unique<EventCallback>(m_fileReader, "load", onLoad);
++ }
++
++ void FileReader::onError(const std::function<void(emscripten::val)> &onError)
++ {
++ m_onError.reset();
++ m_onError = std::make_unique<EventCallback>(m_fileReader, "error", onError);
++ }
++
++ void FileReader::onAbort(const std::function<void(emscripten::val)> &onAbort)
++ {
++ m_onAbort.reset();
++ m_onAbort = std::make_unique<EventCallback>(m_fileReader, "abort", onAbort);
++ }
++
++ emscripten::val FileReader::val() const
++ {
++ return m_fileReader;
++ }
++
++ // Constructs a Uint8Array which references the given emscripten::val, which must contain a JS Unit8Array
++ Uint8Array::Uint8Array(const emscripten::val &uint8Array)
++ : m_uint8Array(uint8Array)
++ {
++
++ }
++
++ // Constructs a Uint8Array which references an ArrayBuffer
++ Uint8Array::Uint8Array(const ArrayBuffer &buffer)
++ : m_uint8Array(Uint8Array::constructor_().new_(buffer.m_arrayBuffer))
++ {
++
++ }
++
++ // Constructs a Uint8Array which references a view into an ArrayBuffer
++ Uint8Array::Uint8Array(const ArrayBuffer &buffer, uint32_t offset, uint32_t length)
++ : m_uint8Array(Uint8Array::constructor_().new_(buffer.m_arrayBuffer, offset, length))
++ {
++
++ }
++
++ // Constructs a Uint8Array which references an area on the heap.
++ Uint8Array::Uint8Array(const char *buffer, uint32_t size)
++ :m_uint8Array(emscripten::typed_memory_view(size, buffer))
++ {
++
++ }
++
++ // Constructs a Uint8Array which allocates and references a new ArrayBuffer with the given size.
++ Uint8Array::Uint8Array(uint32_t size)
++ : m_uint8Array(Uint8Array::constructor_().new_(size))
++ {
++
++ }
++
++ ArrayBuffer Uint8Array::buffer() const
++ {
++ return ArrayBuffer(m_uint8Array["buffer"]);
++ }
++
++ uint32_t Uint8Array::length() const
++ {
++ return m_uint8Array["length"].as<uint32_t>();
++ }
++
++ void Uint8Array::set(const Uint8Array &source)
++ {
++ m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
++ }
++
++ Uint8Array Uint8Array::subarray(uint32_t begin, uint32_t end)
++ {
++ // Note: using uint64_t here errors with "Cannot convert a BigInt value to a number"
++ // (see JS BigInt and Number types). Use uint32_t for now.
++ return Uint8Array(m_uint8Array.call<emscripten::val>("subarray", begin, end));
++ }
++
++ // Copies the Uint8Array content to a destination on the heap
++ void Uint8Array::copyTo(char *destination) const
++ {
++ Uint8Array(destination, length()).set(*this);
++ }
++
++ // Copies the Uint8Array content to a destination QByteArray
++ QByteArray Uint8Array::copyToQByteArray() const
++ {
++ if (length() > std::numeric_limits<qsizetype>::max())
++ return QByteArray();
++
++ QByteArray destinationArray;
++ destinationArray.resize(length());
++ copyTo(destinationArray.data());
++ return destinationArray;
++ }
++
++ // Copies the Uint8Array content to a destination on the heap
++ void Uint8Array::copy(char *destination, const Uint8Array &source)
++ {
++ Uint8Array(destination, source.length()).set(source);
++ }
++
++ // Copies content from a source on the heap to a new Uint8Array object
++ Uint8Array Uint8Array::copyFrom(const char *buffer, uint32_t size)
++ {
++ Uint8Array contentCopy(size);
++ contentCopy.set(Uint8Array(buffer, size));
++ return contentCopy;
++ }
++
++ // Copies content from a QByteArray to a new Uint8Array object
++ Uint8Array Uint8Array::copyFrom(const QByteArray &buffer)
++ {
++ return copyFrom(buffer.constData(), buffer.size());
++ }
++
++ emscripten::val Uint8Array::val() const
++ {
++ return m_uint8Array;
++ }
++
++ emscripten::val Uint8Array::constructor_()
++ {
++ return emscripten::val::global("Uint8Array");
++ }
++
++ EventCallback::EventCallback(emscripten::val element, const std::string &name,
++ const std::function<void(emscripten::val)> &fn)
++ :QWasmEventHandler(element, name, fn)
++ {
++
++ }
++
++ size_t qstdweb::Promise::State::s_numInstances = 0;
++
++ //
++ // When a promise settles, all attached handlers will be called in
++ // the order they where added.
++ //
++ // In particular a finally handler will be called according to its
++ // position in the call chain. Which is not necessarily at the end,
++ //
++ // This makes cleanup difficult. If we cleanup to early, we will remove
++ // handlers before they have a chance to be called. This would be the
++ // case if we add a finally handler in the Promise constructor.
++ //
++ // For correct cleanup it is necessary that it happens after the
++ // last handler has been called.
++ //
++ // We choose to implement this by making sure the last handler
++ // is always a finally handler.
++ //
++ // In this case we have multiple finally handlers, so any called
++ // handler checks if it is the last handler to be called.
++ // If it is, the cleanup is performed, otherwise cleanup
++ // is delayed to the last handler.
++ //
++ // We could try to let the handlers cleanup themselves, but this
++ // only works for finally handlers. A then or catch handler is not
++ // necessarily called, and would not cleanup itself.
++ //
++ // We could try to let a (then,catch) pair cleanup both handlers,
++ // under the assumption that one of them will always be called.
++ // This does not work in the case that we do not have both handlers,
++ // or if the then handler throws (both should be called in this case).
++ //
++ Promise& Promise::addThenFunction(std::function<void(emscripten::val)> thenFunc)
++ {
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++
++ m_state->m_handlers.push_back(suspendResume->registerEventHandler(thenFunc));
++ m_state->m_promise =
++ m_state->m_promise.call<emscripten::val>(
++ "then",
++ suspendResume->jsEventHandlerAt(
++ m_state->m_handlers.back()));
++
++ addFinallyFunction([](){}); // Add a potential cleanup handler
++ return (*this);
++ }
++
++ Promise& Promise::addCatchFunction(std::function<void(emscripten::val)> catchFunc)
++ {
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++
++ m_state->m_handlers.push_back(suspendResume->registerEventHandler(catchFunc));
++ m_state->m_promise =
++ m_state->m_promise.call<emscripten::val>(
++ "catch",
++ suspendResume->jsEventHandlerAt(
++ m_state->m_handlers.back()));
++
++ addFinallyFunction([](){}); // Add a potential cleanup handler
++ return (*this);
++ }
++
++ Promise& Promise::addFinallyFunction(std::function<void()> finallyFunc)
++ {
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++
++ auto thisHandler = std::make_shared<uint32_t>((uint32_t)(-1));
++ auto state = m_state;
++
++ std::function<void(emscripten::val)> func =
++ [state, thisHandler, finallyFunc](emscripten::val element) {
++ Q_UNUSED(element);
++
++ finallyFunc();
++
++ // See comment at top, we can only do the cleanup
++ // if we are the last handler in the handler chain
++ if (state->m_handlers.back() == *thisHandler) {
++ auto guard = state; // removeEventHandler will remove also this function
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++ for (int i = 0; i < guard->m_handlers.size(); ++i) {
++ suspendResume->removeEventHandler(guard->m_handlers[i]);
++ guard->m_handlers[i] = (uint32_t)(-1);
++ }
++ }
++ };
++
++ *thisHandler = suspendResume->registerEventHandler(func);
++ m_state->m_handlers.push_back(*thisHandler);
++ m_state->m_promise =
++ m_state->m_promise.call<emscripten::val>(
++ "finally",
++ suspendResume->jsEventHandlerAt(
++ m_state->m_handlers.back()));
++
++ return (*this);
++ }
++
++ void Promise::suspendExclusive()
++ {
++ Promise::suspendExclusive(m_state->m_handlers);
++ }
++
++ // Blocks/suspends until the promise settles. The returned AwaitResult
++ // carries either the value (on resolve) or the error (on reject); the
++ // other field is left as emscripten::val::undefined().
++ Promise::AwaitResult Promise::awaitExclusive()
++ {
++ AwaitResult r;
++ addThenFunction ([&r](emscripten::val v) { r.value = v; });
++ addCatchFunction([&r](emscripten::val e) { r.error = e; });
++ suspendExclusive();
++ return r;
++ }
++
++ emscripten::val Promise::getPromise() const
++ {
++ return m_state->m_promise;
++ }
++
++ uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers)
++ {
++ Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc,
++ "Promise::adoptPromise", "must provide at least one callback function");
++
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++
++ // Registers a possibly-empty callback with suspendresumecontrol. Returns
++ // the the handler index if there was a valid callback, or nullopt.
++ auto registerCallback = [suspendResume](std::function<void(emscripten::val)> cb) -> std::optional<uint32_t>{
++ if (!cb)
++ return std::nullopt;
++ return std::optional<uint32_t>{suspendResume->registerEventHandler(std::move(cb))};
++ };
++
++ // Register callbacks with suspendresumecontrol, so that it can
++ // resume the wasm instance when the promise resolves. The finally
++ // callback is sepecial, since we remove the event handlers there
++ // as cleanup, including the event handler for the cleanup function
++ // itself.
++ std::optional<uint32_t> thenIndex = registerCallback(std::move(callbacks.thenFunc));
++ std::optional<uint32_t> catchIndex = registerCallback(std::move(callbacks.catchFunc));
++ std::shared_ptr<uint32_t> finallyIndex = std::make_shared<uint32_t>();;
++ auto finallyFunc = callbacks.finallyFunc;
++
++ // 'Finally' callback which performs clean-up and calls the user-provided finally.
++ auto finally = [thenIndex, catchIndex, finallyIndex, finallyFunc](emscripten::val){
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++
++ // Clean up event handlers
++ if (thenIndex)
++ suspendResume->removeEventHandler(*thenIndex);
++ if (catchIndex)
++ suspendResume->removeEventHandler(*catchIndex);
++
++ // Call user finally
++ if (finallyFunc)
++ finallyFunc();
++
++ // Remove the finally (this) event handler last
++ suspendResume->removeEventHandler(*finallyIndex);
++ };
++
++ *finallyIndex = suspendResume->registerEventHandler(std::move(finally));
++
++ // Set handlers on the promise
++ if (thenIndex)
++ promise =
++ promise.call<emscripten::val>("then", suspendResume->jsEventHandlerAt(*thenIndex));
++
++ if (catchIndex)
++ promise = promise.call<emscripten::val>("catch",
++ suspendResume->jsEventHandlerAt(*catchIndex));
++
++ promise = promise.call<emscripten::val>("finally",
++ suspendResume->jsEventHandlerAt(*finallyIndex));
++
++ if (handlers) {
++ if (thenIndex)
++ handlers->push_back(*thenIndex);
++ if (catchIndex)
++ handlers->push_back(*catchIndex);
++ handlers->push_back(*finallyIndex);
++ }
++ return *finallyIndex;
++ }
++
++ void Promise::suspendExclusive(QList<uint32_t> handlerIndices)
++ {
++ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
++ suspendResume->suspendExclusive(handlerIndices);
++ suspendResume->sendPendingEvents();
++ }
++
++ void Promise::all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks)
++ {
++ auto arr = emscripten::val::array(promises);
++ auto all = val::global("Promise").call<emscripten::val>("all", arr);
++ adoptPromise(all, callbacks);
++ }
++
++ // Asyncify and thread blocking: Normally, it's not possible to block the main
++ // thread, except if asyncify is enabled. Secondary threads can always block.
++ //
++ // haveAsyncify(): returns true if the main thread can block on QEventLoop::exec(),
++ // if either asyncify 1 or 2 (JSPI) is available.
++ //
++ // haveJspi(): returns true if asyncify 2 (JSPI) is available.
++ //
++ // canBlockCallingThread(): returns true if the calling thread can block on
++ // QEventLoop::exec(), using either asyncify or as a seconarday thread.
++ bool haveJspi()
++ {
++ static bool HaveJspi = jsHaveJspi();
++ return HaveJspi;
++ }
++
++ bool haveAsyncify()
++ {
++ static bool HaveAsyncify = jsHaveAsyncify() || haveJspi();
++ return HaveAsyncify;
++ }
++
++ bool canBlockCallingThread()
++ {
++ return haveAsyncify() || !emscripten_is_main_runtime_thread();
++ }
++
++ BlobIODevice::BlobIODevice(Blob blob)
++ : m_blob(blob)
++ {
++
++ }
++
++ bool BlobIODevice::open(QIODevice::OpenMode mode)
++ {
++ if (mode.testFlag(QIODevice::WriteOnly))
++ return false;
++ return QIODevice::open(mode);
++ }
++
++ bool BlobIODevice::isSequential() const
++ {
++ return false;
++ }
++
++ qint64 BlobIODevice::size() const
++ {
++ return m_blob.size();
++ }
++
++ bool BlobIODevice::seek(qint64 pos)
++ {
++ if (pos >= size())
++ return false;
++ return QIODevice::seek(pos);
++ }
++
++ qint64 BlobIODevice::readData(char *data, qint64 maxSize)
++ {
++ uint64_t begin = QIODevice::pos();
++ uint64_t end = std::min<uint64_t>(begin + maxSize, size());
++ uint64_t size = end - begin;
++ if (size > 0) {
++ qstdweb::ArrayBuffer buffer = m_blob.slice(begin, end).arrayBuffer_sync();
++ qstdweb::Uint8Array(buffer).copyTo(data);
++ }
++ return size;
++ }
++
++ qint64 BlobIODevice::writeData(const char *, qint64)
++ {
++ Q_UNREACHABLE();
++ }
++
++ Uint8ArrayIODevice::Uint8ArrayIODevice(Uint8Array array)
++ : m_array(array)
++ {
++
++ }
++
++ bool Uint8ArrayIODevice::open(QIODevice::OpenMode mode)
++ {
++ return QIODevice::open(mode);
++ }
++
++ bool Uint8ArrayIODevice::isSequential() const
++ {
++ return false;
++ }
++
++ qint64 Uint8ArrayIODevice::size() const
++ {
++ return m_array.length();
++ }
++
++ bool Uint8ArrayIODevice::seek(qint64 pos)
++ {
++ if (pos >= size())
++ return false;
++ return QIODevice::seek(pos);
++ }
++
++ qint64 Uint8ArrayIODevice::readData(char *data, qint64 maxSize)
++ {
++ uint64_t begin = QIODevice::pos();
++ uint64_t end = std::min<uint64_t>(begin + maxSize, size());
++ uint64_t size = end - begin;
++ if (size > 0)
++ m_array.subarray(begin, end).copyTo(data);
++ return size;
++ }
++
++ qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
++ {
++ uint64_t begin = QIODevice::pos();
++ uint64_t end = std::min<uint64_t>(begin + maxSize, size());
++ uint64_t size = end - begin;
++ if (size > 0)
++ m_array.subarray(begin, end).set(Uint8Array(data, size));
++ return size;
++ }
++
++ FileSystemWritableFileStreamIODevice::FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream)
++ : m_stream(std::move(stream))
++ {
++ }
++
++ bool FileSystemWritableFileStreamIODevice::open(QIODevice::OpenMode mode)
++ {
++ if (mode.testFlag(QIODevice::ReadOnly))
++ return false;
++ return QIODevice::open(mode);
++ }
++
++ void FileSystemWritableFileStreamIODevice::close()
++ {
++ if (!isOpen()) {
++ QIODevice::close();
++ return;
++ }
++
++ QList<uint32_t> handlers;
++ Promise::make(handlers, m_stream.val(), QStringLiteral("close"), {
++ .thenFunc = [](emscripten::val) {
++ }
++ });
++ Promise::suspendExclusive(handlers);
++
++ QIODevice::close();
++ }
++
++ bool FileSystemWritableFileStreamIODevice::isSequential() const
++ {
++ return false;
++ }
++
++ qint64 FileSystemWritableFileStreamIODevice::size() const
++ {
++ return m_size;
++ }
++
++ bool FileSystemWritableFileStreamIODevice::seek(qint64 pos)
++ {
++ bool success = false;
++
++ emscripten::val seekParams = emscripten::val::object();
++ seekParams.set("type", std::string("seek"));
++ seekParams.set("position", static_cast<double>(pos));
++ QList<uint32_t> handlers;
++ Promise::make(handlers, m_stream.val(), QStringLiteral("write"), {
++ .thenFunc = [&success](emscripten::val) {
++ success = true;
++ },
++ .catchFunc = [](emscripten::val) {
++ }
++ }, seekParams);
++ Promise::suspendExclusive(handlers);
++
++ if (!success)
++ return false;
++
++ return QIODevice::seek(pos);
++ }
++
++ qint64 FileSystemWritableFileStreamIODevice::readData(char *, qint64)
++ {
++ Q_UNREACHABLE();
++ }
++
++ qint64 FileSystemWritableFileStreamIODevice::writeData(const char *data, qint64 size)
++ {
++ bool success = false;
++
++ Uint8Array array = Uint8Array::copyFrom(data, size);
++ QList<uint32_t> handlers;
++ Promise::make(handlers, m_stream.val(), QStringLiteral("write"), {
++ .thenFunc = [&success](emscripten::val) {
++ success = true;
++ },
++ .catchFunc = [](emscripten::val) {
++ }
++ }, array.val());
++ Promise::suspendExclusive(handlers);
++
++ if (success) {
++ qint64 newPos = pos() + size;
++ m_size = std::max(m_size, newPos);
++ return size;
++ }
++ return -1;
++ }
++
++ FileSystemWritableFileStream::FileSystemWritableFileStream(const emscripten::val &writableStream)
++ : m_writableStream(writableStream)
++ {
++ }
++
++ emscripten::val FileSystemWritableFileStream::val() const
++ {
++ return m_writableStream;
++ }
++
++ FileSystemFileHandle::FileSystemFileHandle(const emscripten::val &fileHandle)
++ : m_fileHandle(fileHandle)
++ {
++ }
++
++ std::string FileSystemFileHandle::name() const
++ {
++ return m_fileHandle["name"].as<std::string>();
++ }
++
++ std::string FileSystemFileHandle::kind() const
++ {
++ return m_fileHandle["kind"].as<std::string>();
++ }
++
++ emscripten::val FileSystemFileHandle::val() const
++ {
++ return m_fileHandle;
++ }
++
++ FileSystemFileIODevice::FileSystemFileIODevice(FileSystemFileHandle fileHandle)
++ : m_fileHandle(fileHandle)
++ {
++ }
++
++ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
++ {
++ if (isOpen())
++ return false;
++
++ // Read mode: get the File and create a BlobIODevice
++ if (mode & QIODevice::ReadOnly) {
++ File file;
++ bool success = false;
++
++ QList<uint32_t> handlers;
++ Promise::make(handlers, m_fileHandle.val(), QStringLiteral("getFile"), {
++ .thenFunc = [&file, &success](emscripten::val fileVal) {
++ file = File(fileVal);
++ success = true;
++ },
++ .catchFunc = [](emscripten::val) {
++ }
++ });
++ Promise::suspendExclusive(handlers);
++
++ if (success) {
++ m_blobDevice = std::make_unique<BlobIODevice>(file.slice(0, file.size()));
++ m_size = file.size();
++
++ if (!m_blobDevice->open(mode))
++ return false;
++ } else {
++ return false;
++ }
++ }
++
++ // Write mode: create a writable stream
++ if (mode & QIODevice::WriteOnly) {
++ FileSystemWritableFileStream writableStream;
++ bool success = false;
++
++ QList<uint32_t> handlers;
++ Promise::make(handlers, m_fileHandle.val(), QStringLiteral("createWritable"), {
++ .thenFunc = [&writableStream, &success](emscripten::val writable) {
++ writableStream = FileSystemWritableFileStream(writable);
++ success = true;
++ },
++ .catchFunc = [](emscripten::val) {
++ }
++ });
++ Promise::suspendExclusive(handlers);
++
++ if (success) {
++ m_writableDevice = std::make_unique<FileSystemWritableFileStreamIODevice>(writableStream);
++ if (!m_writableDevice->open(mode))
++ return false;
++ } else {
++ return false;
++ }
++ }
++
++ return QIODevice::open(mode);
++ }
++
++ void FileSystemFileIODevice::close()
++ {
++ if (!isOpen()) {
++ QIODevice::close();
++ return;
++ }
++
++ if (m_writableDevice) {
++ m_writableDevice->close();
++ m_writableDevice.reset();
++ }
++ if (m_blobDevice) {
++ m_blobDevice->close();
++ m_blobDevice.reset();
++ }
++
++ QIODevice::close();
++ }
++
++ bool FileSystemFileIODevice::isSequential() const
++ {
++ return false;
++ }
++
++ qint64 FileSystemFileIODevice::size() const
++ {
++ return m_size;
++ }
++
++ bool FileSystemFileIODevice::seek(qint64 pos)
++ {
++ if (m_blobDevice) {
++ if (!m_blobDevice->seek(pos))
++ return false;
++ }
++ if (m_writableDevice) {
++ if (!m_writableDevice->seek(pos))
++ return false;
++ }
++ return QIODevice::seek(pos);
++ }
++
++ qint64 FileSystemFileIODevice::readData(char *data, qint64 maxSize)
++ {
++ if (!m_blobDevice)
++ return -1;
++
++ return m_blobDevice->read(data, maxSize);
++ }
++
++ qint64 FileSystemFileIODevice::writeData(const char *data, qint64 size)
++ {
++ if (!m_writableDevice)
++ return -1;
++
++ qint64 written = m_writableDevice->write(data, size);
++ if (written > 0) {
++ qint64 newPos = pos() + written;
++ m_size = std::max(m_size, newPos);
++ }
++ return written;
++ }
++
++ } // namespace qstdweb
++
++ QT_END_NAMESPACE