diff options
Diffstat (limited to 'src/corelib/platform/wasm/qstdweb.cpp')
-rw-r--r-- | src/corelib/platform/wasm/qstdweb.cpp | 410 |
1 files changed, 245 insertions, 165 deletions
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 0968509150..75e76a6806 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -10,6 +10,8 @@ #include <emscripten/bind.h> #include <emscripten/emscripten.h> #include <emscripten/html5.h> +#include <emscripten/threading.h> + #include <cstdint> #include <iostream> @@ -17,6 +19,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + namespace qstdweb { static void usePotentialyUnusedSymbols() @@ -30,7 +34,7 @@ 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) @@ -134,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: @@ -319,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( @@ -363,118 +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 - -struct DataTransferReader -{ -public: - using DoneCallback = std::function<void(std::unique_ptr<QMimeData>)>; - - static std::shared_ptr<CancellationFlag> read(emscripten::val webDataTransfer, - std::function<QVariant(QByteArray)> imageReader, - DoneCallback onCompleted) - { - auto cancellationFlag = std::make_shared<CancellationFlag>(); - (new DataTransferReader(std::move(onCompleted), std::move(imageReader), cancellationFlag)) - ->read(webDataTransfer); - return cancellationFlag; - } - - ~DataTransferReader() = default; - -private: - DataTransferReader(DoneCallback onCompleted, std::function<QVariant(QByteArray)> imageReader, - std::shared_ptr<CancellationFlag> cancellationFlag) - : mimeData(std::make_unique<QMimeData>()), - imageReader(std::move(imageReader)), - onCompleted(std::move(onCompleted)), - cancellationFlag(cancellationFlag) - { - } - - void read(emscripten::val webDataTransfer) - { - enum class ItemKind { - File, - String, - }; - - const auto items = webDataTransfer["items"]; - for (int i = 0; i < items["length"].as<int>(); ++i) { - const auto item = items[i]; - const auto itemKind = - item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File; - const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>()); - - switch (itemKind) { - case ItemKind::File: { - ++fileCount; - - qstdweb::File file(item.call<emscripten::val>("getAsFile")); - - QByteArray fileContent(file.size(), Qt::Uninitialized); - file.stream(fileContent.data(), [this, itemMimeType, fileContent]() { - if (!fileContent.isEmpty()) { - if (itemMimeType.startsWith("image/")) { - mimeData->setImageData(imageReader(fileContent)); - } else { - mimeData->setData(itemMimeType, fileContent.data()); - } - } - ++doneCount; - onFileRead(); - }); - break; - } - case ItemKind::String: - if (itemMimeType.contains("STRING", Qt::CaseSensitive) - || itemMimeType.contains("TEXT", Qt::CaseSensitive)) { - break; - } - QString a; - const QString data = QString::fromJsString(webDataTransfer.call<emscripten::val>( - "getData", emscripten::val(itemMimeType.toStdString()))); - - if (!data.isEmpty()) { - if (itemMimeType == "text/html") - mimeData->setHtml(data); - else if (itemMimeType.isEmpty() || itemMimeType == "text/plain") - mimeData->setText(data); // the type can be empty - else - mimeData->setData(itemMimeType, data.toLocal8Bit()); - } - break; - } - } - - onFileRead(); - } - - void onFileRead() - { - Q_ASSERT(doneCount <= fileCount); - if (doneCount < fileCount) - return; - - std::unique_ptr<DataTransferReader> deleteThisLater(this); - if (!cancellationFlag.expired()) - onCompleted(std::move(mimeData)); - } - - int fileCount = 0; - int doneCount = 0; - std::unique_ptr<QMimeData> mimeData; - std::function<QVariant(QByteArray)> imageReader; - DoneCallback onCompleted; - - std::weak_ptr<CancellationFlag> cancellationFlag; -}; - } // namespace ArrayBuffer::ArrayBuffer(uint32_t size) @@ -496,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; } @@ -507,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; } @@ -535,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))); @@ -575,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) { @@ -618,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; } @@ -691,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 { @@ -729,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; } @@ -744,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) -{ - emscripten::val target = event["currentTarget"]; - 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) + ,m_handler(std::make_unique<std::function<void(emscripten::val)>>(handler)) { - 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 { @@ -839,17 +810,126 @@ 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; } -std::shared_ptr<CancellationFlag> -readDataTransfer(emscripten::val webDataTransfer, std::function<QVariant(QByteArray)> imageReader, - std::function<void(std::unique_ptr<QMimeData>)> onDone) +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) { - return DataTransferReader::read(webDataTransfer, std::move(imageReader), std::move(onDone)); + 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 |