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 <morten.sorvig@qt.io>
This commit is contained in:
Mikolaj Boc 2022-07-01 11:31:03 +02:00
parent dbe858bf80
commit fb8832de9c
4 changed files with 258 additions and 244 deletions

View File

@ -1360,18 +1360,3 @@ if(APPLE AND QT_FEATURE_framework AND QT_FEATURE_separate_debug_info)
DESTINATION "${dsym_script_install_dir}" DESTINATION "${dsym_script_install_dir}"
) )
endif() 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()

View File

@ -10,12 +10,12 @@
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
#include <unordered_map>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace qstdweb { namespace qstdweb {
const char makeContextfulPromiseFunctionName[] = "makePromise";
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
namespace { namespace {
enum class CallbackType { enum class CallbackType {
@ -28,28 +28,166 @@ void validateCallbacks(const PromiseCallbacks& callbacks) {
Q_ASSERT(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc); Q_ASSERT(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc);
} }
void injectScript(const std::string& source, const std::string& injectionName) 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
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;
}
// 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;
};
Q_GLOBAL_STATIC(ThunkPool, g_thunkPool)
ThunkPool* ThunkPool::get()
{ {
using namespace emscripten; return g_thunkPool;
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; #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)
}
#undef CALLBACK_BINDING
#undef THUNK_NAME
class WebPromiseManager class WebPromiseManager
{ {
public: public:
static const char contextfulPromiseSupportObjectName[];
static const char webPromiseManagerCallbackThunkExportName[];
WebPromiseManager(); WebPromiseManager();
~WebPromiseManager(); ~WebPromiseManager();
@ -62,43 +200,30 @@ public:
static WebPromiseManager* get(); static WebPromiseManager* get();
static void callbackThunk(emscripten::val callbackType, emscripten::val context, emscripten::val result);
private: private:
struct RegistryEntry {
PromiseCallbacks callbacks;
std::unique_ptr<ThunkPool::ThunkAllocation> allocation;
};
static std::optional<CallbackType> parseCallbackType(emscripten::val callbackType); static std::optional<CallbackType> parseCallbackType(emscripten::val callbackType);
void subscribeToJsPromiseCallbacks(const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise); void subscribeToJsPromiseCallbacks(int i, const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise);
void callback(CallbackType type, emscripten::val context, emscripten::val result); void promiseThunkCallback(int i, CallbackType type, emscripten::val result);
void registerPromise(PromiseContext context, PromiseCallbacks promise); void registerPromise(std::unique_ptr<ThunkPool::ThunkAllocation> allocation, PromiseCallbacks promise);
void unregisterPromise(PromiseContext context); void unregisterPromise(ThunkId context);
QHash<PromiseContext, PromiseCallbacks> m_promiseRegistry; std::array<RegistryEntry, ThunkPool::poolSize> 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) Q_GLOBAL_STATIC(WebPromiseManager, webPromiseManager)
WebPromiseManager::WebPromiseManager() WebPromiseManager::WebPromiseManager()
{ {
QFile injection(QStringLiteral(":/injections/qtcontextfulpromise_injection.js")); ThunkPool::get()->setThunkCallback(std::bind(
if (!injection.open(QIODevice::ReadOnly)) &WebPromiseManager::promiseThunkCallback, this,
qFatal("Missing resource"); std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
injectScript(injection.readAll().toStdString(), "contextfulpromise");
qAddPostRoutine(&qStdWebCleanup);
} }
std::optional<CallbackType> std::optional<CallbackType>
@ -124,75 +249,51 @@ WebPromiseManager *WebPromiseManager::get()
return webPromiseManager(); return webPromiseManager();
} }
void WebPromiseManager::callbackThunk(emscripten::val callbackType, void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, emscripten::val result)
emscripten::val context,
emscripten::val result)
{ {
auto parsedCallbackType = parseCallbackType(callbackType); auto* promiseState = &m_promiseRegistry[context];
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;
}
auto* callbacks = &promiseState->callbacks;
bool expectingOtherCallbacks; bool expectingOtherCallbacks;
switch (type) { switch (type) {
case CallbackType::Then: 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. // 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; break;
case CallbackType::Catch: case CallbackType::Catch:
found->catchFunc(result); callbacks->catchFunc(result);
expectingOtherCallbacks = !!found->finallyFunc; expectingOtherCallbacks = !!callbacks->finallyFunc;
break; break;
case CallbackType::Finally: case CallbackType::Finally:
found->finallyFunc(); callbacks->finallyFunc();
expectingOtherCallbacks = false; expectingOtherCallbacks = false;
break; break;
} }
if (!expectingOtherCallbacks) 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) { void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks callbacks) {
emscripten::val context(m_nextContextId++); ThunkPool::get()->allocateThunk([=](std::unique_ptr<ThunkPool::ThunkAllocation> allocation) {
allocation->bindToPromise(std::move(target), callbacks);
auto jsContextfulPromise = emscripten::val::global("window") registerPromise(std::move(allocation), std::move(callbacks));
[contextfulPromiseSupportObjectName].call<emscripten::val>( });
makeContextfulPromiseFunctionName, target, context,
emscripten::val::module_property(webPromiseManagerCallbackThunkExportName));
subscribeToJsPromiseCallbacks(callbacks, jsContextfulPromise);
registerPromise(context.as<int>(), std::move(callbacks));
} }
} // namespace } // namespace
@ -509,7 +610,6 @@ std::string EventCallback::contextPropertyName(const std::string &eventName)
EMSCRIPTEN_BINDINGS(qtStdwebCalback) { EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate); emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
emscripten::function(WebPromiseManager::webPromiseManagerCallbackThunkExportName, &WebPromiseManager::callbackThunk);
} }
namespace Promise { namespace Promise {

View File

@ -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();

View File

@ -17,14 +17,15 @@ class WasmPromiseTest : public QObject
Q_OBJECT Q_OBJECT
public: public:
WasmPromiseTest() : m_window(val::global("window")), m_testSupport(val::object()) { WasmPromiseTest() : m_window(val::global("window")), m_testSupport(val::object()) {}
m_window.set("testSupport", m_testSupport);
}
~WasmPromiseTest() noexcept {} ~WasmPromiseTest() noexcept = default;
private: private:
void init() { void init() {
m_testSupport = val::object();
m_window.set("testSupport", m_testSupport);
EM_ASM({ EM_ASM({
testSupport.resolve = {}; testSupport.resolve = {};
testSupport.reject = {}; testSupport.reject = {};
@ -108,52 +109,32 @@ void WasmPromiseTest::multipleResolve()
{ {
init(); init();
auto onThen = std::make_shared<BarrierCallback>(3, []() { static constexpr int promiseCount = 1000;
auto onThen = std::make_shared<BarrierCallback>(promiseCount, []() {
QWASMSUCCESS(); QWASMSUCCESS();
}); });
qstdweb::Promise::make(m_testSupport, "makeTestPromise", { for (int i = 0; i < promiseCount; ++i) {
.thenFunc = [=](val result) { qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
QWASMVERIFY(result.isString()); .thenFunc = [=](val result) {
QWASMCOMPARE("Data 1", result.as<std::string>()); QWASMVERIFY(result.isString());
QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>());
(*onThen)(); (*onThen)();
}, },
.catchFunc = [](val error) { .catchFunc = [](val error) {
Q_UNUSED(error); Q_UNUSED(error);
QWASMFAIL("Unexpected catch"); QWASMFAIL("Unexpected catch");
} }
}, std::string("1")); }, (QStringLiteral("test") + QString::number(i)).toStdString());
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"));
EM_ASM({ EM_ASM({
testSupport.resolve["3"]("Data 3"); for (let i = $0 - 1; i >= 0; --i) {
testSupport.resolve["1"]("Data 1"); testSupport.resolve['test' + i](`${i}`);
testSupport.resolve["2"]("Data 2"); }
}); }, promiseCount);
} }
void WasmPromiseTest::simpleReject() void WasmPromiseTest::simpleReject()
@ -179,53 +160,32 @@ void WasmPromiseTest::simpleReject()
void WasmPromiseTest::multipleReject() void WasmPromiseTest::multipleReject()
{ {
init(); static constexpr int promiseCount = 1000;
auto onThen = std::make_shared<BarrierCallback>(3, []() {
auto onCatch = std::make_shared<BarrierCallback>(promiseCount, []() {
QWASMSUCCESS(); QWASMSUCCESS();
}); });
qstdweb::Promise::make(m_testSupport, "makeTestPromise", { for (int i = 0; i < promiseCount; ++i) {
.thenFunc = [](val result) { qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
Q_UNUSED(result); .thenFunc = [=](val result) {
QWASMFAIL("Unexpected then"); QWASMVERIFY(result.isString());
}, QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>());
.catchFunc = [=](val error) {
QWASMVERIFY(error.isString());
QWASMCOMPARE("Error 1", error.as<std::string>());
(*onThen)(); (*onCatch)();
} },
}, std::string("1")); .catchFunc = [](val error) {
qstdweb::Promise::make(m_testSupport, "makeTestPromise", { Q_UNUSED(error);
.thenFunc = [](val result) { QWASMFAIL("Unexpected catch");
Q_UNUSED(result); }
QWASMFAIL("Unexpected then"); }, (QStringLiteral("test") + QString::number(i)).toStdString());
}, }
.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"));
EM_ASM({ EM_ASM({
testSupport.reject["3"]("Error 3"); for (let i = $0 - 1; i >= 0; --i) {
testSupport.reject["1"]("Error 1"); testSupport.resolve['test' + i](`${i}`);
testSupport.reject["2"]("Error 2"); }
}); }, promiseCount);
} }
void WasmPromiseTest::throwInThen() void WasmPromiseTest::throwInThen()
@ -241,8 +201,7 @@ void WasmPromiseTest::throwInThen()
}, },
.catchFunc = [](val error) { .catchFunc = [](val error) {
QWASMCOMPARE("Expected error", error.as<std::string>()); QWASMCOMPARE("Expected error", error.as<std::string>());
//QWASMSUCCESS(); QWASMSUCCESS();
QWASMFAIL("Other nasty problem");
} }
}, std::string("throwInThen")); }, std::string("throwInThen"));
@ -386,39 +345,42 @@ void WasmPromiseTest::all()
{ {
init(); init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1")); static constexpr int promiseCount = 1000;
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto thenCalledOnce = std::shared_ptr<bool>(); auto thenCalledOnce = std::shared_ptr<bool>();
*thenCalledOnce = true; *thenCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, { std::vector<val> promises;
.thenFunc = [thenCalledOnce](val result) { 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); QWASMVERIFY(*thenCalledOnce);
*thenCalledOnce = false; *thenCalledOnce = false;
QWASMVERIFY(result.isArray()); QWASMVERIFY(result.isArray());
QWASMCOMPARE(3, result["length"].as<int>()); QWASMCOMPARE(promiseCount, result["length"].as<int>());
QWASMCOMPARE("Data 1", result[0].as<std::string>()); for (int i = 0; i < promiseCount; ++i) {
QWASMCOMPARE("Data 2", result[1].as<std::string>()); QWASMCOMPARE(QStringLiteral("Data %1").arg(i).toStdString(), result[i].as<std::string>());
QWASMCOMPARE("Data 3", result[2].as<std::string>()); }
QWASMSUCCESS(); QWASMSUCCESS();
}, },
.catchFunc = [](val result) { .catchFunc = [](val error) {
Q_UNUSED(result); Q_UNUSED(error);
EM_ASM({ QWASMFAIL("Unexpected catch");
throw new Error("Unexpected error");
});
} }
}); });
EM_ASM({ EM_ASM({
testSupport.resolve["promise3"]("Data 3"); console.log('Resolving');
testSupport.resolve["promise1"]("Data 1"); for (let i = $0 - 1; i >= 0; --i) {
testSupport.resolve["promise2"]("Data 2"); testSupport.resolve['all' + i](`Data ${i}`);
}); }
}, promiseCount);
} }
void WasmPromiseTest::allWithThrow() void WasmPromiseTest::allWithThrow()
@ -503,8 +465,7 @@ void WasmPromiseTest::allWithFinallyAndThrow()
.finallyFunc = [finallyCalledOnce]() { .finallyFunc = [finallyCalledOnce]() {
QWASMVERIFY(*finallyCalledOnce); QWASMVERIFY(*finallyCalledOnce);
*finallyCalledOnce = false; *finallyCalledOnce = false;
// QWASMSUCCESS(); QWASMSUCCESS();
QWASMFAIL("Some nasty problem");
} }
}); });