diff options
-rw-r--r-- | src/corelib/CMakeLists.txt | 15 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qstdweb.cpp | 242 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qstdweb_p.h | 32 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qtcontextfulpromise_injection.js | 32 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmclipboard.cpp | 109 | ||||
-rw-r--r-- | tests/auto/wasm/CMakeLists.txt | 17 | ||||
-rw-r--r-- | tests/auto/wasm/tst_qstdweb.cpp | 629 |
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" |