wasm: disable spinbox context menu if not ASYNCIFY to avoid deadlock

Using exec will lock the application if asyncify is not used

Fixes: QTBUG-120925
Change-Id: Ic8b3acc402f3ecbfb07fd8cb80013e02e2421402
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Even Oscar Andersen 2024-03-15 22:28:59 +01:00
parent 183a775f80
commit 00ce86a908
3 changed files with 115 additions and 33 deletions

View File

@ -2,6 +2,9 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <qplatformdefs.h> #include <qplatformdefs.h>
#ifdef Q_OS_WASM
# include <private/qstdweb_p.h>
#endif
#include <private/qabstractspinbox_p.h> #include <private/qabstractspinbox_p.h>
#include <private/qapplication_p.h> #include <private/qapplication_p.h>
#if QT_CONFIG(datetimeparser) #if QT_CONFIG(datetimeparser)
@ -1281,6 +1284,12 @@ void QAbstractSpinBox::timerEvent(QTimerEvent *event)
#if QT_CONFIG(contextmenu) #if QT_CONFIG(contextmenu)
void QAbstractSpinBox::contextMenuEvent(QContextMenuEvent *event) void QAbstractSpinBox::contextMenuEvent(QContextMenuEvent *event)
{ {
#ifdef Q_OS_WASM
if (!qstdweb::haveAsyncify()) {
qDebug() << " Skipping context menu for spinbox since asyncify is off";
return;
}
#endif
Q_D(QAbstractSpinBox); Q_D(QAbstractSpinBox);
QPointer<QMenu> menu = d->edit->createStandardContextMenu(); QPointer<QMenu> menu = d->edit->createStandardContextMenu();

View File

@ -81,6 +81,35 @@ class WidgetTestCase(unittest.TestCase):
clearWidgets(self._driver) clearWidgets(self._driver)
#Looks weird, no asserts, the test is that
#the test itself finishes
def test_showContextMenu_doesNotDeadlock(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=1200)
w0 = Widget(self._driver, "w0")
w0.show()
w0.showContextMenu()
w1 = Widget(self._driver, "w1")
w1.setNoFocusShow()
w1.show()
w1.showContextMenu()
w2 = Widget(self._driver, "w2")
w2.show()
w2.showContextMenu()
w3 = Widget(self._driver, "w3")
w3.setNoFocusShow()
w3.show()
w3.showContextMenu()
w3.activate();
w3.showContextMenu()
clearWidgets(self._driver)
def test_window_resizing(self): def test_window_resizing(self):
screen = Screen(self._driver, ScreenPosition.FIXED, screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=600) x=0, y=0, width=600, height=600)
@ -686,6 +715,12 @@ class Widget:
information = call_instance_function(self.driver, 'windowInformation') information = call_instance_function(self.driver, 'windowInformation')
return next(filter(lambda e: e['title'] == "Dialog", information)) return next(filter(lambda e: e['title'] == "Dialog", information))
def showContextMenu(self):
self.driver.execute_script(
f'''
instance.showContextMenuWidget('{self.name}');
'''
)
class Window: class Window:
def __init__(self, parent=None, rect=None, title=None, element=None, visible=True, opengl=0): def __init__(self, parent=None, rect=None, title=None, element=None, visible=True, opengl=0):

View File

@ -20,6 +20,7 @@
#include <QFileSystemModel> #include <QFileSystemModel>
#include <QScrollArea> #include <QScrollArea>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QSpinBox>
#include <QOpenGLWindow> #include <QOpenGLWindow>
#include <QOpenGLFunctions> #include <QOpenGLFunctions>
@ -33,6 +34,34 @@
#include <sstream> #include <sstream>
#include <vector> #include <vector>
// Our dialog to test two things
// 1) Focus logic
// 2) spinbox context menu
class TestWidget : public QDialog
{
Q_OBJECT
};
// We override to be able to test that the contextMenu
// calls popup and not exec. Calling exec locks the
// test.
class TestSpinBox : public QSpinBox
{
Q_OBJECT
public:
TestSpinBox(QWidget *parent = nullptr) : QSpinBox(parent) { }
void ShowContextMenu()
{
QContextMenuEvent event(QContextMenuEvent::Reason::Mouse, QPoint(0, geometry().bottom()),
mapToGlobal(QPoint(0, geometry().bottom())), Qt::NoModifier);
contextMenuEvent(&event);
}
};
// Baseclass for our windows, openglwindow and raster window
class TestWindowBase class TestWindowBase
{ {
public: public:
@ -45,11 +74,6 @@ public:
virtual void opengl_color_at_0_0(int *r, int *g, int *b) = 0; virtual void opengl_color_at_0_0(int *r, int *g, int *b) = 0;
}; };
class TestWidget : public QDialog
{
Q_OBJECT
};
class TestWindow : public QRasterWindow, public TestWindowBase class TestWindow : public QRasterWindow, public TestWindowBase
{ {
Q_OBJECT Q_OBJECT
@ -363,9 +387,8 @@ public:
static WidgetStorage *getInstance() static WidgetStorage *getInstance()
{ {
if (!s_instance) if (!s_instance)
{
s_instance = new WidgetStorage(); s_instance = new WidgetStorage();
}
return s_instance; return s_instance;
} }
static void clearInstance() static void clearInstance()
@ -382,10 +405,10 @@ public:
return nullptr; return nullptr;
} }
QLineEdit *findEdit(const std::string &name) TestSpinBox *findSpinBox(const std::string &name)
{ {
auto it = m_lineEdits.find(name); auto it = m_spinBoxes.find(name);
if (it != m_lineEdits.end()) if (it != m_spinBoxes.end())
return it->second; return it->second;
return nullptr; return nullptr;
} }
@ -393,34 +416,37 @@ public:
void make(const std::string &name) void make(const std::string &name)
{ {
auto widget = std::make_shared<TestWidget>(); auto widget = std::make_shared<TestWidget>();
widget->setWindowTitle("Dialog"); widget->setWindowTitle("Dialog");
auto *lineEdit = new QLineEdit(widget.get()); auto *spinBox = new TestSpinBox(widget.get());
widget->setMinimumSize(200, 200); widget->setMinimumSize(200, 200);
widget->setMaximumSize(200, 200); widget->setMaximumSize(200, 200);
widget->setGeometry(0, m_widgetY, 200, 200); widget->setGeometry(0, m_widgetY, 200, 200);
m_widgetY += 200; m_widgetY += 200;
lineEdit->setText("Hello world");
m_widgets[name] = widget; m_widgets[name] = widget;
m_lineEdits[name] = lineEdit; m_spinBoxes[name] = spinBox;
}
void showContextMenu(const std::string &name)
{
TestSpinBox *spinBox = findSpinBox(name);
if (spinBox)
spinBox->ShowContextMenu();
} }
void makeNative(const std::string &name) void makeNative(const std::string &name)
{ {
auto widget = std::make_shared<TestWidget>(); auto widget = std::make_shared<TestWidget>();
widget->setWindowTitle("Dialog"); widget->setWindowTitle("Dialog");
auto *lineEdit = new QLineEdit(); auto *spinBox = new TestSpinBox(widget.get());
widget->setMinimumSize(200, 200); widget->setMinimumSize(200, 200);
widget->setMaximumSize(200, 200); widget->setMaximumSize(200, 200);
widget->setGeometry(0, m_widgetY, 200, 200); widget->setGeometry(0, m_widgetY, 200, 200);
m_widgetY += 200; m_widgetY += 200;
lineEdit->setText("Hello world");
m_widgets[name] = widget; m_widgets[name] = widget;
m_lineEdits[name] = lineEdit; m_spinBoxes[name] = spinBox;
QFileSystemModel *model = new QFileSystemModel; QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath()); model->setRootPath(QDir::currentPath());
@ -430,22 +456,22 @@ public:
auto *treeView = new QTreeView(scrollArea); auto *treeView = new QTreeView(scrollArea);
treeView->setModel(model); treeView->setModel(model);
layout->addWidget(lineEdit); layout->addWidget(spinBox);
layout->addWidget(scrollArea); layout->addWidget(scrollArea);
treeView->setAttribute(Qt::WA_NativeWindow); treeView->setAttribute(Qt::WA_NativeWindow);
scrollArea->setAttribute(Qt::WA_NativeWindow); scrollArea->setAttribute(Qt::WA_NativeWindow);
lineEdit->setAttribute(Qt::WA_NativeWindow); spinBox->setAttribute(Qt::WA_NativeWindow);
widget->setAttribute(Qt::WA_NativeWindow); widget->setAttribute(Qt::WA_NativeWindow);
} }
private: private:
using TestWidgetPtr = std::shared_ptr<TestWidget>; using TestWidgetPtr = std::shared_ptr<TestWidget>;
static WidgetStorage * s_instance; static WidgetStorage *s_instance;
std::map<std::string, TestWidgetPtr> m_widgets; std::map<std::string, TestWidgetPtr> m_widgets;
std::map<std::string, QLineEdit *> m_lineEdits; std::map<std::string, TestSpinBox *> m_spinBoxes;
int m_widgetY = 0; int m_widgetY = 0;
}; };
WidgetStorage *WidgetStorage::s_instance = nullptr; WidgetStorage *WidgetStorage::s_instance = nullptr;
@ -540,11 +566,17 @@ void createWidget(const std::string &name)
WidgetStorage::getInstance()->make(name); WidgetStorage::getInstance()->make(name);
} }
void createNativeWidget(const std::string &name) void createNativeWidget(const std::string &name)
{ {
WidgetStorage::getInstance()->makeNative(name); WidgetStorage::getInstance()->makeNative(name);
} }
void showContextMenuWidget(const std::string &name)
{
WidgetStorage::getInstance()->showContextMenu(name);
}
void setWidgetNoFocusShow(const std::string &name) void setWidgetNoFocusShow(const std::string &name)
{ {
auto w = WidgetStorage::getInstance()->findWidget(name); auto w = WidgetStorage::getInstance()->findWidget(name);
@ -562,9 +594,9 @@ void showWidget(const std::string &name)
void hasWidgetFocus(const std::string &name) void hasWidgetFocus(const std::string &name)
{ {
bool focus = false; bool focus = false;
auto le = WidgetStorage::getInstance()->findEdit(name); auto spinBox = WidgetStorage::getInstance()->findSpinBox(name);
if (le) if (spinBox)
focus = le->hasFocus(); focus = spinBox->hasFocus();
emscripten::val::global("window").call<void>("hasWidgetFocusCallback", emscripten::val::global("window").call<void>("hasWidgetFocusCallback",
emscripten::val(focus)); emscripten::val(focus));
@ -681,12 +713,17 @@ bool closeWindow(const std::string &title)
std::string colorToJs(int r, int g, int b) std::string colorToJs(int r, int g, int b)
{ {
return return "[{"
"[{" " r: "
" r: " + std::to_string(r) + "," + std::to_string(r)
" g: " + std::to_string(g) + "," + ","
" b: " + std::to_string(b) + "" " g: "
"}]"; + std::to_string(g)
+ ","
" b: "
+ std::to_string(b)
+ ""
"}]";
} }
void getOpenGLColorAt_0_0(const std::string &windowTitle) void getOpenGLColorAt_0_0(const std::string &windowTitle)
@ -721,6 +758,7 @@ EMSCRIPTEN_BINDINGS(qwasmwindow)
emscripten::function("createWidget", &createWidget); emscripten::function("createWidget", &createWidget);
emscripten::function("createNativeWidget", &createNativeWidget); emscripten::function("createNativeWidget", &createNativeWidget);
emscripten::function("showContextMenuWidget", &showContextMenuWidget);
emscripten::function("setWidgetNoFocusShow", &setWidgetNoFocusShow); emscripten::function("setWidgetNoFocusShow", &setWidgetNoFocusShow);
emscripten::function("showWidget", &showWidget); emscripten::function("showWidget", &showWidget);
emscripten::function("activateWidget", &activateWidget); emscripten::function("activateWidget", &activateWidget);