summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikolaj Boc <[email protected]>2022-07-01 11:31:03 +0200
committerMikolaj Boc <[email protected]>2022-07-07 06:28:13 +0200
commitfb8832de9c2d9cec08a83047137fbd316e6f7d1f (patch)
tree0662caa1e66b7635d480934155f7a06ea26bb12f
parentdbe858bf80f554c4d916ee230fc9dcde01699bd7 (diff)
Make the promises js-less using a newly introduced thunk pool
Since we cannot rely on the clients specifying a suitable CSP that will not forbid execution of js injections, we have to refrain from using any explicit <script> elements. To keep the promise system working, a thunk pool was introduced which keeps track of a limited pool of promise callback exports. In case the resources are busy, pending calls are enqueued. This works since the JS Promise.then/catch/finally always fire, even on ready/failed promises. As the situation of full thunk pool allocation is unlikely to happen en masse IRL, the solution should not adversely affect the performance. Heavy unit tests were created to confirm the solution works as expected. Task-number: QTBUG-99611 Change-Id: I0e6982d4ee76a4263b59e72b004b3ff2f167e4df Reviewed-by: Morten Johan Sørvig <[email protected]>
-rw-r--r--src/corelib/CMakeLists.txt15
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp274
-rw-r--r--src/corelib/platform/wasm/qtcontextfulpromise_injection.js32
-rw-r--r--tests/manual/wasm/qstdweb/promise_main.cpp181
4 files changed, 258 insertions, 244 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 5da5205f1cc..5d498b7f5d0 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -1360,18 +1360,3 @@ 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 d13c374dc4a..43cd079e9db 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -10,12 +10,12 @@
#include <cstdint>
#include <iostream>
+#include <unordered_map>
+
QT_BEGIN_NAMESPACE
namespace qstdweb {
-const char makeContextfulPromiseFunctionName[] = "makePromise";
-
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
namespace {
enum class CallbackType {
@@ -28,28 +28,166 @@ 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;
+using ThunkId = int;
+
+#define THUNK_NAME(type, i) callbackThunk##type##i
+
+// A resource pool for exported promise thunk functions. ThunkPool::poolSize sets of
+// 3 promise thunks (then, catch, finally) are exported and can be used by promises
+// in C++. To allocate a thunk, call allocateThunk. When a thunk is ready for use,
+// a callback with allocation RAII object ThunkAllocation will be returned. Deleting
+// the object frees the thunk and automatically makes any pending allocateThunk call
+// run its callback with a free thunk slot.
+class ThunkPool {
+public:
+ static constexpr size_t poolSize = 4;
+
+ // An allocation for a thunk function set. Following the RAII pattern, destruction of
+ // this objects frees a corresponding thunk pool entry.
+ // To actually make the thunks react to a js promise's callbacks, call bindToPromise.
+ class ThunkAllocation {
+ public:
+ ThunkAllocation(int thunkId, ThunkPool* pool) : m_thunkId(thunkId), m_pool(pool) {}
+ ~ThunkAllocation() {
+ m_pool->free(m_thunkId);
+ }
+
+ // The id of the underlaying thunk set
+ int id() const { return m_thunkId; }
+
+ // Binds the corresponding thunk set to the js promise 'target'.
+ void bindToPromise(emscripten::val target, const PromiseCallbacks& callbacks) {
+ using namespace emscripten;
+
+ if (Q_LIKELY(callbacks.thenFunc)) {
+ target = target.call<val>(
+ "then",
+ emscripten::val::module_property(thunkName(CallbackType::Then, id()).data()));
+ }
+ if (callbacks.catchFunc) {
+ target = target.call<val>(
+ "catch",
+ emscripten::val::module_property(thunkName(CallbackType::Catch, id()).data()));
+ }
+ if (callbacks.finallyFunc) {
+ target = target.call<val>(
+ "finally",
+ emscripten::val::module_property(thunkName(CallbackType::Finally, id()).data()));
+ }
+ }
+
+ private:
+ int m_thunkId;
+ ThunkPool* m_pool;
+ };
+
+ ThunkPool() {
+ std::iota(m_free.begin(), m_free.end(), 0);
+ }
+
+ void setThunkCallback(std::function<void(int, CallbackType, emscripten::val)> callback) {
+ m_callback = std::move(callback);
+ }
+
+ void allocateThunk(std::function<void(std::unique_ptr<ThunkAllocation>)> onAllocated) {
+ if (m_free.empty()) {
+ m_pendingAllocations.push_back(std::move(onAllocated));
+ return;
+ }
+
+ const int thunkId = m_free.back();
+ m_free.pop_back();
+ onAllocated(std::make_unique<ThunkAllocation>(thunkId, this));
+ }
+
+ static QByteArray thunkName(CallbackType type, size_t i) {
+ return QStringLiteral("promiseCallback%1%2").arg([type]() -> QString {
+ switch (type) {
+ case CallbackType::Then:
+ return QStringLiteral("Then");
+ case CallbackType::Catch:
+ return QStringLiteral("Catch");
+ case CallbackType::Finally:
+ return QStringLiteral("Finally");
+ }
+ }()).arg(i).toLatin1();
+ }
+
+ static ThunkPool* get();
+
+#define THUNK(i) \
+ static void THUNK_NAME(Then, i)(emscripten::val result) \
+ { \
+ get()->onThunkCalled(i, CallbackType::Then, std::move(result)); \
+ } \
+ static void THUNK_NAME(Catch, i)(emscripten::val result) \
+ { \
+ get()->onThunkCalled(i, CallbackType::Catch, std::move(result)); \
+ } \
+ static void THUNK_NAME(Finally, i)() \
+ { \
+ get()->onThunkCalled(i, CallbackType::Finally, emscripten::val::undefined()); \
+ }
+
+ THUNK(0);
+ THUNK(1);
+ THUNK(2);
+ THUNK(3);
+
+#undef THUNK
- auto script = val::global("document").call<val>("createElement", val("script"));
- auto head = val::global("document").call<val>("getElementsByTagName", val("head"));
+private:
+ void onThunkCalled(int index, CallbackType type, emscripten::val result) {
+ m_callback(index, type, std::move(result));
+ }
+
+ void free(int thunkId) {
+ if (m_pendingAllocations.empty()) {
+ // Return the thunk to the free pool
+ m_free.push_back(thunkId);
+ return;
+ }
- script.call<void>("setAttribute", val("qtinjection"), val(injectionName));
- script.set("innerText", val(source));
+ // Take the next enqueued allocation and reuse the thunk
+ auto allocation = m_pendingAllocations.back();
+ m_pendingAllocations.pop_back();
+ allocation(std::make_unique<ThunkAllocation>(thunkId, this));
+ }
+
+ std::function<void(int, CallbackType, emscripten::val)> m_callback;
+
+ std::vector<int> m_free = std::vector<int>(poolSize);
+ std::vector<std::function<void(std::unique_ptr<ThunkAllocation>)>> m_pendingAllocations;
+};
- head[0].call<void>("appendChild", std::move(script));
+Q_GLOBAL_STATIC(ThunkPool, g_thunkPool)
+
+ThunkPool* ThunkPool::get()
+{
+ return g_thunkPool;
+}
+
+#define CALLBACK_BINDING(i) \
+ emscripten::function(ThunkPool::thunkName(CallbackType::Then, i).data(), \
+ &ThunkPool::THUNK_NAME(Then, i)); \
+ emscripten::function(ThunkPool::thunkName(CallbackType::Catch, i).data(), \
+ &ThunkPool::THUNK_NAME(Catch, i)); \
+ emscripten::function(ThunkPool::thunkName(CallbackType::Finally, i).data(), \
+ &ThunkPool::THUNK_NAME(Finally, i));
+
+EMSCRIPTEN_BINDINGS(qtThunkPool) {
+ CALLBACK_BINDING(0)
+ CALLBACK_BINDING(1)
+ CALLBACK_BINDING(2)
+ CALLBACK_BINDING(3)
}
-using PromiseContext = int;
+#undef CALLBACK_BINDING
+#undef THUNK_NAME
class WebPromiseManager
{
public:
- static const char contextfulPromiseSupportObjectName[];
-
- static const char webPromiseManagerCallbackThunkExportName[];
-
WebPromiseManager();
~WebPromiseManager();
@@ -62,43 +200,30 @@ public:
static WebPromiseManager* get();
- static void callbackThunk(emscripten::val callbackType, emscripten::val context, emscripten::val result);
-
private:
+ struct RegistryEntry {
+ PromiseCallbacks callbacks;
+ std::unique_ptr<ThunkPool::ThunkAllocation> allocation;
+ };
+
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 subscribeToJsPromiseCallbacks(int i, const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise);
+ void promiseThunkCallback(int i, CallbackType type, emscripten::val result);
- void registerPromise(PromiseContext context, PromiseCallbacks promise);
- void unregisterPromise(PromiseContext context);
+ void registerPromise(std::unique_ptr<ThunkPool::ThunkAllocation> allocation, PromiseCallbacks promise);
+ void unregisterPromise(ThunkId context);
- QHash<PromiseContext, PromiseCallbacks> m_promiseRegistry;
- int m_nextContextId = 0;
+ std::array<RegistryEntry, ThunkPool::poolSize> m_promiseRegistry;
};
-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);
+ ThunkPool::get()->setThunkCallback(std::bind(
+ &WebPromiseManager::promiseThunkCallback, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
std::optional<CallbackType>
@@ -124,75 +249,51 @@ 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)
+void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, emscripten::val result)
{
- auto found = m_promiseRegistry.find(context.as<PromiseContext>());
- if (found == m_promiseRegistry.end()) {
- return;
- }
+ auto* promiseState = &m_promiseRegistry[context];
+ auto* callbacks = &promiseState->callbacks;
bool expectingOtherCallbacks;
switch (type) {
case CallbackType::Then:
- found->thenFunc(result);
+ callbacks->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;
+ expectingOtherCallbacks = !!callbacks->finallyFunc;
break;
case CallbackType::Catch:
- found->catchFunc(result);
- expectingOtherCallbacks = !!found->finallyFunc;
+ callbacks->catchFunc(result);
+ expectingOtherCallbacks = !!callbacks->finallyFunc;
break;
case CallbackType::Finally:
- found->finallyFunc();
+ callbacks->finallyFunc();
expectingOtherCallbacks = false;
break;
}
if (!expectingOtherCallbacks)
- unregisterPromise(context.as<int>());
+ unregisterPromise(context);
}
-void WebPromiseManager::registerPromise(PromiseContext context, PromiseCallbacks callbacks)
+void WebPromiseManager::registerPromise(
+ std::unique_ptr<ThunkPool::ThunkAllocation> allocation,
+ PromiseCallbacks callbacks)
{
- m_promiseRegistry.emplace(context, std::move(callbacks));
+ const ThunkId id = allocation->id();
+ m_promiseRegistry[id] =
+ RegistryEntry {std::move(callbacks), std::move(allocation)};
}
-void WebPromiseManager::unregisterPromise(PromiseContext context)
+void WebPromiseManager::unregisterPromise(ThunkId context)
{
- m_promiseRegistry.remove(context);
+ m_promiseRegistry[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));
+ ThunkPool::get()->allocateThunk([=](std::unique_ptr<ThunkPool::ThunkAllocation> allocation) {
+ allocation->bindToPromise(std::move(target), callbacks);
+ registerPromise(std::move(allocation), std::move(callbacks));
+ });
}
} // namespace
@@ -509,7 +610,6 @@ std::string EventCallback::contextPropertyName(const std::string &eventName)
EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
- emscripten::function(WebPromiseManager::webPromiseManagerCallbackThunkExportName, &WebPromiseManager::callbackThunk);
}
namespace Promise {
diff --git a/src/corelib/platform/wasm/qtcontextfulpromise_injection.js b/src/corelib/platform/wasm/qtcontextfulpromise_injection.js
deleted file mode 100644
index ce5623171c6..00000000000
--- a/src/corelib/platform/wasm/qtcontextfulpromise_injection.js
+++ /dev/null
@@ -1,32 +0,0 @@
-`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/tests/manual/wasm/qstdweb/promise_main.cpp b/tests/manual/wasm/qstdweb/promise_main.cpp
index 456fb7eb329..351f06c91d4 100644
--- a/tests/manual/wasm/qstdweb/promise_main.cpp
+++ b/tests/manual/wasm/qstdweb/promise_main.cpp
@@ -17,14 +17,15 @@ class WasmPromiseTest : public QObject
Q_OBJECT
public:
- WasmPromiseTest() : m_window(val::global("window")), m_testSupport(val::object()) {
- m_window.set("testSupport", m_testSupport);
- }
+ WasmPromiseTest() : m_window(val::global("window")), m_testSupport(val::object()) {}
- ~WasmPromiseTest() noexcept {}
+ ~WasmPromiseTest() noexcept = default;
private:
void init() {
+ m_testSupport = val::object();
+ m_window.set("testSupport", m_testSupport);
+
EM_ASM({
testSupport.resolve = {};
testSupport.reject = {};
@@ -108,52 +109,32 @@ void WasmPromiseTest::multipleResolve()
{
init();
- auto onThen = std::make_shared<BarrierCallback>(3, []() {
+ static constexpr int promiseCount = 1000;
+
+ auto onThen = std::make_shared<BarrierCallback>(promiseCount, []() {
QWASMSUCCESS();
});
- qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
- .thenFunc = [=](val result) {
- QWASMVERIFY(result.isString());
- QWASMCOMPARE("Data 1", result.as<std::string>());
-
- (*onThen)();
- },
- .catchFunc = [](val error) {
- Q_UNUSED(error);
- QWASMFAIL("Unexpected catch");
- }
- }, std::string("1"));
- qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
- .thenFunc = [=](val result) {
- QWASMVERIFY(result.isString());
- QWASMCOMPARE("Data 2", result.as<std::string>());
-
- (*onThen)();
- },
- .catchFunc = [](val error) {
- Q_UNUSED(error);
- QWASMFAIL("Unexpected catch");
- }
- }, std::string("2"));
- qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
- .thenFunc = [=](val result) {
- QWASMVERIFY(result.isString());
- QWASMCOMPARE("Data 3", result.as<std::string>());
-
- (*onThen)();
- },
- .catchFunc = [](val error) {
- Q_UNUSED(error);
- QWASMFAIL("Unexpected catch");
- }
- }, std::string("3"));
+ for (int i = 0; i < promiseCount; ++i) {
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [=](val result) {
+ QWASMVERIFY(result.isString());
+ QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>());
+
+ (*onThen)();
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QWASMFAIL("Unexpected catch");
+ }
+ }, (QStringLiteral("test") + QString::number(i)).toStdString());
+ }
EM_ASM({
- testSupport.resolve["3"]("Data 3");
- testSupport.resolve["1"]("Data 1");
- testSupport.resolve["2"]("Data 2");
- });
+ for (let i = $0 - 1; i >= 0; --i) {
+ testSupport.resolve['test' + i](`${i}`);
+ }
+ }, promiseCount);
}
void WasmPromiseTest::simpleReject()
@@ -179,53 +160,32 @@ void WasmPromiseTest::simpleReject()
void WasmPromiseTest::multipleReject()
{
- init();
- auto onThen = std::make_shared<BarrierCallback>(3, []() {
+ static constexpr int promiseCount = 1000;
+
+ auto onCatch = std::make_shared<BarrierCallback>(promiseCount, []() {
QWASMSUCCESS();
});
- qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
- .thenFunc = [](val result) {
- Q_UNUSED(result);
- QWASMFAIL("Unexpected then");
- },
- .catchFunc = [=](val error) {
- QWASMVERIFY(error.isString());
- QWASMCOMPARE("Error 1", error.as<std::string>());
-
- (*onThen)();
- }
- }, std::string("1"));
- qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
- .thenFunc = [](val result) {
- Q_UNUSED(result);
- QWASMFAIL("Unexpected then");
- },
- .catchFunc = [=](val error) {
- QWASMVERIFY(error.isString());
- QWASMCOMPARE("Error 2", error.as<std::string>());
-
- (*onThen)();
- }
- }, std::string("2"));
- qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
- .thenFunc = [](val result) {
- Q_UNUSED(result);
- QWASMFAIL("Unexpected then");
- },
- .catchFunc = [=](val error) {
- QWASMVERIFY(error.isString());
- QWASMCOMPARE("Error 3", error.as<std::string>());
-
- (*onThen)();
- }
- }, std::string("3"));
+ for (int i = 0; i < promiseCount; ++i) {
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [=](val result) {
+ QWASMVERIFY(result.isString());
+ QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>());
+
+ (*onCatch)();
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QWASMFAIL("Unexpected catch");
+ }
+ }, (QStringLiteral("test") + QString::number(i)).toStdString());
+ }
EM_ASM({
- testSupport.reject["3"]("Error 3");
- testSupport.reject["1"]("Error 1");
- testSupport.reject["2"]("Error 2");
- });
+ for (let i = $0 - 1; i >= 0; --i) {
+ testSupport.resolve['test' + i](`${i}`);
+ }
+ }, promiseCount);
}
void WasmPromiseTest::throwInThen()
@@ -241,8 +201,7 @@ void WasmPromiseTest::throwInThen()
},
.catchFunc = [](val error) {
QWASMCOMPARE("Expected error", error.as<std::string>());
- //QWASMSUCCESS();
- QWASMFAIL("Other nasty problem");
+ QWASMSUCCESS();
}
}, std::string("throwInThen"));
@@ -386,39 +345,42 @@ void WasmPromiseTest::all()
{
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"));
-
+ static constexpr int promiseCount = 1000;
auto thenCalledOnce = std::shared_ptr<bool>();
*thenCalledOnce = true;
- qstdweb::Promise::all({promise1, promise2, promise3}, {
- .thenFunc = [thenCalledOnce](val result) {
+ std::vector<val> promises;
+ promises.reserve(promiseCount);
+
+ for (int i = 0; i < promiseCount; ++i) {
+ promises.push_back(m_testSupport.call<val>("makeTestPromise", val(("all" + QString::number(i)).toStdString())));
+ }
+
+ qstdweb::Promise::all(std::move(promises), {
+ .thenFunc = [=](val result) {
QWASMVERIFY(*thenCalledOnce);
*thenCalledOnce = false;
QWASMVERIFY(result.isArray());
- QWASMCOMPARE(3, result["length"].as<int>());
- QWASMCOMPARE("Data 1", result[0].as<std::string>());
- QWASMCOMPARE("Data 2", result[1].as<std::string>());
- QWASMCOMPARE("Data 3", result[2].as<std::string>());
+ QWASMCOMPARE(promiseCount, result["length"].as<int>());
+ for (int i = 0; i < promiseCount; ++i) {
+ QWASMCOMPARE(QStringLiteral("Data %1").arg(i).toStdString(), result[i].as<std::string>());
+ }
QWASMSUCCESS();
},
- .catchFunc = [](val result) {
- Q_UNUSED(result);
- EM_ASM({
- throw new Error("Unexpected error");
- });
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QWASMFAIL("Unexpected catch");
}
});
EM_ASM({
- testSupport.resolve["promise3"]("Data 3");
- testSupport.resolve["promise1"]("Data 1");
- testSupport.resolve["promise2"]("Data 2");
- });
+ console.log('Resolving');
+ for (let i = $0 - 1; i >= 0; --i) {
+ testSupport.resolve['all' + i](`Data ${i}`);
+ }
+ }, promiseCount);
}
void WasmPromiseTest::allWithThrow()
@@ -503,8 +465,7 @@ void WasmPromiseTest::allWithFinallyAndThrow()
.finallyFunc = [finallyCalledOnce]() {
QWASMVERIFY(*finallyCalledOnce);
*finallyCalledOnce = false;
- // QWASMSUCCESS();
- QWASMFAIL("Some nasty problem");
+ QWASMSUCCESS();
}
});