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 <stephan.bergmann@allotropia.de>
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
This commit is contained in:
Stephan Bergmann
2025-02-12 13:14:26 +01:00
parent af7e4a555f
commit 3645088025
16 changed files with 323 additions and 23 deletions
+1
View File
@@ -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 \
@@ -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: */
@@ -12,3 +12,4 @@ _lok_preinit
_lok_preinit_2
_malloc
_free
__ZN10emscripten8internal13MethodInvokerINS0_3rvp11default_tagEMN7qstdweb13EventListenerEFvNS_3valEEvPS5_JS6_EE6invokeERKS8_S9_PNS_7_EM_VALE
@@ -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: */
+22 -2
View File
@@ -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);
+2
View File
@@ -82,6 +82,8 @@ public:
const char *GetDebugName() const { return mpDebugName; }
virtual bool DecideTransferredExecution();
// Call handler
virtual void Invoke() = 0;
@@ -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
+14
View File
@@ -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,
+15 -4
View File
@@ -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);
});
}
}
+6 -2
View File
@@ -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: */
+81 -1
View File
@@ -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;
+15 -7
View File
@@ -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);
}
+7
View File
@@ -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();
+29
View File
@@ -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: */
+3 -3
View File
@@ -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);
+19 -2
View File
@@ -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())
{