summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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"