diff options
Diffstat (limited to 'src/corelib/platform/wasm')
-rw-r--r-- | src/corelib/platform/wasm/qstdweb.cpp | 393 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qstdweb_p.h | 158 |
2 files changed, 447 insertions, 104 deletions
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 69ed8fe34f..75e76a6806 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -5,9 +5,13 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qfile.h> +#include <QtCore/qmimedata.h> + #include <emscripten/bind.h> #include <emscripten/emscripten.h> #include <emscripten/html5.h> +#include <emscripten/threading.h> + #include <cstdint> #include <iostream> @@ -15,6 +19,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + namespace qstdweb { static void usePotentialyUnusedSymbols() @@ -28,12 +34,59 @@ static void usePotentialyUnusedSymbols() // called at runtime. volatile bool doIt = false; if (doIt) - emscripten_set_wheel_callback(NULL, 0, 0, NULL); + 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; +}; + enum class CallbackType { Then, Catch, @@ -85,11 +138,12 @@ public: "catch", emscripten::val::module_property(thunkName(CallbackType::Catch, id()).data())); } - if (callbacks.finallyFunc) { - target = target.call<val>( - "finally", - emscripten::val::module_property(thunkName(CallbackType::Finally, id()).data())); - } + // Guarantee the invocation of at least one callback by always + // registering 'finally'. This is required by WebPromiseManager + // design + target = target.call<val>( + "finally", emscripten::val::module_property( + thunkName(CallbackType::Finally, id()).data())); } private: @@ -270,25 +324,21 @@ void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, ems auto* promiseState = &m_promiseRegistry[context]; auto* callbacks = &promiseState->callbacks; - bool expectingOtherCallbacks; switch (type) { case CallbackType::Then: callbacks->thenFunc(result); - // At this point, if there is no finally function, we are sure that the Catch callback won't be issued. - expectingOtherCallbacks = !!callbacks->finallyFunc; break; case CallbackType::Catch: callbacks->catchFunc(result); - expectingOtherCallbacks = !!callbacks->finallyFunc; break; case CallbackType::Finally: - callbacks->finallyFunc(); - expectingOtherCallbacks = false; + // Final callback may be empty, used solely for promise unregistration + if (callbacks->finallyFunc) { + callbacks->finallyFunc(); + } + unregisterPromise(context); break; - } - - if (!expectingOtherCallbacks) - unregisterPromise(context); + } } void WebPromiseManager::registerPromise( @@ -314,13 +364,15 @@ void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks ca #if defined(QT_STATIC) EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; }); +EM_JS(bool, jsHaveJspi, (), + { return typeof Asyncify !== "undefined" && !!Asyncify.makeAsyncFunction && !!WebAssembly.Function; }); #else bool jsHaveAsyncify() { return false; } +bool jsHaveJspi() { return false; } #endif - } // namespace ArrayBuffer::ArrayBuffer(uint32_t size) @@ -342,7 +394,12 @@ uint32_t ArrayBuffer::byteLength() const return m_arrayBuffer["byteLength"].as<uint32_t>(); } -emscripten::val ArrayBuffer::val() +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; } @@ -353,24 +410,55 @@ Blob::Blob(const emscripten::val &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>(); } -// Copies content from the given buffer into a Blob object -Blob Blob::copyFrom(const char *buffer, uint32_t size) +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","application/octet-stream"); + type.set("type", std::move(mimeType)); return Blob(emscripten::val::global("Blob").new_(contentArray, type)); } -emscripten::val Blob::val() +// 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 +{ + QEventLoop loop; + emscripten::val buffer; + qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), { + .thenFunc = [&loop, &buffer](emscripten::val arrayBuffer) { + buffer = arrayBuffer; + loop.quit(); + } + }); + loop.exec(); + return ArrayBuffer(buffer); +} + +emscripten::val Blob::val() const { return m_blob; } @@ -381,6 +469,17 @@ File::File(const emscripten::val &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))); @@ -403,47 +502,17 @@ std::string Blob::type() const // 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, const std::function<void ()> &completed) const +void File::stream(uint32_t offset, uint32_t length, char *buffer, + std::function<void()> completed) const { - // Read file in chunks in order to avoid holding two copies in memory at the same time - const uint32_t chunkSize = 256 * 1024; - const uint32_t end = offset + length; - // assert end < file.size - auto fileReader = std::make_shared<qstdweb::FileReader>(); - - // "this" is valid now, but may not be by the time the chunkCompleted callback - // below is made. Make a copy of the file handle. - const File fileHandle = *this; - auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>(); - *chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable { - - // 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 - uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end); - uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end); - if (nextChunkBegin == end) { - completed(); - chunkCompleted.reset(); - return; - } - char *nextChunkBuffer = chunkBuffer + result.byteLength(); - fileReader->onLoad([=](emscripten::val) { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); }); - qstdweb::Blob blob = fileHandle.slice(nextChunkBegin, nextChunkEnd); - fileReader->readAsArrayBuffer(blob); - }; - - // Read first chunk. First iteration is a dummy iteration with no available data. - (*chunkCompleted)(offset, buffer); + 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, const std::function<void ()> &completed) const +void File::stream(char *buffer, std::function<void()> completed) const { - stream(0, size(), buffer, completed); + stream(0, size(), buffer, std::move(completed)); } std::string File::type() const @@ -451,11 +520,27 @@ std::string File::type() const return m_file["type"].as<std::string>(); } -emscripten::val File::val() +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) { @@ -494,20 +579,23 @@ void FileReader::readAsArrayBuffer(const Blob &blob) const void FileReader::onLoad(const std::function<void(emscripten::val)> &onLoad) { - m_onLoad.reset(new EventCallback(m_fileReader, "load", 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(new EventCallback(m_fileReader, "error", 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(new EventCallback(m_fileReader, "abort", onAbort)); + m_onAbort.reset(); + m_onAbort = std::make_unique<EventCallback>(m_fileReader, "abort", onAbort); } -emscripten::val FileReader::val() +emscripten::val FileReader::val() const { return m_fileReader; } @@ -567,6 +655,13 @@ 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 { @@ -605,7 +700,7 @@ Uint8Array Uint8Array::copyFrom(const QByteArray &buffer) return copyFrom(buffer.constData(), buffer.size()); } -emscripten::val Uint8Array::val() +emscripten::val Uint8Array::val() const { return m_uint8Array; } @@ -620,45 +715,45 @@ emscripten::val Uint8Array::constructor_() return emscripten::val::global("Uint8Array"); } +class EventListener { +public: + EventListener(uintptr_t handler) + :m_handler(handler) + { + + } + + // Special function - addEventListender() allows adding an object with a + // handleEvent() function which eceives the event. + void handleEvent(emscripten::val event) { + auto handlerPtr = reinterpret_cast<std::function<void(emscripten::val)> *>(m_handler); + (*handlerPtr)(event); + } + + uintptr_t m_handler; +}; + // Registers a callback function for a named event on the given element. The event // name must be the name as returned by the Event.type property: e.g. "load", "error". EventCallback::~EventCallback() { - // Clean up if this instance's callback is still installed on the element - if (m_element[contextPropertyName(m_eventName).c_str()].as<intptr_t>() == intptr_t(this)) { - m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val::undefined()); - m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::undefined()); - } + m_element.call<void>("removeEventListener", m_eventName, m_eventListener); } -EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &fn) +EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &handler) :m_element(element) ,m_eventName(name) - ,m_fn(fn) -{ - m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val(intptr_t(this))); - m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::module_property("qtStdWebEventCallbackActivate")); -} - -void EventCallback::activate(emscripten::val event) + ,m_handler(std::make_unique<std::function<void(emscripten::val)>>(handler)) { - emscripten::val target = event["target"]; - std::string eventName = event["type"].as<std::string>(); - emscripten::val property = target[contextPropertyName(eventName)]; - // This might happen when the event bubbles - if (property.isUndefined()) - return; - EventCallback *that = reinterpret_cast<EventCallback *>(property.as<intptr_t>()); - that->m_fn(event); -} - -std::string EventCallback::contextPropertyName(const std::string &eventName) -{ - return std::string("data-qtEventCallbackContext") + eventName; + uintptr_t handlerUint = reinterpret_cast<uintptr_t>(m_handler.get()); // FIXME: pass pointer directly instead + m_eventListener = emscripten::val::module_property("QtEventListener").new_(handlerUint); + m_element.call<void>("addEventListener", m_eventName, m_eventListener); } EMSCRIPTEN_BINDINGS(qtStdwebCalback) { - emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate); + emscripten::class_<EventListener>("QtEventListener") + .constructor<uintptr_t>() + .function("handleEvent", &EventListener::handleEvent); } namespace Promise { @@ -715,12 +810,128 @@ namespace Promise { } } +// 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(); + 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; +} + } // namespace qstdweb QT_END_NAMESPACE diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index b3fc8a95f7..a3b5bd5b6b 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -16,15 +16,28 @@ // #include <private/qglobal_p.h> +#include <QtCore/qglobal.h> +#include "QtCore/qhash.h" +#include "QtCore/qiodevice.h" + #include <emscripten/val.h> + #include <cstdint> #include <functional> -#include "initializer_list" -#include <QtCore/qglobal.h> -#include "QtCore/qhash.h" +#include <initializer_list> +#include <memory> +#include <string> +#include <utility> + +#if QT_CONFIG(thread) +#include <emscripten/proxying.h> +#include <emscripten/threading.h> +#endif // #if QT_CONFIG(thread) QT_BEGIN_NAMESPACE +class QMimeData; + namespace qstdweb { extern const char makeContextfulPromiseFunctionName[]; @@ -46,7 +59,8 @@ namespace qstdweb { explicit ArrayBuffer(uint32_t size); explicit ArrayBuffer(const emscripten::val &arrayBuffer); uint32_t byteLength() const; - emscripten::val val(); + ArrayBuffer slice(uint32_t begin, uint32_t end) const; + emscripten::val val() const; private: friend class Uint8Array; @@ -56,9 +70,13 @@ namespace qstdweb { class Q_CORE_EXPORT Blob { public: explicit Blob(const emscripten::val &blob); + static Blob fromArrayBuffer(const ArrayBuffer &arrayBuffer); uint32_t size() const; + static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType); static Blob copyFrom(const char *buffer, uint32_t size); - emscripten::val val(); + Blob slice(uint32_t begin, uint32_t end) const; + ArrayBuffer arrayBuffer_sync() const; + emscripten::val val() const; std::string type() const; private: @@ -70,19 +88,49 @@ namespace qstdweb { public: File() = default; explicit File(const emscripten::val &file); + ~File(); + + File(const File &other); + File(File &&other); + File &operator=(const File &other); + File &operator=(File &&other); Blob slice(uint64_t begin, uint64_t end) const; std::string name() const; uint64_t size() const; std::string type() const; - void stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const; - void stream(char *buffer, const std::function<void ()> &completed) const; - emscripten::val val(); + void stream(uint32_t offset, uint32_t length, char *buffer, + std::function<void()> completed) const; + void stream(char *buffer, std::function<void()> completed) const; + emscripten::val val() const; + void fileUrlRegistration() const; + const QString &fileUrlPath() const { return m_urlPath; } + emscripten::val file() const { return m_file; } private: emscripten::val m_file = emscripten::val::undefined(); + QString m_urlPath; + }; + + class Q_CORE_EXPORT FileUrlRegistration + { + public: + explicit FileUrlRegistration(File file); + ~FileUrlRegistration(); + + FileUrlRegistration(const FileUrlRegistration &other) = delete; + FileUrlRegistration(FileUrlRegistration &&other); + FileUrlRegistration &operator=(const FileUrlRegistration &other) = delete; + FileUrlRegistration &operator=(FileUrlRegistration &&other); + + const QString &path() const { return m_path; } + + private: + QString m_path; }; + using FileUrlRegistrations = std::vector<std::unique_ptr<FileUrlRegistration>>; + class Q_CORE_EXPORT FileList { public: FileList() = default; @@ -105,7 +153,7 @@ namespace qstdweb { void onLoad(const std::function<void(emscripten::val)> &onLoad); void onError(const std::function<void(emscripten::val)> &onError); void onAbort(const std::function<void(emscripten::val)> &onAbort); - emscripten::val val(); + emscripten::val val() const; private: emscripten::val m_fileReader = emscripten::val::global("FileReader").new_(); @@ -126,6 +174,7 @@ namespace qstdweb { ArrayBuffer buffer() const; uint32_t length() const; void set(const Uint8Array &source); + Uint8Array subarray(uint32_t begin, uint32_t end); void copyTo(char *destination) const; QByteArray copyToQByteArray() const; @@ -133,7 +182,7 @@ namespace qstdweb { static void copy(char *destination, const Uint8Array &source); static Uint8Array copyFrom(const char *buffer, uint32_t size); static Uint8Array copyFrom(const QByteArray &buffer); - emscripten::val val(); + emscripten::val val() const; private: static emscripten::val heap_(); @@ -150,13 +199,12 @@ namespace qstdweb { EventCallback& operator=(EventCallback const&) = delete; EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &fn); - static void activate(emscripten::val event); private: - static std::string contextPropertyName(const std::string &eventName); emscripten::val m_element = emscripten::val::undefined(); std::string m_eventName; - std::function<void(emscripten::val)> m_fn; + std::unique_ptr<std::function<void(emscripten::val)>> m_handler; + emscripten::val m_eventListener = emscripten::val::undefined(); }; struct PromiseCallbacks @@ -187,6 +235,46 @@ namespace qstdweb { void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); }; + template<class F> + decltype(auto) bindForever(F wrappedCallback) + { + return wrappedCallback; + } + + class Q_CORE_EXPORT BlobIODevice: public QIODevice + { + public: + BlobIODevice(Blob blob); + bool open(QIODeviceBase::OpenMode mode) override; + bool isSequential() const override; + qint64 size() const override; + bool seek(qint64 pos) override; + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *, qint64) override; + + private: + Blob m_blob; + }; + + class Uint8ArrayIODevice: public QIODevice + { + public: + Uint8ArrayIODevice(Uint8Array array); + bool open(QIODevice::OpenMode mode) override; + bool isSequential() const override; + qint64 size() const override; + bool seek(qint64 pos) override; + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 size) override; + + private: + Uint8Array m_array; + }; + inline emscripten::val window() { static emscripten::val savedWindow = emscripten::val::global("window"); @@ -194,6 +282,50 @@ namespace qstdweb { } bool haveAsyncify(); + bool Q_CORE_EXPORT haveJspi(); + bool canBlockCallingThread(); + + struct CancellationFlag + { + }; + +#if QT_CONFIG(thread) + template<class T> + T proxyCall(std::function<T()> task, emscripten::ProxyingQueue *queue) + { + T result; + queue->proxySync(emscripten_main_runtime_thread_id(), + [task, result = &result]() { *result = task(); }); + return result; + } + + template<> + inline void proxyCall<void>(std::function<void()> task, emscripten::ProxyingQueue *queue) + { + queue->proxySync(emscripten_main_runtime_thread_id(), task); + } + + template<class T> + T runTaskOnMainThread(std::function<T()> task, emscripten::ProxyingQueue *queue) + { + return emscripten_is_main_runtime_thread() ? task() : proxyCall<T>(std::move(task), queue); + } + + template<class T> + T runTaskOnMainThread(std::function<T()> task) + { + emscripten::ProxyingQueue singleUseQueue; + return runTaskOnMainThread<T>(task, &singleUseQueue); + } + +#else + template<class T> + T runTaskOnMainThread(std::function<T()> task) + { + return task(); + } +#endif // QT_CONFIG(thread) + } QT_END_NAMESPACE |