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.cpp437
1 files changed, 351 insertions, 86 deletions
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index 486bfaf485..75e76a6806 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -5,8 +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>
@@ -14,10 +19,74 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
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;
+};
+
enum class CallbackType {
Then,
Catch,
@@ -69,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:
@@ -254,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(
@@ -295,7 +361,19 @@ void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks ca
registerPromise(std::move(allocation), std::move(callbacks));
});
}
-} // namespace
+#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)
{
@@ -316,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;
}
@@ -327,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;
}
@@ -355,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)));
@@ -377,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
@@ -425,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)
{
@@ -468,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;
}
@@ -514,7 +628,7 @@ Uint8Array::Uint8Array(const ArrayBuffer &buffer, uint32_t offset, uint32_t leng
// Constructs a Uint8Array which references an area on the heap.
Uint8Array::Uint8Array(const char *buffer, uint32_t size)
-:m_uint8Array(Uint8Array::constructor_().new_(Uint8Array::heap().buffer().m_arrayBuffer, uint32_t(buffer), size))
+:m_uint8Array(Uint8Array::constructor_().new_(Uint8Array::heap().buffer().m_arrayBuffer, uintptr_t(buffer), size))
{
}
@@ -541,12 +655,31 @@ 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)
{
@@ -561,7 +694,13 @@ Uint8Array Uint8Array::copyFrom(const char *buffer, uint32_t size)
return contentCopy;
}
-emscripten::val Uint8Array::val()
+// 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;
}
@@ -576,41 +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_handler(std::make_unique<std::function<void(emscripten::val)>>(handler))
{
- 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["target"];
- std::string eventName = event["type"].as<std::string>();
- EventCallback *that = reinterpret_cast<EventCallback *>(target[contextPropertyName(eventName).c_str()].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 {
@@ -667,6 +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() || 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