summaryrefslogtreecommitdiffstats
path: root/src/corelib/platform/wasm/qstdweb.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/platform/wasm/qstdweb.cpp')
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp392
1 files changed, 231 insertions, 161 deletions
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index 183d2e0016..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,6 +410,13 @@ 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>();
@@ -529,7 +439,26 @@ Blob Blob::copyFrom(const char *buffer, uint32_t size)
return copyFrom(buffer, size, "application/octet-stream");
}
-emscripten::val Blob::val()
+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;
}
@@ -540,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)));
@@ -580,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)
{
@@ -639,7 +595,7 @@ void FileReader::onAbort(const std::function<void(emscripten::val)> &onAbort)
m_onAbort = std::make_unique<EventCallback>(m_fileReader, "abort", onAbort);
}
-emscripten::val FileReader::val()
+emscripten::val FileReader::val() const
{
return m_fileReader;
}
@@ -699,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
{
@@ -737,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;
}
@@ -752,47 +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)
-{
- Q_ASSERT_X(m_element[contextPropertyName(m_eventName)].isUndefined(), Q_FUNC_INFO,
- "Only one event callback of type currently supported with EventCallback");
- 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["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)
-{
- 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 {
@@ -849,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