comphelper/Library_comphelper.mk                       |    1 
 comphelper/source/misc/emscriptenthreading.cxx         |   60 ++++++++++++
 desktop/util/Executable_soffice_bin-emscripten-exports |    1 
 include/comphelper/emscriptenthreading.hxx             |   40 ++++++++
 include/vcl/syswin.hxx                                 |   24 ++++
 include/vcl/task.hxx                                   |    2 
 solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk         |   10 +-
 vcl/inc/qt5/QtInstance.hxx                             |   14 ++
 vcl/qt5/QtFrame.cxx                                    |   19 +++
 vcl/qt5/QtGraphicsBase.cxx                             |    8 +
 vcl/qt5/QtInstance.cxx                                 |   82 ++++++++++++++++-
 vcl/qt5/QtMenu.cxx                                     |   22 +++-
 vcl/qt5/QtTimer.cxx                                    |    7 +
 vcl/source/app/scheduler.cxx                           |   29 ++++++
 vcl/source/window/dialog.cxx                           |    6 -
 vcl/source/window/syswin.cxx                           |   21 +++-
 16 files changed, 323 insertions(+), 23 deletions(-)

New commits:
commit 364508802571c25ac993e9327c67e2d5c575f668
Author:     Stephan Bergmann <[email protected]>
AuthorDate: Tue Feb 11 14:11:25 2025 +0100
Commit:     Stephan Bergmann <[email protected]>
CommitDate: Wed Feb 12 13:14:26 2025 +0100

    Experimental Emscripten Qt6 --enable-emscripten-proxy-to-pthread ersatz
    
    With Qt5, the (enabled by default) --enable-emscripten-proxy-to-pthread 
