summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikolaj Boc <mikolaj.boc@qt.io>2022-06-21 17:50:04 +0200
committerMikolaj Boc <mikolaj.boc@qt.io>2022-06-30 01:20:28 +0200
commitd0eba2449adcee6f1daa33426f9c9b8ebf7c28f5 (patch)
treec651a3f7759a0a46f3f792a79697d91e374994f7
parent97665c9615ff399e9a074b94926ab06e0c9619e5 (diff)
Create a promise wrapper for C++ and port existing uses
Currently, to use a promise from C++ we either have to use an ASM block (which does not work well with dynamic linking) or declare exports in the EMSCRIPTEN_BINDINGS block, which is cumbersome and cannot be chained. This solution makes it easy to use js promises by introducing the WebPromiseManager which dispatches callbacks to appropriate callers when available. This is a preliminary patch for FileSystem support, which will heavily use async APIs. Task-number: QTBUG-99611 Change-Id: I368a8f173027eaa883a9ca18d0ea6a3e99b86071 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
-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
-rw-r--r--tests/auto/wasm/CMakeLists.txt17
-rw-r--r--tests/auto/wasm/tst_qstdweb.cpp629
7 files changed, 977 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)
diff --git a/tests/auto/wasm/CMakeLists.txt b/tests/auto/wasm/CMakeLists.txt
new file mode 100644
index 0000000000..2e6c6976fb
--- /dev/null
+++ b/tests/auto/wasm/CMakeLists.txt
@@ -0,0 +1,17 @@
+#####################################################################
+## tst_wasm Test:
+#####################################################################
+
+qt_internal_add_test(tst_qstdweb
+ SOURCES
+ tst_qstdweb.cpp
+ DEFINES
+ QT_NO_FOREACH
+ QT_NO_KEYWORDS
+ LIBRARIES
+ Qt::GuiPrivate
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+)
diff --git a/tests/auto/wasm/tst_qstdweb.cpp b/tests/auto/wasm/tst_qstdweb.cpp
new file mode 100644
index 0000000000..f074866a3d
--- /dev/null
+++ b/tests/auto/wasm/tst_qstdweb.cpp
@@ -0,0 +1,629 @@
+// 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
+
+#include <QtCore/qtimer.h>
+#include <QtCore/private/qstdweb_p.h>
+#include <QTest>
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+
+#if defined(QT_HAVE_EMSCRIPTEN_ASYNCIFY)
+#define SKIP_IF_NO_ASYNCIFY()
+#else
+#define SKIP_IF_NO_ASYNCIFY() QSKIP("Needs QT_HAVE_EMSCRIPTEN_ASYNCIFY")
+#endif
+
+using namespace emscripten;
+
+class tst_QStdWeb : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QStdWeb() : m_window(val::global("window")), m_testSupport(val::object()) {
+ instance = this;
+
+ m_window.set("testSupport", m_testSupport);
+ }
+ ~tst_QStdWeb() noexcept {}
+
+private:
+ static tst_QStdWeb* instance;
+
+ void init() {
+ EM_ASM({
+ testSupport.resolve = {};
+ testSupport.reject = {};
+ testSupport.promises = {};
+ testSupport.waitConditionPromise = new Promise((resolve, reject) => {
+ testSupport.finishWaiting = resolve;
+ });
+
+ testSupport.makeTestPromise = (param) => {
+ testSupport.promises[param] = new Promise((resolve, reject) => {
+ testSupport.resolve[param] = resolve;
+ testSupport.reject[param] = reject;
+ });
+
+ return testSupport.promises[param];
+ };
+ });
+ }
+
+ val m_window;
+ val m_testSupport;
+
+private Q_SLOTS:
+ void simpleResolve();
+ void multipleResolve();
+ void simpleReject();
+ void multipleReject();
+ void throwInThen();
+ void bareFinally();
+ void finallyWithThen();
+ void finallyWithThrow();
+ void finallyWithThrowInThen();
+ void nested();
+ void all();
+ void allWithThrow();
+ void allWithFinally();
+ void allWithFinallyAndThrow();
+};
+
+tst_QStdWeb* tst_QStdWeb::instance = nullptr;
+
+EM_ASYNC_JS(void, awaitCondition, (), {
+ await testSupport.waitConditionPromise;
+});
+
+void tst_QStdWeb::simpleResolve()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Some lovely data", result.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("simpleResolve"));
+
+ EM_ASM({
+ testSupport.resolve["simpleResolve"]("Some lovely data");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::multipleResolve()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Data 1", result.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("1"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Data 2", result.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("2"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Data 3", result.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("3"));
+
+ EM_ASM({
+ testSupport.resolve["3"]("Data 3");
+ testSupport.resolve["1"]("Data 1");
+ testSupport.resolve["2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::simpleReject()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Evil error", result.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("simpleReject"));
+
+ EM_ASM({
+ testSupport.reject["simpleReject"]("Evil error");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::multipleReject()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val error) {
+ QVERIFY(error.isString());
+ QCOMPARE("Error 1", error.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ }
+ }, std::string("1"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val error) {
+ QVERIFY(error.isString());
+ QCOMPARE("Error 2", error.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ }
+ }, std::string("2"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val error) {
+ QVERIFY(error.isString());
+ QCOMPARE("Error 3", error.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ }
+ }, std::string("3"));
+
+ EM_ASM({
+ testSupport.reject["3"]("Error 3");
+ testSupport.reject["1"]("Error 1");
+ testSupport.reject["2"]("Error 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::throwInThen()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw "Expected error";
+ });
+ },
+ .catchFunc = [](val error) {
+ QCOMPARE("Expected error", error.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("throwInThen"));
+
+ EM_ASM({
+ testSupport.resolve["throwInThen"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::bareFinally()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("bareFinally"));
+
+ EM_ASM({
+ testSupport.resolve["bareFinally"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::finallyWithThen()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [] (val result) {
+ Q_UNUSED(result);
+ },
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("finallyWithThen"));
+
+ EM_ASM({
+ testSupport.resolve["finallyWithThen"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::finallyWithThrow()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ },
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("finallyWithThrow"));
+
+ EM_ASM({
+ testSupport.reject["finallyWithThrow"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::finallyWithThrowInThen()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw "Expected error";
+ });
+ },
+ .catchFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Expected error", result.as<std::string>());
+ },
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("bareFinallyWithThen"));
+
+ EM_ASM({
+ testSupport.resolve["bareFinallyWithThen"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::nested()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [this](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Outer data", result.as<std::string>());
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [this](val innerResult) {
+ QVERIFY(innerResult.isString());
+ QCOMPARE("Inner data", innerResult.as<std::string>());
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val innerResult) {
+ QVERIFY(innerResult.isString());
+ QCOMPARE("Innermost data", innerResult.as<std::string>());
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("innermost"));
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("inner"));
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("outer"));
+
+ EM_ASM({
+ testSupport.resolve["outer"]("Outer data");
+ });
+
+ awaitCondition();
+
+ EM_ASM({
+ testSupport.waitConditionPromise = new Promise((resolve, reject) => {
+ testSupport.finishWaiting = resolve;
+ });
+ });
+
+ EM_ASM({
+ testSupport.resolve["inner"]("Inner data");
+ });
+
+ awaitCondition();
+
+ EM_ASM({
+ testSupport.waitConditionPromise = new Promise((resolve, reject) => {
+ testSupport.finishWaiting = resolve;
+ });
+ });
+
+ EM_ASM({
+ testSupport.resolve["innermost"]("Innermost data");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::all()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto thenCalledOnce = std::shared_ptr<bool>();
+ *thenCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [thenCalledOnce](val result) {
+ QVERIFY(*thenCalledOnce);
+ *thenCalledOnce = false;
+
+ QVERIFY(result.isArray());
+ QCOMPARE(3, result["length"].as<int>());
+ QCOMPARE("Data 1", result[0].as<std::string>());
+ QCOMPARE("Data 2", result[1].as<std::string>());
+ QCOMPARE("Data 3", result[2].as<std::string>());
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw new Error("Unexpected error");
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.resolve["promise2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::allWithThrow()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto catchCalledOnce = std::shared_ptr<bool>();
+ *catchCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [catchCalledOnce](val result) {
+ QVERIFY(*catchCalledOnce);
+ *catchCalledOnce = false;
+ QVERIFY(result.isString());
+ QCOMPARE("Error 2", result.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.reject["promise2"]("Error 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::allWithFinally()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto finallyCalledOnce = std::shared_ptr<bool>();
+ *finallyCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ },
+ .finallyFunc = [finallyCalledOnce]() {
+ QVERIFY(*finallyCalledOnce);
+ *finallyCalledOnce = false;
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.resolve["promise2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::allWithFinallyAndThrow()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto finallyCalledOnce = std::shared_ptr<bool>();
+ *finallyCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw "This breaks it all!!!";
+ });
+ },
+ .finallyFunc = [finallyCalledOnce]() {
+ QVERIFY(*finallyCalledOnce);
+ *finallyCalledOnce = false;
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.resolve["promise2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+QTEST_APPLESS_MAIN(tst_QStdWeb)
+#include "tst_qstdweb.moc"