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:
parent
dbe858bf80
commit
fb8832de9c
@ -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()
|
||||
|
@ -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 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;
|
||||
|
||||
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));
|
||||
return g_thunkPool;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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)
|
||||
void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, 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;
|
||||
}
|
||||
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 {
|
||||
|
@ -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();
|
@ -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>());
|
||||
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");
|
||||
}
|
||||
}, 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"));
|
||||
(*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>());
|
||||
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)();
|
||||
}
|
||||
}, 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"));
|
||||
(*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();
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user