(i.e.,
    leveraging Emscripten's -sPROXY_TO_PTHREAD) works reasonably well with the
    handful of hacks we added to our Qt5 5.15.2+wasm branch.  However, for Qt6 
any
    necessary hacking there would need to be excessive and looks unrealistic 
(see
    the mailing list thread starting at
    
<https://lists.qt-project.org/pipermail/development/2024-December/045960.html>
    "[Development] Wasm: Support for Emscripten PROXY_TO_PTHREAD?" and the
    presentation at
    
<https://fosdem.org/2025/schedule/event/fosdem-2025-5169-lowa-in-need-of-a-vcl-plug/>).
    
    But when leveraging the upcoming browser support for JSPI (through 
Emscripten's
    -sJSPI, Qt6's -feature-wasm-jspi, and our --enable-emscripten-jspi), it 
looks
    feasible to leave the Qt6 code alone and instead of proxying the complete
    application main thread (including the Qt6 event handling) off the browser 
main
    thread to an additional thread (automatically set up through Emscripten
    -sPROXY_TO_PTHREAD), to just proxy some of the LO event handling off the 
browser
    main thread to an additional thread (set up through a new
    comphelper::emscriptenthreading::setUp).  That way, problematic LO code 
that is
    triggered from event handling (like "Tools - Extensions..." wanting to spawn
    additional threads, which requires the browser main thread to be unblocked) 
can
    be offloaded to the additional thread and get the browser main thread 
unblocked.
    
    Thus there is a new "Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD" mode now (via
    --enable-qt6 --enable-emscripten-jspi 
--disable-emscripten-proxy-to-pthread).
    It is still experimental and known to occasionally hang and crash, but has 
been
    seen to generally work at least with recent emsdk 4.0.3 and a recent Qt dev
    branch (towards Qt 6.10, built with -feature-wasm-jspi) and running on 
recent
    Chrome 132 with "Experimental WebAssembly JavaScript Promise Integration 
(JSPI)"
    enabled under <chrome://flags>.
    
    There are two places where code is proxied off the browser main thread to 
the
    additional thread:  One is QtInstance::ProcessEvent, the other is invoking
    certain kinds of tasks (just "vcl::Dialog maLayoutIdle", for now) in
    Scheduler::CallbackTaskScheduling (unconditionally proxying off all kinds of
    tasks there appeared to cause more issues than it would solve).  For the 
latter,
    the concept of "transferability" has been added to class Task; the new
    Emscripten mode is the only case making use of that concept.
    
    Also for the latter, it is important that the browser main thread does not 
get
    blocked at the
    
    >     SolarMutexGuard aGuard;
    
    in QtTimer::timeoutActivated when such a timer event happens while the
    additional thread has the SolarMutex locked and tries to do something that
    requires the browser main thread to not be blocked (like spawning additional
    threads upon "Tools - Extensions...").  Therefore, as a bad hack, and only 
for
    the new Emscripen mode, that SolarMutexGuard has been moved from
    QtTimer::timeoutActivated down to the call of pTask->Invoke() in
    Scheduler::CallbackTaskScheduling (see the two TODO comments added to the 
code).
    This appears to work relatively well, but is still a bad hack that should 
rather
    be done properly.
    
    And then there's some code across vcl/qt5 that may now end up running 
proxied
    off the browser main thread, but which actually requires to be run on the
    browser main thread (because, e.g., it calls into Qt6 code that accesses JS
    entities that are only available there).  Those places use a newly 
introduced
    QtInstance::EmscriptenLightweightRunInMainThread that takes some code via a
    lambda:  For the new Emscripten mode, the code is explicitly proxied back 
to the
    browser main thread, while in all other cases it is run directly in place.
    
    Some further notes:
    
    * -sPTHREAD_POOL_SIZE needed to be bumped by one, to accommodate for the
      additional new thread.
    
    * The code requires two additional JSPI "entry points",
      _emscripten_check_mailbox (to complement the suspension in
      QtInstance::ProcessEvent) and the invocation of the Qt6-internal
      qstdweb::EventListener::handleEvent (to complement a Qt6-internal 
suspension
      point hit when e.g. doing "Tools - Extension Manager..." and clicking 
"Add").
    
    * The new Emscripten mode requires -fexperimental-library (for std::jthread 
and
      std::stop_token) when building at least with emsdk 4.0.3, but it shouldn't
      hurt to have that switch enabled unconditionally for Emscripten builds 
(which
      are self-contained, so can't be hit by any compatibility issues that 
switch
      might cause).
    
    Change-Id: Id3b13e2cc3c304b072f388c6d736e7e663b36c58
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181424
    Tested-by: Jenkins
    Reviewed-by: Stephan Bergmann <[email protected]>
    Reviewed-by: Michael Weghorn <[email protected]>

diff --git a/comphelper/Library_comphelper.mk b/comphelper/Library_comphelper.mk
index 85a43d6a779b..f4257653d5a1 100644
--- a/comphelper/Library_comphelper.mk
+++ b/comphelper/Library_comphelper.mk
@@ -107,6 +107,7 @@ $(eval $(call gb_Library_add_exception_objects,comphelper,\
     comphelper/source/misc/docpasswordhelper \
     comphelper/source/misc/docpasswordrequest \
     comphelper/source/misc/documentinfo \
+    comphelper/source/misc/emscriptenthreading \
     comphelper/source/misc/errcode \
     comphelper/source/misc/evtlistenerhlp \
     comphelper/source/misc/evtmethodhelper \
diff --git a/comphelper/source/misc/emscriptenthreading.cxx 
b/comphelper/source/misc/emscriptenthreading.cxx
new file mode 100644
index 000000000000..483e3c7445ad
--- /dev/null
+++ b/comphelper/source/misc/emscriptenthreading.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <comphelper/emscriptenthreading.hxx>
+#include <config_emscripten.h>
+#include <config_vclplug.h>
+
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+
+#include <cassert>
+#include <mutex>
+#include <stop_token>
+#include <thread>
+
+namespace
+{
+std::mutex mutex;
+comphelper::emscriptenthreading::Data* data = nullptr;
+}
+
+void comphelper::emscriptenthreading::setUp()
+{
+    std::scoped_lock g(mutex);
+    assert(data == nullptr);
+    data = new Data;
+    data->eventHandlerThread = std::jthread([](std::stop_token token) {
+        while (!token.stop_requested())
+        {
+            data->proxyingQueue.execute();
+        }
+    });
+}
+
+void comphelper::emscriptenthreading::tearDown()
+{
+    std::scoped_lock g(mutex);
+    assert(data != nullptr);
+    data->eventHandlerThread.request_stop();
+    data->eventHandlerThread.join();
+    data = nullptr;
+}
+
+comphelper::emscriptenthreading::Data& 
comphelper::emscriptenthreading::getData()
+{
+    std::scoped_lock g(mutex);
+    assert(data != nullptr);
+    return *data;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/desktop/util/Executable_soffice_bin-emscripten-exports 
b/desktop/util/Executable_soffice_bin-emscripten-exports
index 2206ccccb2d5..7fde7f859828 100644
--- a/desktop/util/Executable_soffice_bin-emscripten-exports
+++ b/desktop/util/Executable_soffice_bin-emscripten-exports
@@ -12,3 +12,4 @@ _lok_preinit
 _lok_preinit_2
 _malloc
 _free
+__ZN10emscripten8internal13MethodInvokerINS0_3rvp11default_tagEMN7qstdweb13EventListenerEFvNS_3valEEvPS5_JS6_EE6invokeERKS8_S9_PNS_7_EM_VALE
diff --git a/include/comphelper/emscriptenthreading.hxx 
b/include/comphelper/emscriptenthreading.hxx
new file mode 100644
index 000000000000..6f1b9306caa5
--- /dev/null
+++ b/include/comphelper/emscriptenthreading.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <config_emscripten.h>
+#include <config_vclplug.h>
+
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+
+#include <thread>
+
+#include <emscripten/proxying.h>
+
+namespace comphelper::emscriptenthreading
+{
+struct Data
+{
+    emscripten::ProxyingQueue proxyingQueue;
+    std::jthread eventHandlerThread;
+};
+
+void setUp();
+
+void tearDown();
+
+Data& getData();
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/include/vcl/syswin.hxx b/include/vcl/syswin.hxx
index c1a5a8879310..34cbde005260 100644
--- a/include/vcl/syswin.hxx
+++ b/include/vcl/syswin.hxx
@@ -96,6 +96,25 @@ class VCL_DLLPUBLIC SystemWindow
     class ImplData;
 
 private:
+    class LayoutIdle: public Idle {
+    public:
+        LayoutIdle(char const * pDebugName, SystemWindow & parent, bool 
transferable):
+            Idle(pDebugName), parent_(parent),
+            transferState_(
+                transferable ? TransferState::Transferable : 
TransferState::NotTransferable)
+        {}
+
+        bool DecideTransferredExecution() override;
+
+        bool wasTransferred() const { return transferState_ == 
TransferState::Transferred; }
+
+    private:
+        enum class TransferState { NotTransferable, Transferable, Transferred 
};
+
+        SystemWindow & parent_;
+        TransferState transferState_;
+    };
+
     std::unique_ptr<ImplData> mpImplData;
     VclPtr<MenuBar> mpMenuBar;
     OUString        maNotebookBarUIFile;
@@ -110,7 +129,7 @@ private:
     bool            mbInSetNoteBookBar : 1 = false;
     bool            mbPaintComplete : 1 = false;
     bool            mbIsDeferredInit : 1 = false;
-    Idle            maLayoutIdle;
+    LayoutIdle      maLayoutIdle;
 protected:
     VclPtr<vcl::Window> mpDialogParent;
 public:
@@ -136,7 +155,8 @@ private:
 
 protected:
     // Single argument ctors shall be explicit.
-    SAL_DLLPRIVATE explicit SystemWindow(WindowType nType, const char* 
pIdleDebugName);
+    SAL_DLLPRIVATE explicit SystemWindow(
+        WindowType nType, const char* pIdleDebugName, bool transferableIdle = 
false);
     SAL_DLLPRIVATE void loadUI(vcl::Window* pParent, const OUString& rID, 
const OUString& rUIXMLDescription, const 
css::uno::Reference<css::frame::XFrame> &rFrame = 
css::uno::Reference<css::frame::XFrame>());
 
     SAL_DLLPRIVATE void SetWindowState(const vcl::WindowData& rData);
diff --git a/include/vcl/task.hxx b/include/vcl/task.hxx
index 41c73bf39644..f6e1d7b49459 100644
--- a/include/vcl/task.hxx
+++ b/include/vcl/task.hxx
@@ -82,6 +82,8 @@ public:
 
     const char     *GetDebugName() const { return mpDebugName; }
 
+    virtual bool DecideTransferredExecution();
+
     // Call handler
     virtual void    Invoke() = 0;
 
diff --git a/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk 
b/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
index 4165281d1c09..495cc87c9d46 100644
--- a/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
+++ b/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
@@ -18,7 +18,7 @@ gb_EMSCRIPTEN_LDFLAGS := $(gb_EMSCRIPTEN_CPPFLAGS)
 gb_EMSCRIPTEN_LDFLAGS += -s TOTAL_MEMORY=1GB
 
 ifeq ($(ENABLE_EMSCRIPTEN_PROXY_TO_PTHREAD),)
-gb_EMSCRIPTEN_LDFLAGS += -sPTHREAD_POOL_SIZE=6
+gb_EMSCRIPTEN_LDFLAGS += -sPTHREAD_POOL_SIZE=7
 endif
 
 # Double the main thread stack size, but keep the default value for other 
threads:
@@ -31,7 +31,9 @@ gb_EMSCRIPTEN_LDFLAGS += --bind -s FORCE_FILESYSTEM=1 -s 
WASM_BIGINT=1 -s ERROR_
 gb_EMSCRIPTEN_QTDEFS := -DQT_NO_LINKED_LIST -DQT_NO_JAVA_STYLE_ITERATORS 
-DQT_NO_EXCEPTIONS -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB
 
 ifeq ($(ENABLE_EMSCRIPTEN_JSPI),TRUE)
-gb_EMSCRIPTEN_LDFLAGS += -sJSPI
+gb_EMSCRIPTEN_LDFLAGS += \
+    -sJSPI \
+    
-sJSPI_EXPORTS=_emscripten_check_mailbox,_ZN10emscripten8internal13MethodInvokerINS0_3rvp11default_tagEMN7qstdweb13EventListenerEFvNS_3valEEvPS5_JS6_EE6invokeERKS8_S9_PNS_7_EM_VALE
 endif
 
 ifeq ($(ENABLE_EMSCRIPTEN_PROXY_POSIX_SOCKETS),TRUE)
@@ -56,6 +58,10 @@ endif
 gb_LinkTarget_LDFLAGS += $(gb_EMSCRIPTEN_LDFLAGS) $(gb_EMSCRIPTEN_CPPFLAGS) \
     $(gb_EMSCRIPTEN_EXCEPT) -sEXPORT_EXCEPTION_HANDLING_HELPERS
 
+# Depending on emsdk version being used, enable e.g. std::jthread and 
std::stop_token used by some
+# build configurations:
+gb_LinkTarget_CXXFLAGS += -fexperimental-library
+
 ifeq ($(ENABLE_OPTIMIZED),TRUE)
 ifneq ($(ENABLE_SYMBOLS_FOR),)
 gb_LinkTarget__emscripten_warnings_ldflags := 
-Wno-limited-postlink-optimizations
diff --git a/vcl/inc/qt5/QtInstance.hxx b/vcl/inc/qt5/QtInstance.hxx
index 90c32bf06bd5..50c6fd64bbbf 100644
--- a/vcl/inc/qt5/QtInstance.hxx
+++ b/vcl/inc/qt5/QtInstance.hxx
@@ -19,6 +19,8 @@
 
 #pragma once
 
+#include <config_emscripten.h>
+#include <config_vclplug.h>
 #include <vclpluginapi.h>
 #include <unx/geninst.h>
 #include <salusereventlist.hxx>
@@ -43,6 +45,13 @@ class QApplication;
 class SalYieldMutex;
 class SalFrame;
 
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+namespace comphelper::emscriptenthreading
+{
+struct Data;
+}
+#endif
+
 struct StdFreeCStr
 {
     void operator()(char* arg) const noexcept { std::free(arg); }
@@ -71,6 +80,10 @@ class VCLPLUG_QT_PUBLIC QtInstance : public QObject,
 
     QtFrame* m_pActivePopup;
 
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    comphelper::emscriptenthreading::Data* m_emscriptenThreadingData;
+#endif
+
     DECL_DLLPRIVATE_LINK(updateStyleHdl, Timer*, void);
     void AfterAppInit() override;
 
@@ -114,6 +127,7 @@ public:
     static std::unique_ptr<QApplication> CreateQApplication(int& nArgc, char** 
pArgv);
 
     void RunInMainThread(std::function<void()> func);
+    void EmscriptenLightweightRunInMainThread(std::function<void()> func);
 
     virtual SalFrame* CreateFrame(SalFrame* pParent, SalFrameStyleFlags 
nStyle) override;
     virtual SalFrame* CreateChildFrame(SystemParentData* pParent,
diff --git a/vcl/qt5/QtFrame.cxx b/vcl/qt5/QtFrame.cxx
index 791901dcbc29..4d020ec93395 100644
--- a/vcl/qt5/QtFrame.cxx
+++ b/vcl/qt5/QtFrame.cxx
@@ -195,8 +195,10 @@ QtFrame::~QtFrame()
 void QtFrame::Damage(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 
nExtentsWidth,
                      sal_Int32 nExtentsHeight) const
 {
-    m_pQWidget->update(scaledQRect(QRect(nExtentsX, nExtentsY, nExtentsWidth, 
nExtentsHeight),
-                                   1 / devicePixelRatioF()));
+    GetQtInstance().EmscriptenLightweightRunInMainThread([
+        this, r = scaledQRect(QRect(nExtentsX, nExtentsY, nExtentsWidth, 
nExtentsHeight),
+                              1 / devicePixelRatioF())
+    ] { m_pQWidget->update(r); });
 }
 
 SalGraphics* QtFrame::AcquireGraphics()
@@ -258,7 +260,13 @@ QWidget* QtFrame::asChild() const
     return m_pQWidget;
 }
 
-qreal QtFrame::devicePixelRatioF() const { return 
asChild()->devicePixelRatioF(); }
+qreal QtFrame::devicePixelRatioF() const
+{
+    qreal ret;
+    GetQtInstance().EmscriptenLightweightRunInMainThread(
+        [ child = asChild(), &ret ] { ret = child->devicePixelRatioF(); });
+    return ret;
+}
 
 bool QtFrame::isWindow() const { return asChild()->isWindow(); }
 
@@ -430,7 +438,10 @@ void QtFrame::SetMinClientSize(tools::Long nWidth, 
tools::Long nHeight)
     if (!isChild())
     {
         const qreal fRatio = devicePixelRatioF();
-        asChild()->setMinimumSize(round(nWidth / fRatio), round(nHeight / 
fRatio));
+        GetQtInstance().EmscriptenLightweightRunInMainThread(
+            [ child = asChild(), w = round(nWidth / fRatio), h = round(nHeight 
/ fRatio) ] {
+                child->setMinimumSize(w, h);
+            });
     }
 }
 
diff --git a/vcl/qt5/QtGraphicsBase.cxx b/vcl/qt5/QtGraphicsBase.cxx
index 12f939d1b931..bfed88a8954b 100644
--- a/vcl/qt5/QtGraphicsBase.cxx
+++ b/vcl/qt5/QtGraphicsBase.cxx
@@ -8,6 +8,7 @@
  */
 
 #include <QtGraphicsBase.hxx>
+#include <QtInstance.hxx>
 
 #include <QtGui/QScreen>
 
@@ -26,8 +27,11 @@ void QtGraphicsBase::ImplGetResolution(QtFrame* pFrame, 
sal_Int32& rDPIX, sal_In
         return;
 
     QScreen* pScreen = pFrame->GetQWidget()->screen();
-    rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
-    rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+    qreal devicePixelRatio;
+    GetQtInstance().EmscriptenLightweightRunInMainThread(
+        [pScreen, &devicePixelRatio] { devicePixelRatio = 
pScreen->devicePixelRatio(); });
+    rDPIX = pScreen->logicalDotsPerInchX() * devicePixelRatio + 0.5;
+    rDPIY = pScreen->logicalDotsPerInchY() * devicePixelRatio + 0.5;
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/vcl/qt5/QtInstance.cxx b/vcl/qt5/QtInstance.cxx
index 2a206aaf6a2f..a1482687b874 100644
--- a/vcl/qt5/QtInstance.cxx
+++ b/vcl/qt5/QtInstance.cxx
@@ -52,6 +52,7 @@
 
 #include <vclpluginapi.h>
 #include <tools/debug.hxx>
+#include <comphelper/emscriptenthreading.hxx>
 #include <comphelper/flagguard.hxx>
 #include <config_emscripten.h>
 #include <config_vclplug.h>
@@ -61,6 +62,7 @@
 #include <vcl/sysdata.hxx>
 #include <sal/log.hxx>
 #include <o3tl/unreachable.hxx>
+#include <o3tl/temporary.hxx>
 #include <osl/process.h>
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && 
QT5_HAVE_GOBJECT
 #include <unx/gstsink.hxx>
@@ -78,6 +80,13 @@ Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
 #endif
 #endif
 
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+#include <emscripten/promise.h>
+#include <emscripten/proxying.h>
+#include <emscripten/threading.h>
+#include <pthread.h>
+#endif
+
 namespace
 {
 /// TODO: not much Qt specific here? could be generalised, esp. for OSX...
@@ -226,6 +235,13 @@ void QtInstance::RunInMainThread(std::function<void()> 
func)
         func();
         return;
     }
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    if (pthread_self() == 
m_emscriptenThreadingData->eventHandlerThread.native_handle())
+    {
+        EmscriptenLightweightRunInMainThread(func);
+        return;
+    }
+#endif
 
     QtYieldMutex* const pMutex(static_cast<QtYieldMutex*>(GetYieldMutex()));
     {
@@ -245,6 +261,26 @@ void QtInstance::RunInMainThread(std::function<void()> 
func)
     }
 }
 
+void QtInstance::EmscriptenLightweightRunInMainThread(std::function<void()> 
func)
+{
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    if (pthread_self() != emscripten_main_runtime_thread_id())
+    {
+        SolarMutexReleaser release;
+        emscripten_sync_run_in_main_runtime_thread(
+            EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(1)
+                | EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_P),
+            +[](void* pf) {
+                SolarMutexGuard g;
+                (*static_cast<std::function<void()>*>(pf))();
+            },
+            &func);
+        return;
+    }
+#endif
+    func();
+}
+
 OUString QtInstance::constructToolkitID(std::u16string_view sTKname)
 {
     OUString sID(sTKname + OUString::Concat(u" ("));
@@ -266,6 +302,11 @@ QtInstance::QtInstance(std::unique_ptr<QApplication>& 
pQApp)
     , m_bUpdateFonts(false)
     , m_pActivePopup(nullptr)
 {
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    comphelper::emscriptenthreading::setUp();
+    m_emscriptenThreadingData = &comphelper::emscriptenthreading::getData();
+#endif
+
     ImplSVData* pSVData = ImplGetSVData();
     const OUString sToolkit = "qt" + OUString::number(QT_VERSION_MAJOR);
     pSVData->maAppData.mxToolkitName = constructToolkitID(sToolkit);
@@ -309,6 +350,10 @@ QtInstance::~QtInstance()
     // force freeing the QApplication before freeing the arguments,
     // as it uses references to the provided arguments!
     m_pQApplication.reset();
+
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    comphelper::emscriptenthreading::tearDown();
+#endif
 }
 
 void QtInstance::AfterAppInit()
@@ -497,6 +542,26 @@ bool QtInstance::DoYield(bool bWait, bool 
bHandleAllCurrentEvents)
         if (bWasEvent)
             m_aWaitingYieldCond.set();
     }
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    else if (pthread_self() == 
m_emscriptenThreadingData->eventHandlerThread.native_handle())
+    {
+        SolarMutexReleaser release;
+        struct Args
+        {
+            QtInstance* This;
+            bool bWait;
+            bool bHandleAllCurrentEvents;
+            bool& bWasEvent;
+        };
+        (void)emscripten_promise_await(emscripten_proxy_promise(
+            m_emscriptenThreadingData->proxyingQueue.queue, 
emscripten_main_runtime_thread_id(),
+            [](void* p) {
+                Args const& args = *static_cast<Args*>(p);
+                args.bWasEvent = args.This->DoYield(args.bWait, 
args.bHandleAllCurrentEvents);
+            },
+            &o3tl::temporary<Args>({ this, bWait, bHandleAllCurrentEvents, 
bWasEvent })));
+    }
+#endif
     else
     {
         {
@@ -545,7 +610,20 @@ void QtInstance::TriggerUserEventProcessing()
 
 void QtInstance::ProcessEvent(SalUserEvent aEvent)
 {
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+    SolarMutexReleaser release;
+    (void)emscripten_promise_await(
+        
emscripten_proxy_promise(m_emscriptenThreadingData->proxyingQueue.queue,
+                                 
m_emscriptenThreadingData->eventHandlerThread.native_handle(),
+                                 [](void* p) {
+                                     auto& aEvent = 
*static_cast<SalUserEvent*>(p);
+                                     SolarMutexGuard g;
+                                     
aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
+                                 },
+                                 &aEvent));
+#else
     aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
+#endif
 }
 
 rtl::Reference<QtFilePicker>
@@ -601,7 +679,9 @@ QtInstance::CreateClipboard(const 
css::uno::Sequence<css::uno::Any>& arguments)
     if (it != m_aClipboards.end())
         return it->second;
 
-    css::uno::Reference<css::uno::XInterface> xClipboard = 
QtClipboard::create(sel);
+    css::uno::Reference<css::uno::XInterface> xClipboard;
+    EmscriptenLightweightRunInMainThread(
+        [&sel, &xClipboard] { xClipboard = QtClipboard::create(sel); });
     if (xClipboard.is())
         m_aClipboards[sel] = xClipboard;
 
diff --git a/vcl/qt5/QtMenu.cxx b/vcl/qt5/QtMenu.cxx
index 9b45106338cb..2d677bf6112a 100644
--- a/vcl/qt5/QtMenu.cxx
+++ b/vcl/qt5/QtMenu.cxx
@@ -769,19 +769,24 @@ QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& 
rIcon, const QString& rTo
     if (!pWidget)
     {
         assert(!m_pButtonGroup);
-        pWidget = new QWidget(mpQMenuBar);
+        GetQtInstance().EmscriptenLightweightRunInMainThread(
+            [this, &pWidget] { pWidget = new QWidget(mpQMenuBar); });
         assert(!pWidget->layout());
-        pLayout = new QHBoxLayout();
+        GetQtInstance().EmscriptenLightweightRunInMainThread(
+            [&pLayout] { pLayout = new QHBoxLayout(); });
         pLayout->setContentsMargins(QMargins());
         pLayout->setSpacing(0);
         pWidget->setLayout(pLayout);
-        m_pButtonGroup = new QButtonGroup(pLayout);
+        GetQtInstance().EmscriptenLightweightRunInMainThread(
+            [this, pLayout] { m_pButtonGroup = new QButtonGroup(pLayout); });
         m_pButtonGroup->setObjectName(gButtonGroupKey);
         m_pButtonGroup->setExclusive(false);
         connect(m_pButtonGroup, 
QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
                 &QtMenu::slotMenuBarButtonClicked);
-        pWidget->show();
-        mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
+        GetQtInstance().EmscriptenLightweightRunInMainThread([this, pWidget] {
+            pWidget->show();
+            mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
+        });
     }
     else
         pLayout = static_cast<QHBoxLayout*>(pWidget->layout());
@@ -792,7 +797,8 @@ QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& 
rIcon, const QString& rTo
     if (pButton)
         RemoveMenuBarButton(nId);
 
-    pButton = new QPushButton();
+    GetQtInstance().EmscriptenLightweightRunInMainThread(
+        [&pButton] { pButton = new QPushButton(); });
     // we don't want the button to increase the QMenuBar height, so a fixed 
size square it is
     const int nFixedLength
         = mpQMenuBar->height() - 2 * 
mpQMenuBar->style()->pixelMetric(QStyle::PM_MenuBarVMargin);
@@ -828,7 +834,9 @@ void QtMenu::connectHelpShortcut(QMenu* pMenu)
 {
     assert(pMenu);
     QKeySequence sequence(QKeySequence::HelpContents);
-    QShortcut* pQShortcut = new QShortcut(sequence, pMenu);
+    QShortcut* pQShortcut;
+    GetQtInstance().EmscriptenLightweightRunInMainThread(
+        [&sequence, pMenu, &pQShortcut] { pQShortcut = new QShortcut(sequence, 
pMenu); });
     connect(pQShortcut, &QShortcut::activated, this, QtMenu::slotShowHelp);
     connect(pQShortcut, &QShortcut::activatedAmbiguously, this, 
QtMenu::slotShowHelp);
 }
diff --git a/vcl/qt5/QtTimer.cxx b/vcl/qt5/QtTimer.cxx
index 9506801609e9..78856d104281 100644
--- a/vcl/qt5/QtTimer.cxx
+++ b/vcl/qt5/QtTimer.cxx
@@ -25,6 +25,8 @@
 #include <QtWidgets/QApplication>
 #include <QtCore/QThread>
 
+#include <config_emscripten.h>
+#include <config_vclplug.h>
 #include <vcl/svapp.hxx>
 #include <sal/log.hxx>
 
@@ -41,7 +43,12 @@ QtTimer::QtTimer()
 
 void QtTimer::timeoutActivated()
 {
+#if !(defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD)
+    //TODO: While the special Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD mode 
doesn't lock the
+    // SolarMutex here, but only when calling pTask->Invoke() in 
Scheduler::CallbackTaskScheduling,
+    // that looks too brittle in general, so treat that special mode specially 
here.
     SolarMutexGuard aGuard;
+#endif
     if (Application::IsUseSystemEventLoop())
     {
         const ImplSVData* pSVData = ImplGetSVData();
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
index eaca69adafa1..6234e6355117 100644
--- a/vcl/source/app/scheduler.cxx
+++ b/vcl/source/app/scheduler.cxx
@@ -25,6 +25,8 @@
 #include <typeinfo>
 
 #include <com/sun/star/uno/Exception.hpp>
+#include <config_emscripten.h>
+#include <config_vclplug.h>
 #include <sal/log.hxx>
 #include <sal/types.h>
 #include <svdata.hxx>
@@ -37,6 +39,7 @@
 #include <vcl/idle.hxx>
 #include <saltimer.hxx>
 #include <salinst.hxx>
+#include <comphelper/emscriptenthreading.hxx>
 #include <comphelper/profilezone.hxx>
 #include <schedulerimpl.hxx>
 
@@ -358,7 +361,12 @@ void Scheduler::CallbackTaskScheduling()
     ImplSVData *pSVData = ImplGetSVData();
     ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
 
+#if !(defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD)
+    //TODO: While the special Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD mode 
doesn't lock the
+    // SolarMutex in QtTimer::timeoutActivated, but only down below when 
calling pTask->Invoke(),
+    // that looks too brittle in general, so treat that special mode specially 
here.
     DBG_TESTSOLARMUTEX();
+#endif
 
     SchedulerGuard aSchedulerGuard;
     if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod )
@@ -506,7 +514,23 @@ void Scheduler::CallbackTaskScheduling()
         {
             // prepare Scheduler object for deletion after handling
             pTask->SetDeletionFlags();
+#if defined EMSCRIPTEN && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI && 
!HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
+            if (pTask->DecideTransferredExecution())
+            {
+                auto & data = comphelper::emscriptenthreading::getData();
+                
data.proxyingQueue.proxyAsync(data.eventHandlerThread.native_handle(), [pTask] {
+                    SolarMutexGuard g;
+                    pTask->Invoke();
+                });
+            }
+            else
+            {
+                SolarMutexGuard g;
+                pTask->Invoke();
+            }
+#else
             pTask->Invoke();
+#endif
         }
     }
     catch (css::uno::Exception&)
@@ -693,4 +717,9 @@ Task::~Task() COVERITY_NOEXCEPT_FALSE
         assert(nullptr == mpSchedulerData || comphelper::IsFuzzing());
 }
 
+bool Task::DecideTransferredExecution()
+{
+    return false;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx
index d407626388d6..b65319e9aea5 100644
--- a/vcl/source/window/dialog.cxx
+++ b/vcl/source/window/dialog.cxx
@@ -548,7 +548,7 @@ void Dialog::ImplLOKNotifier(vcl::Window* pParent)
 }
 
 Dialog::Dialog( WindowType nType )
-    : SystemWindow( nType, "vcl::Dialog maLayoutIdle" )
+    : SystemWindow( nType, "vcl::Dialog maLayoutIdle", true )
     , mnInitFlag(InitFlag::Default)
 {
     ImplInitDialogData();
@@ -574,7 +574,7 @@ void Dialog::ImplDeferredInit(vcl::Window* pParent, WinBits 
nBits)
 }
 
 Dialog::Dialog(vcl::Window* pParent, const OUString& rID, const OUString& 
rUIXMLDescription)
-    : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
+    : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle", true)
     , mnInitFlag(InitFlag::Default)
 {
     ImplLOKNotifier(pParent);
@@ -583,7 +583,7 @@ Dialog::Dialog(vcl::Window* pParent, const OUString& rID, 
const OUString& rUIXML
 }
 
 Dialog::Dialog(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
-    : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
+    : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle", true)
     , mnInitFlag(eFlag)
 {
     ImplLOKNotifier(pParent);
diff --git a/vcl/source/window/syswin.cxx b/vcl/source/window/syswin.cxx
index 6c3e89ca50f1..1601501f0571 100644
--- a/vcl/source/window/syswin.cxx
+++ b/vcl/source/window/syswin.cxx
@@ -17,10 +17,12 @@
  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
  */
 
+#include <cassert>
 #include <memory>
 
 #include "menubarwindow.hxx"
 
+#include <comphelper/scopeguard.hxx>
 #include <o3tl/safeint.hxx>
 #include <sal/config.h>
 #include <sal/log.hxx>
@@ -66,10 +68,20 @@ SystemWindow::ImplData::ImplData()
     maMaxOutSize = Size( SHRT_MAX, SHRT_MAX );
 }
 
-SystemWindow::SystemWindow(WindowType nType, const char* pIdleDebugName)
+bool SystemWindow::LayoutIdle::DecideTransferredExecution()
+{
+    if (transferState_ == TransferState::NotTransferable) {
+        return false;
+    }
+    parent_.acquire(); // paired with release in ImplHandleLayoutTimerHdl
+    transferState_ = TransferState::Transferred;
+    return true;
+}
+
+SystemWindow::SystemWindow(WindowType nType, const char* pIdleDebugName, bool 
transferableIdle)
     : Window(nType)
     , mpImplData(new ImplData)
-    , maLayoutIdle( pIdleDebugName )
+    , maLayoutIdle( pIdleDebugName, *this, transferableIdle )
 {
     mpWindowImpl->mbSysWin            = true;
     mpWindowImpl->mnActivateMode      = ActivateModeFlags::GrabFocus;
@@ -1036,6 +1048,11 @@ void SystemWindow::setPosSizeOnContainee(Size aSize, 
Window &rBox)
 
 IMPL_LINK_NOARG( SystemWindow, ImplHandleLayoutTimerHdl, Timer*, void )
 {
+    comphelper::ScopeGuard g([this] {
+        if (maLayoutIdle.wasTransferred()) {
+            release(); // paired with acquire in 
LayoutIdle::DecideTransferredExecution
+        }
+    });
     Window *pBox = GetWindow(GetWindowType::FirstChild);
     if (!isLayoutEnabled())
     {

Reply via email to