summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/corelib/CMakeLists.txt15
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp242
-rw-r--r--src/corelib/platform/wasm/qstdweb_p.h32
-rw-r--r--src/corelib/platform/wasm/qtcontextfulpromise_injection.js32
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp109
5 files changed, 331 insertions, 99 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index f89aaae573..5394ee642d 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -1359,3 +1359,18 @@ if(APPLE AND QT_FEATURE_framework AND QT_FEATURE_separate_debug_info)
DESTINATION "${dsym_script_install_dir}"
)
endif()
+
+if(WASM)
+ set(wasm_injections
+ "${CMAKE_CURRENT_SOURCE_DIR}/platform/wasm/qtcontextfulpromise_injection.js"
+ )
+
+ qt_internal_add_resource(Core "wasminjections"
+ PREFIX
+ "/injections"
+ BASE
+ "${CMAKE_CURRENT_SOURCE_DIR}/platform/wasm"
+ FILES
+ ${wasm_injections}
+ )
+endif()
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index a915c031fe..d13c374dc4 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -3,7 +3,10 @@
#include "qstdweb_p.h"
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qfile.h>
#include <emscripten/bind.h>
+#include <emscripten/emscripten.h>
#include <cstdint>
#include <iostream>
@@ -11,7 +14,187 @@ QT_BEGIN_NAMESPACE
namespace qstdweb {
+const char makeContextfulPromiseFunctionName[] = "makePromise";
+
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
+namespace {
+enum class CallbackType {
+ Then,
+ Catch,
+ Finally,
+};
+
+void validateCallbacks(const PromiseCallbacks& callbacks) {
+ Q_ASSERT(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc);
+}
+
+void injectScript(const std::string& source, const std::string& injectionName)
+{
+ using namespace emscripten;
+
+ auto script = val::global("document").call<val>("createElement", val("script"));
+ auto head = val::global("document").call<val>("getElementsByTagName", val("head"));
+
+ script.call<void>("setAttribute", val("qtinjection"), val(injectionName));
+ script.set("innerText", val(source));
+
+ head[0].call<void>("appendChild", std::move(script));
+}
+
+using PromiseContext = int;
+
+class WebPromiseManager
+{
+public:
+ static const char contextfulPromiseSupportObjectName[];
+
+ static const char webPromiseManagerCallbackThunkExportName[];
+
+ WebPromiseManager();
+ ~WebPromiseManager();
+
+ WebPromiseManager(const WebPromiseManager& other) = delete;
+ WebPromiseManager(WebPromiseManager&& other) = delete;
+ WebPromiseManager& operator=(const WebPromiseManager& other) = delete;
+ WebPromiseManager& operator=(WebPromiseManager&& other) = delete;
+
+ void adoptPromise(emscripten::val target, PromiseCallbacks callbacks);
+
+ static WebPromiseManager* get();
+
+ static void callbackThunk(emscripten::val callbackType, emscripten::val context, emscripten::val result);
+
+private:
+ static std::optional<CallbackType> parseCallbackType(emscripten::val callbackType);
+
+ void subscribeToJsPromiseCallbacks(const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise);
+ void callback(CallbackType type, emscripten::val context, emscripten::val result);
+
+ void registerPromise(PromiseContext context, PromiseCallbacks promise);
+ void unregisterPromise(PromiseContext context);
+
+ QHash<PromiseContext, PromiseCallbacks> m_promiseRegistry;
+ int m_nextContextId = 0;
+};
+
+static void qStdWebCleanup()
+{
+ auto window = emscripten::val::global("window");
+ auto contextfulPromiseSupport = window[WebPromiseManager::contextfulPromiseSupportObjectName];
+ if (contextfulPromiseSupport.isUndefined())
+ return;
+
+ contextfulPromiseSupport.call<void>("removeRef");
+}
+
+const char WebPromiseManager::webPromiseManagerCallbackThunkExportName[] = "qtStdWebWebPromiseManagerCallbackThunk";
+const char WebPromiseManager::contextfulPromiseSupportObjectName[] = "qtContextfulPromiseSupport";
+
+Q_GLOBAL_STATIC(WebPromiseManager, webPromiseManager)
+
+WebPromiseManager::WebPromiseManager()
+{
+ QFile injection(QStringLiteral(":/injections/qtcontextfulpromise_injection.js"));
+ if (!injection.open(QIODevice::ReadOnly))
+ qFatal("Missing resource");
+ injectScript(injection.readAll().toStdString(), "contextfulpromise");
+ qAddPostRoutine(&qStdWebCleanup);
+}
+
+std::optional<CallbackType>
+WebPromiseManager::parseCallbackType(emscripten::val callbackType)
+{
+ if (!callbackType.isString())
+ return std::nullopt;
+
+ const std::string data = callbackType.as<std::string>();
+ if (data == "then")
+ return CallbackType::Then;
+ if (data == "catch")
+ return CallbackType::Catch;
+ if (data == "finally")
+ return CallbackType::Finally;
+ return std::nullopt;
+}
+
+WebPromiseManager::~WebPromiseManager() = default;
+
+WebPromiseManager *WebPromiseManager::get()
+{
+ return webPromiseManager();
+}
+
+void WebPromiseManager::callbackThunk(emscripten::val callbackType,
+ emscripten::val context,
+ emscripten::val result)
+{
+ auto parsedCallbackType = parseCallbackType(callbackType);
+ if (!parsedCallbackType) {
+ qFatal("Bad callback type");
+ }
+ WebPromiseManager::get()->callback(*parsedCallbackType, context, std::move(result));
+}
+
+void WebPromiseManager::subscribeToJsPromiseCallbacks(const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromiseObject) {
+ using namespace emscripten;
+
+ if (Q_LIKELY(callbacks.thenFunc))
+ jsContextfulPromiseObject = jsContextfulPromiseObject.call<val>("then");
+ if (callbacks.catchFunc)
+ jsContextfulPromiseObject = jsContextfulPromiseObject.call<val>("catch");
+ if (callbacks.finallyFunc)
+ jsContextfulPromiseObject = jsContextfulPromiseObject.call<val>("finally");
+}
+
+void WebPromiseManager::callback(CallbackType type, emscripten::val context, emscripten::val result)
+{
+ auto found = m_promiseRegistry.find(context.as<PromiseContext>());
+ if (found == m_promiseRegistry.end()) {
+ return;
+ }
+
+ bool expectingOtherCallbacks;
+ switch (type) {
+ case CallbackType::Then:
+ found->thenFunc(result);
+ // At this point, if there is no finally function, we are sure that the Catch callback won't be issued.
+ expectingOtherCallbacks = !!found->finallyFunc;
+ break;
+ case CallbackType::Catch:
+ found->catchFunc(result);
+ expectingOtherCallbacks = !!found->finallyFunc;
+ break;
+ case CallbackType::Finally:
+ found->finallyFunc();
+ expectingOtherCallbacks = false;
+ break;
+ }
+
+ if (!expectingOtherCallbacks)
+ unregisterPromise(context.as<int>());
+}
+
+void WebPromiseManager::registerPromise(PromiseContext context, PromiseCallbacks callbacks)
+{
+ m_promiseRegistry.emplace(context, std::move(callbacks));
+}
+
+void WebPromiseManager::unregisterPromise(PromiseContext context)
+{
+ m_promiseRegistry.remove(context);
+}
+
+void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks callbacks) {
+ emscripten::val context(m_nextContextId++);
+
+ auto jsContextfulPromise = emscripten::val::global("window")
+ [contextfulPromiseSupportObjectName].call<emscripten::val>(
+ makeContextfulPromiseFunctionName, target, context,
+ emscripten::val::module_property(webPromiseManagerCallbackThunkExportName));
+ subscribeToJsPromiseCallbacks(callbacks, jsContextfulPromise);
+ registerPromise(context.as<int>(), std::move(callbacks));
+}
+} // namespace
ArrayBuffer::ArrayBuffer(uint32_t size)
{
@@ -167,6 +350,10 @@ File FileList::operator[](int index) const
return item(index);
}
+emscripten::val FileList::val() {
+ return m_fileList;
+}
+
ArrayBuffer FileReader::result() const
{
return ArrayBuffer(m_fileReader["result"]);
@@ -322,6 +509,61 @@ std::string EventCallback::contextPropertyName(const std::string &eventName)
EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
+ emscripten::function(WebPromiseManager::webPromiseManagerCallbackThunkExportName, &WebPromiseManager::callbackThunk);
+}
+
+namespace Promise {
+ void adoptPromise(emscripten::val promiseObject, PromiseCallbacks callbacks) {
+ validateCallbacks(callbacks);
+
+ WebPromiseManager::get()->adoptPromise(
+ std::move(promiseObject), std::move(callbacks));
+ }
+
+ void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks) {
+ struct State {
+ std::map<int, emscripten::val> results;
+ int remainingThenCallbacks;
+ int remainingFinallyCallbacks;
+ };
+
+ validateCallbacks(callbacks);
+
+ auto state = std::make_shared<State>();
+ state->remainingThenCallbacks = state->remainingFinallyCallbacks = promises.size();
+
+ for (size_t i = 0; i < promises.size(); ++i) {
+ PromiseCallbacks individualPromiseCallback;
+ if (callbacks.thenFunc) {
+ individualPromiseCallback.thenFunc = [i, state, callbacks](emscripten::val partialResult) mutable {
+ state->results.emplace(i, std::move(partialResult));
+ if (!--(state->remainingThenCallbacks)) {
+ std::vector<emscripten::val> transformed;
+ for (auto& data : state->results) {
+ transformed.push_back(std::move(data.second));
+ }
+ callbacks.thenFunc(emscripten::val::array(std::move(transformed)));
+ }
+ };
+ }
+ if (callbacks.catchFunc) {
+ individualPromiseCallback.catchFunc = [state, callbacks](emscripten::val error) mutable {
+ callbacks.catchFunc(error);
+ };
+ }
+ individualPromiseCallback.finallyFunc = [state, callbacks]() mutable {
+ if (!--(state->remainingFinallyCallbacks)) {
+ if (callbacks.finallyFunc)
+ callbacks.finallyFunc();
+ // Explicitly reset here for verbosity, this would have been done automatically with the
+ // destruction of the adopted promise in WebPromiseManager.
+ state.reset();
+ }
+ };
+
+ adoptPromise(std::move(promises.at(i)), std::move(individualPromiseCallback));
+ }
+ }
}
} // namespace qstdweb
diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h
index dc8f643507..b4b8948b3a 100644
--- a/src/corelib/platform/wasm/qstdweb_p.h
+++ b/src/corelib/platform/wasm/qstdweb_p.h
@@ -19,12 +19,14 @@
#include <emscripten/val.h>
#include <cstdint>
#include <functional>
+#include "initializer_list"
#include <QtCore/qglobal.h>
-
+#include "QtCore/qhash.h"
QT_BEGIN_NAMESPACE
namespace qstdweb {
+ extern const char makeContextfulPromiseFunctionName[];
// DOM API in C++, implemented using emscripten val.h and bind.h.
// This is private API and can be extended and changed as needed.
@@ -153,6 +155,34 @@ namespace qstdweb {
std::string m_eventName;
std::function<void(emscripten::val)> m_fn;
};
+
+ struct PromiseCallbacks
+ {
+ std::function<void(emscripten::val)> thenFunc;
+ std::function<void(emscripten::val)> catchFunc;
+ std::function<void()> finallyFunc;
+ };
+
+ namespace Promise {
+ void adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
+
+ template<typename... Args>
+ void make(emscripten::val target,
+ QString methodName,
+ PromiseCallbacks callbacks,
+ Args... args)
+ {
+ emscripten::val promiseObject = target.call<emscripten::val>(
+ methodName.toStdString().c_str(), std::forward<Args>(args)...);
+ if (promiseObject.isUndefined() || promiseObject["constructor"]["name"].as<std::string>() != "Promise") {
+ qFatal("This function did not return a promise");
+ }
+
+ adoptPromise(std::move(promiseObject), std::move(callbacks));
+ }
+
+ void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
+ };
}
QT_END_NAMESPACE
diff --git a/src/corelib/platform/wasm/qtcontextfulpromise_injection.js b/src/corelib/platform/wasm/qtcontextfulpromise_injection.js
new file mode 100644
index 0000000000..ce5623171c
--- /dev/null
+++ b/src/corelib/platform/wasm/qtcontextfulpromise_injection.js
@@ -0,0 +1,32 @@
+`Copyright (C) 2022 The Qt Company Ltd.
+Copyright (C) 2016 Intel Corporation.
+SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0`;
+
+if (window.qtContextfulPromiseSupport) {
+ ++window.qtContextfulPromiseSupport.refs;
+} else {
+ window.qtContextfulPromiseSupport = {
+ refs: 1,
+ removeRef: () => {
+ --window.qtContextfulPromiseSupport.refs, 0 === window.qtContextfulPromiseSupport.refs && delete window.qtContextfulPromiseSupport;
+ },
+ makePromise: (a, b, c) => new window.qtContextfulPromiseSupport.ContextfulPromise(a, b, c),
+ };
+
+ window.qtContextfulPromiseSupport.ContextfulPromise = class {
+ constructor(a, b, c) {
+ (this.wrappedPromise = a), (this.context = b), (this.callbackThunk = c);
+ }
+ then() {
+ return (this.wrappedPromise = this.wrappedPromise.then((a) => { this.callbackThunk("then", this.context, a); })), this;
+ }
+ catch() {
+ return (this.wrappedPromise = this.wrappedPromise.catch((a) => { this.callbackThunk("catch", this.context, a); })), this;
+ }
+ finally() {
+ return (this.wrappedPromise = this.wrappedPromise.finally(() => this.callbackThunk("finally", this.context, undefined))), this;
+ }
+ };
+}
+
+document.querySelector("[qtinjection=contextfulpromise]")?.remove();
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 88043ba092..99f3e61155 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -19,83 +19,6 @@
QT_BEGIN_NAMESPACE
using namespace emscripten;
-static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr)
-{
- QString formatString = QWasmString::toQString(format);
- QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>());
-
- QMimeData *mMimeData = new QMimeData;
- mMimeData->setData(formatString, dataArray);
-
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
-// QWasmIntegration::get()->getWasmClipboard()->isPaste = false;
-}
-
-static void qClipboardPasteResolve(emscripten::val blob)
-{
- // read Blob here
-
- auto fileReader = std::make_shared<qstdweb::FileReader>();
- auto _blob = qstdweb::Blob(blob);
- QString formatString = QString::fromStdString(_blob.type());
-
- fileReader->readAsArrayBuffer(_blob);
- char *chunkBuffer = nullptr;
- qstdweb::ArrayBuffer result = fileReader->result();
- qstdweb::Uint8Array(result).copyTo(chunkBuffer);
- QMimeData *mMimeData = new QMimeData;
- mMimeData->setData(formatString, chunkBuffer);
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
-}
-
-static void qClipboardPromiseResolve(emscripten::val clipboardItems)
-{
- int itemsCount = clipboardItems["length"].as<int>();
-
- for (int i = 0; i < itemsCount; i++) {
- int typesCount = clipboardItems[i]["types"]["length"].as<int>(); // ClipboardItem
-
- std::string mimeFormat = clipboardItems[i]["types"][0].as<std::string>();
-
- if (mimeFormat.find(std::string("text")) != std::string::npos) {
- // simple val object, no further processing
-
- val navigator = val::global("navigator");
- val textPromise = navigator["clipboard"].call<val>("readText");
- val readTextResolve = val::global("Module")["qtClipboardTextPromiseResolve"];
- textPromise.call<val>("then", readTextResolve);
-
- } else {
- // binary types require additional processing
- for (int j = 0; j < typesCount; j++) {
- val pasteResolve = emscripten::val::module_property("qtClipboardPasteResolve");
- val pasteException = emscripten::val::module_property("qtClipboardPromiseException");
-
- // get the blob
- clipboardItems[i]
- .call<val>("getType", clipboardItems[i]["types"][j])
- .call<val>("then", pasteResolve)
- .call<val>("catch", pasteException);
- }
- }
- }
-}
-
-static void qClipboardCopyPromiseResolve(emscripten::val something)
-{
- Q_UNUSED(something)
- qWarning() << "copy succeeeded";
-}
-
-
-static emscripten::val qClipboardPromiseException(emscripten::val something)
-{
- qWarning() << "clipboard error"
- << QString::fromStdString(something["name"].as<std::string>())
- << QString::fromStdString(something["message"].as<std::string>());
- return something;
-}
-
static void commonCopyEvent(val event)
{
QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
@@ -215,24 +138,10 @@ static void qClipboardPasteTo(val dataTransfer)
QWasmIntegration::get()->getWasmClipboard()->m_isListener = false;
}
-static void qClipboardTextPromiseResolve(emscripten::val clipdata)
-{
- pasteClipboardData(emscripten::val("text/plain"), clipdata);
-}
-
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
- function("qtPasteClipboardData", &pasteClipboardData);
-
- function("qtClipboardTextPromiseResolve", &qClipboardTextPromiseResolve);
- function("qtClipboardPromiseResolve", &qClipboardPromiseResolve);
-
- function("qtClipboardCopyPromiseResolve", &qClipboardCopyPromiseResolve);
- function("qtClipboardPromiseException", &qClipboardPromiseException);
-
function("qtClipboardCutTo", &qClipboardCutTo);
function("qtClipboardCopyTo", &qClipboardCopyTo);
function("qtClipboardPasteTo", &qClipboardPasteTo);
- function("qtClipboardPasteResolve", &qClipboardPasteResolve);
}
QWasmClipboard::QWasmClipboard() :
@@ -419,14 +328,18 @@ void QWasmClipboard::writeToClipboardApi()
// break;
}
- val copyResolve = emscripten::val::module_property("qtClipboardCopyPromiseResolve");
- val copyException = emscripten::val::module_property("qtClipboardPromiseException");
-
val navigator = val::global("navigator");
- navigator["clipboard"]
- .call<val>("write", clipboardWriteArray)
- .call<val>("then", copyResolve)
- .call<val>("catch", copyException);
+
+ qstdweb::Promise::make(
+ navigator["clipboard"], "write",
+ {
+ .catchFunc = [](emscripten::val error) {
+ qWarning() << "clipboard error"
+ << QString::fromStdString(error["name"].as<std::string>())
+ << QString::fromStdString(error["message"].as<std::string>());
+ }
+ },
+ clipboardWriteArray);
}
void QWasmClipboard::writeToClipboard(const QMimeData *data)