diff options
Diffstat (limited to 'tests/manual/wasm/eventloop')
17 files changed, 754 insertions, 0 deletions
diff --git a/tests/manual/wasm/eventloop/CMakeLists.txt b/tests/manual/wasm/eventloop/CMakeLists.txt new file mode 100644 index 0000000000..132fd15dbb --- /dev/null +++ b/tests/manual/wasm/eventloop/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(asyncify_exec) +add_subdirectory(eventloop_auto) +add_subdirectory(main_exec) +add_subdirectory(main_noexec) +add_subdirectory(thread_exec) +if(QT_FEATURE_widgets) +add_subdirectory(dialog_exec) +endif() diff --git a/tests/manual/wasm/eventloop/README.md b/tests/manual/wasm/eventloop/README.md new file mode 100644 index 0000000000..e1a5a1a3b7 --- /dev/null +++ b/tests/manual/wasm/eventloop/README.md @@ -0,0 +1,15 @@ +Event loop exec() and main() on Qt for WebAssembly +================================================== + +These examples demonstrate how QEventLoop::exec() works on +Qt for WebAssembly, and also shows how to implement main() +without calling QApplication::exec(). + +Contents +======== + + main_exec Standard Qt main(), where QApplication::exec() does not return + main_noexec Qt main() without QApplication::exec() + dialog_exec Shows how QDialog::exec() also does not return + thread_exec Shows how to use QThread::exec() + eventloop_auto Event loop autotest (manually run) diff --git a/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt b/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt new file mode 100644 index 0000000000..fe7cfb9030 --- /dev/null +++ b/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(asyncify_exec + SOURCES + main.cpp + LIBRARIES + Qt::Core +) + +# Enable asyncify for this test. Also enable optimizations in order to reduce the binary size. +target_link_options(asyncify_exec PUBLIC -sASYNCIFY -Os) diff --git a/tests/manual/wasm/eventloop/asyncify_exec/main.cpp b/tests/manual/wasm/eventloop/asyncify_exec/main.cpp new file mode 100644 index 0000000000..f09163184d --- /dev/null +++ b/tests/manual/wasm/eventloop/asyncify_exec/main.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtCore> + +// This test shows how to use asyncify to enable blocking the main +// thread on QEventLoop::exec(), while event processing continues. +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + QTimer::singleShot(1000, []() { + + QEventLoop loop; + QTimer::singleShot(2000, [&loop]() { + qDebug() << "Calling QEventLoop::quit()"; + loop.quit(); + }); + + qDebug() << "Calling QEventLoop::exec()"; + loop.exec(); + qDebug() << "Returned from QEventLoop::exec()"; + }); + + app.exec(); +} diff --git a/tests/manual/wasm/eventloop/dialog_exec/CMakeLists.txt b/tests/manual/wasm/eventloop/dialog_exec/CMakeLists.txt new file mode 100644 index 0000000000..ac18643c63 --- /dev/null +++ b/tests/manual/wasm/eventloop/dialog_exec/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(dialog_exec + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/tests/manual/wasm/eventloop/dialog_exec/main.cpp b/tests/manual/wasm/eventloop/dialog_exec/main.cpp new file mode 100644 index 0000000000..f5b072fc0b --- /dev/null +++ b/tests/manual/wasm/eventloop/dialog_exec/main.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtGui> +#include <QtWidgets> + +// This example show how calling QDialog::exec() shows the dialog, +// but does not return. + +class ClickWindow: public QRasterWindow +{ +public: + ClickWindow() { + qDebug() << "ClickWindow constructor"; + } + + ~ClickWindow() { + qDebug() << "ClickWindow destructor"; + } + + void paintEvent(QPaintEvent *ev) override { + QPainter p(this); + p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue); + p.drawText(50, 100, "Application has started. See the developer tools console for debug output"); + } + + void mousePressEvent(QMouseEvent *) override { + qDebug() << "mousePressEvent(): calling QMessageBox::exec()"; + + QMessageBox messageBox; + messageBox.setText("Hello! This is a message box."); + connect(&messageBox, &QMessageBox::buttonClicked, [](QAbstractButton *button) { + qDebug() << "Button Clicked" << button; + }); + messageBox.exec(); // <-- does not return + + qDebug() << "mousePressEvent(): done"; // <--- will not be printed + } +}; + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + ClickWindow window; + window.show(); + + return app.exec(); +} diff --git a/tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt b/tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt new file mode 100644 index 0000000000..9bfa875be7 --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt @@ -0,0 +1,43 @@ +include_directories(../../qtwasmtestlib/) + +# default buid +qt_internal_add_manual_test(eventloop_auto + SOURCES + main.cpp + ../../qtwasmtestlib/qtwasmtestlib.cpp + LIBRARIES + Qt::Core + Qt::CorePrivate +) + +add_custom_command( + TARGET eventloop_auto POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/eventloop_auto.html + ${CMAKE_CURRENT_BINARY_DIR}/eventloop_auto.html) + +add_custom_command( + TARGET eventloop_auto POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/../../qtwasmtestlib/qtwasmtestlib.js + ${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js) + +# asyncify enabled build +qt_internal_add_manual_test(eventloop_auto_asyncify + SOURCES + main.cpp + ../../qtwasmtestlib/qtwasmtestlib.cpp + LIBRARIES + Qt::Core + Qt::CorePrivate +) + +target_link_options(eventloop_auto_asyncify PRIVATE -sASYNCIFY -Os) + +add_custom_command( + TARGET eventloop_auto_asyncify POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/eventloop_auto_asyncify.html + ${CMAKE_CURRENT_BINARY_DIR}/eventloop_auto_asyncify.html) + + diff --git a/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html b/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html new file mode 100644 index 0000000000..e8e35abcbb --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html @@ -0,0 +1,10 @@ +<!doctype html> +<script type="text/javascript" src="qtwasmtestlib.js"></script> +<script type="text/javascript" src="eventloop_auto.js"></script> +<script> + window.onload = () => { + runTestCase(eventloop_auto_entry, document.getElementById("log")); + }; +</script> +<p>Running event dispatcher auto test.</p> +<div id="log"></div> diff --git a/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto_asyncify.html b/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto_asyncify.html new file mode 100644 index 0000000000..f09b29d85b --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto_asyncify.html @@ -0,0 +1,10 @@ +<!doctype html> +<script type="text/javascript" src="qtwasmtestlib.js"></script> +<script type="text/javascript" src="eventloop_auto_asyncify.js"></script> +<script> + window.onload = () => { + runTestCase(eventloop_auto_asyncify_entry, document.getElementById("log")); + }; +</script> +<p>Running event dispatcher auto test.</p> +<div id="log"></div> diff --git a/tests/manual/wasm/eventloop/eventloop_auto/main.cpp b/tests/manual/wasm/eventloop/eventloop_auto/main.cpp new file mode 100644 index 0000000000..32af372b62 --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/main.cpp @@ -0,0 +1,327 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QCoreApplication> +#include <QtCore/QEvent> +#include <QtCore/QMutex> +#include <QtCore/QObject> +#include <QtCore/QThread> +#include <QtCore/QTimer> +#include <QtCore/private/qstdweb_p.h> + +#include <qtwasmtestlib.h> + +#include "emscripten.h" + +const int timerTimeout = 10; + +class WasmEventDispatcherTest: public QObject +{ + Q_OBJECT +private slots: + void postEventMainThread(); + void timerMainThread(); + void timerMainThreadMultiple(); + +#if QT_CONFIG(thread) + void postEventSecondaryThread(); + void postEventSecondaryThreads(); + void postEventToSecondaryThread(); + void timerSecondaryThread(); +#endif + + void postEventAsyncify(); + void timerAsyncify(); + void postEventAsyncifyLoop(); + +private: +// Disabled test function: Asyncify wait on pthread_join is not supported, +// see https://github.com/emscripten-core/emscripten/issues/9910 +#if QT_CONFIG(thread) + void threadAsyncifyWait(); +#endif +}; + +class EventTarget : public QObject +{ + Q_OBJECT + +public: + static EventTarget *create(std::function<void()> callback) + { + return new EventTarget(callback); + } + + static QEvent *createEvent() + { + return new QEvent(QEvent::User); + } + +protected: + EventTarget(std::function<void()> callback) + : m_callback(callback) { } + + bool event(QEvent *evt) + { + if (evt->type() == QEvent::User) { + m_callback(); + deleteLater(); + return true; + } + return QObject::event(evt); + } + +private: + std::function<void()> m_callback; +}; + +class CompleteTestFunctionRefGuard { +public: + CompleteTestFunctionRefGuard(CompleteTestFunctionRefGuard const&) = delete; + CompleteTestFunctionRefGuard& operator=(CompleteTestFunctionRefGuard const&) = delete; + + static CompleteTestFunctionRefGuard *create() { + return new CompleteTestFunctionRefGuard(); + } + + void ref() { + QMutexLocker lock(&mutex); + ++m_counter; + } + + void deref() { + const bool finalDeref = [this] { + QMutexLocker lock(&mutex); + return --m_counter == 0; + }(); + + if (finalDeref) + QtWasmTest::completeTestFunction(); + } +private: + CompleteTestFunctionRefGuard() { }; + + QMutex mutex; + int m_counter = 0; +}; + +#if QT_CONFIG(thread) + +class TestThread : public QThread +{ +public: + static QThread *create(std::function<void()> started, std::function<void()> finished) + { + TestThread *thread = new TestThread(); + connect(thread, &QThread::started, [started]() { + started(); + }); + connect(thread, &QThread::finished, [thread, finished]() { + finished(); + thread->deleteLater(); + }); + thread->start(); + return thread; + } +}; + +#endif + +// Post event to the main thread and verify that it is processed. +void WasmEventDispatcherTest::postEventMainThread() +{ + QCoreApplication::postEvent(EventTarget::create([](){ + QtWasmTest::completeTestFunction(); + }), EventTarget::createEvent()); +} + +// Create a timer on the main thread and verify that it fires +void WasmEventDispatcherTest::timerMainThread() +{ + QTimer::singleShot(timerTimeout, [](){ + QtWasmTest::completeTestFunction(); + }); +} + +void WasmEventDispatcherTest::timerMainThreadMultiple() +{ + CompleteTestFunctionRefGuard *completeGuard = CompleteTestFunctionRefGuard::create(); + int timers = 10; + for (int i = 0; i < timers; ++i) { + completeGuard->ref(); + QTimer::singleShot(timerTimeout * i, [completeGuard](){ + completeGuard->deref(); + }); + } +} + +#if QT_CONFIG(thread) + +// Post event on a secondary thread and verify that it is processed. +void WasmEventDispatcherTest::postEventSecondaryThread() +{ + auto started = [](){ + QCoreApplication::postEvent(EventTarget::create([](){ + QThread::currentThread()->quit(); + }), EventTarget::createEvent()); + }; + + auto finished = [](){ + QtWasmTest::completeTestFunction(); + }; + + TestThread::create(started, finished); +} + +// Post event _to_ a secondary thread and verify that it is processed. +void WasmEventDispatcherTest::postEventToSecondaryThread() +{ + auto started = [](){}; + auto finished = [](){ + QtWasmTest::completeTestFunction(); + }; + + QThread *t = TestThread::create(started, finished); + EventTarget *target = EventTarget::create([](){ + QThread::currentThread()->quit(); + }); + target->moveToThread(t); + QCoreApplication::postEvent(target, EventTarget::createEvent()); +} + +// Post events to many secondary threads and verify that they are processed. +void WasmEventDispatcherTest::postEventSecondaryThreads() +{ + // This test completes afer all threads has finished + CompleteTestFunctionRefGuard *completeGuard = CompleteTestFunctionRefGuard::create(); + completeGuard->ref(); // including this thread + + auto started = [](){ + QCoreApplication::postEvent(EventTarget::create([](){ + QThread::currentThread()->quit(); + }), EventTarget::createEvent()); + }; + + auto finished = [completeGuard](){ + completeGuard->deref(); + }; + + // Start a nymber of threads in parallel, keeping in mind that the browser + // has some max number of concurrent web workers (maybe 20), and that starting + // a new web worker requires completing a GET network request for the worker's JS. + const int numThreads = 10; + for (int i = 0; i < numThreads; ++i) { + completeGuard->ref(); + TestThread::create(started, finished); + } + + completeGuard->deref(); +} + +// Create a timer a secondary thread and verify that it fires +void WasmEventDispatcherTest::timerSecondaryThread() +{ + auto started = [](){ + QTimer::singleShot(timerTimeout, [](){ + QThread::currentThread()->quit(); + }); + }; + + auto finished = [](){ + QtWasmTest::completeTestFunction(); + }; + + TestThread::create(started, finished); +} + +#endif + +// Post an event to the main thread and asyncify wait for it +void WasmEventDispatcherTest::postEventAsyncify() +{ + if (!qstdweb::haveAsyncify()) { + QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify"); + return; + } + + QEventLoop loop; + QCoreApplication::postEvent(EventTarget::create([&loop](){ + loop.quit(); + }), EventTarget::createEvent()); + loop.exec(); + + QtWasmTest::completeTestFunction(); +} + +// Create a timer on the main thread and asyncify wait for it +void WasmEventDispatcherTest::timerAsyncify() +{ + if (!qstdweb::haveAsyncify()) { + QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify"); + return; + } + + QEventLoop loop; + QTimer::singleShot(timerTimeout, [&loop](){ + loop.quit(); + }); + loop.exec(); + + QtWasmTest::completeTestFunction(); +} + +// Asyncify wait in a loop +void WasmEventDispatcherTest::postEventAsyncifyLoop() +{ + if (!qstdweb::haveAsyncify()) { + QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify"); + return; + } + + for (int i = 0; i < 10; ++i) { + QEventLoop loop; + QCoreApplication::postEvent(EventTarget::create([&loop]() { + loop.quit(); + }), EventTarget::createEvent()); + loop.exec(); + } + + QtWasmTest::completeTestFunction(); +} + +#if QT_CONFIG(thread) +// Asyncify wait for QThread::wait() / pthread_join() +void WasmEventDispatcherTest::threadAsyncifyWait() +{ + if (!qstdweb::haveAsyncify()) + QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify"); + + const int threadCount = 15; + + QVector<QThread *> threads; + threads.reserve(threadCount); + + for (int i = 0; i < threadCount; ++i) { + QThread *thread = new QThread(); + threads.push_back(thread); + thread->start(); + } + + for (int i = 0; i < threadCount; ++i) { + QThread *thread = threads[i]; + thread->wait(); + delete thread; + } + + QtWasmTest::completeTestFunction(); +} +#endif + +int main(int argc, char **argv) +{ + auto testObject = std::make_shared<WasmEventDispatcherTest>(); + QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject); + return 0; +} + +#include "main.moc" diff --git a/tests/manual/wasm/eventloop/main_exec/CMakeLists.txt b/tests/manual/wasm/eventloop/main_exec/CMakeLists.txt new file mode 100644 index 0000000000..1f263ddbcf --- /dev/null +++ b/tests/manual/wasm/eventloop/main_exec/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(main_exec + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui +) diff --git a/tests/manual/wasm/eventloop/main_exec/main.cpp b/tests/manual/wasm/eventloop/main_exec/main.cpp new file mode 100644 index 0000000000..17eccafe18 --- /dev/null +++ b/tests/manual/wasm/eventloop/main_exec/main.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtGui> + +// This example demonstrates how the standard Qt main() +// pattern works on Emscripten/WebAssambly, where exec() +// does not return. + +class ClickWindow: public QRasterWindow +{ +public: + ClickWindow() { + qDebug() << "ClickWindow constructor"; + } + ~ClickWindow() { + qDebug() << "ClickWindow destructor"; + } + + void paintEvent(QPaintEvent *ev) override { + QPainter p(this); + p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue); + p.drawText(50, 100, "Application has started. See the developer tools console for debug output"); + } + + void mousePressEvent(QMouseEvent *) override { + qDebug() << "mousePressEvent(): calling QGuiApplication::quit()"; + QGuiApplication::quit(); + } +}; + +int main(int argc, char **argv) +{ + qDebug() << "main(): Creating QGuiApplication object"; + QGuiApplication app(argc, argv); + + QObject::connect(&app, &QCoreApplication::aboutToQuit, [](){ + qDebug() << "QCoreApplication::aboutToQuit"; + }); + + ClickWindow window; + window.show(); + + qDebug() << "main(): calling exec()"; + app.exec(); + + // The exec() call above never returns; instead, a JavaScript exception + // is thrown such that control returns to the browser while preserving + // the C++ stack. + + // This means that the window object above is not destroyed, and that + // shutdown code after exec() does not run. + + qDebug() << "main(): after exit"; // <- will not be printed +} + +// Global variables are created before main() as usual, but not destroyed +class Global +{ +public: + Global() { + qDebug() << "Global constructor"; + } + ~Global() { + qDebug() << "Global destructor"; // <- will not be printed + } +}; +Global global; diff --git a/tests/manual/wasm/eventloop/main_noexec/CMakeLists.txt b/tests/manual/wasm/eventloop/main_noexec/CMakeLists.txt new file mode 100644 index 0000000000..e929089479 --- /dev/null +++ b/tests/manual/wasm/eventloop/main_noexec/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(main_noexec + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui +) diff --git a/tests/manual/wasm/eventloop/main_noexec/main.cpp b/tests/manual/wasm/eventloop/main_noexec/main.cpp new file mode 100644 index 0000000000..6ddd88bd14 --- /dev/null +++ b/tests/manual/wasm/eventloop/main_noexec/main.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtGui> + +// This example demonstrates how to create QGuiApplication +// without calling exec(), and then exiting main() without +// shutting down the Qt event loop. + +class ClickWindow: public QRasterWindow +{ +public: + + ClickWindow() { + qDebug() << "ClickWindow constructor"; + } + ~ClickWindow() { + qDebug() << "ClickWindow destructor"; + } + + void paintEvent(QPaintEvent *ev) override { + QPainter p(this); + p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue); + p.drawText(50, 100, "Application has started. See the developer tools console for debug output"); + } + + void mousePressEvent(QMouseEvent *) override { + qDebug() << "mousePressEvent(): calling QGuiApplication::quit()"; + QGuiApplication::quit(); + } +}; + +int main(int argc, char **argv) +{ + qDebug() << "main(): Creating QGuiApplication object"; + QGuiApplication *app = new QGuiApplication(argc, argv); + + QObject::connect(app, &QCoreApplication::aboutToQuit, [](){ + qDebug() << "QCoreApplication::aboutToQuit"; + }); + + qDebug() << "main(): Creating ClickWindow object"; + ClickWindow *window = new ClickWindow(); + window->show(); + + // We can exit main; the Qt event loop and the emscripten runtime + // will keep running, as long as Emscriptens EXIT_RUNTIME option + // has not been enabled. + + qDebug() << "main(): exit"; +} + +// Global variables are created before main() as usual, but not destroyed +class Global +{ +public: + Global() { + qDebug() << "Global constructor"; + } + ~Global() { + qDebug() << "Global destructor"; // <- will not be printed + } +}; +Global global; + + + diff --git a/tests/manual/wasm/eventloop/thread_exec/CMakeLists.txt b/tests/manual/wasm/eventloop/thread_exec/CMakeLists.txt new file mode 100644 index 0000000000..765ccee4f1 --- /dev/null +++ b/tests/manual/wasm/eventloop/thread_exec/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(thread_exec + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui +) diff --git a/tests/manual/wasm/eventloop/thread_exec/main.cpp b/tests/manual/wasm/eventloop/thread_exec/main.cpp new file mode 100644 index 0000000000..589066b34d --- /dev/null +++ b/tests/manual/wasm/eventloop/thread_exec/main.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtGui> + +class EventTarget : public QObject +{ + Q_OBJECT +protected: + bool event(QEvent *evt) + { + if (evt->type() == QEvent::User) { + qDebug() << "User event on thread" << QThread::currentThread(); + return true; + } + return QObject::event(evt); + } +}; + +class EventPosterWindow: public QRasterWindow +{ +public: + EventPosterWindow(EventTarget *target) + :m_target(target) + { } + + void paintEvent(QPaintEvent *ev) override { + QPainter p(this); + p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue); + p.drawText(50, 100, "Application has started. Click to post events.\n See the developer tools console for debug output"); + } + + void mousePressEvent(QMouseEvent *) override { + qDebug() << "Posting events from thread" << QThread::currentThread(); + QGuiApplication::postEvent(m_target, new QEvent(QEvent::User)); + QTimer::singleShot(500, m_target, []() { + qDebug() << "Timer event on secondary thread" << QThread::currentThread(); + }); + } + +public: + EventTarget *m_target; +}; + +class SecondaryThread : public QThread +{ +public: + void run() override { + qDebug() << "exec on secondary thread" << QThread::currentThread(); + exec(); + } +}; + +// This example demonstrates how to start a secondary thread event loop +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + EventTarget eventTarget; + + EventPosterWindow window(&eventTarget); + window.show(); + + SecondaryThread thread; + eventTarget.moveToThread(&thread); + +#if QT_CONFIG(thread) + thread.start(); +#else + qDebug() << "Warning: This test requires a multithreaded build of Qt for WebAssembly"; +#endif + + return app.exec(); +} + +#include "main.moc" diff --git a/tests/manual/wasm/eventloop/thread_exec/thread_exec.html b/tests/manual/wasm/eventloop/thread_exec/thread_exec.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/manual/wasm/eventloop/thread_exec/thread_exec.html |