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
#include <qplatformdefs.h>
#ifdef Q_OS_WASM
# include <private/qstdweb_p.h>
#endif
#include <private/qabstractspinbox_p.h>
#include <private/qapplication_p.h>
#if QT_CONFIG(datetimeparser)
@ -1281,6 +1284,12 @@ void QAbstractSpinBox::timerEvent(QTimerEvent *event)
#if QT_CONFIG(contextmenu)
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);
QPointer<QMenu> menu = d->edit->createStandardContextMenu();

View File

@ -81,6 +81,35 @@ class WidgetTestCase(unittest.TestCase):
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):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=600)
@ -686,6 +715,12 @@ class Widget:
information = call_instance_function(self.driver, 'windowInformation')
return next(filter(lambda e: e['title'] == "Dialog", information))
def showContextMenu(self):
self.driver.execute_script(
f'''
instance.showContextMenuWidget('{self.name}');
'''
)
class Window:
def __init__(self, parent=None, rect=None, title=None, element=None, visible=True, opengl=0):

View File

@ -20,6 +20,7 @@
#include <QFileSystemModel>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QOpenGLWindow>
#include <QOpenGLFunctions>
@ -33,6 +34,34 @@
#include <sstream>
#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
{
public:
@ -45,11 +74,6 @@ public:
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
{
Q_OBJECT
@ -363,9 +387,8 @@ public:
static WidgetStorage *getInstance()
{
if (!s_instance)
{
s_instance = new WidgetStorage();
}
return s_instance;
}
static void clearInstance()
@ -382,10 +405,10 @@ public:
return nullptr;
}
QLineEdit *findEdit(const std::string &name)
TestSpinBox *findSpinBox(const std::string &name)
{
auto it = m_lineEdits.find(name);
if (it != m_lineEdits.end())
auto it = m_spinBoxes.find(name);
if (it != m_spinBoxes.end())
return it->second;
return nullptr;
}
@ -393,34 +416,37 @@ public:
void make(const std::string &name)
{
auto widget = std::make_shared<TestWidget>();
widget->setWindowTitle("Dialog");
auto *lineEdit = new QLineEdit(widget.get());
auto *spinBox = new TestSpinBox(widget.get());
widget->setMinimumSize(200, 200);
widget->setMaximumSize(200, 200);
widget->setGeometry(0, m_widgetY, 200, 200);
m_widgetY += 200;
lineEdit->setText("Hello world");
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)
{
auto widget = std::make_shared<TestWidget>();
widget->setWindowTitle("Dialog");
auto *lineEdit = new QLineEdit();
auto *spinBox = new TestSpinBox(widget.get());
widget->setMinimumSize(200, 200);
widget->setMaximumSize(200, 200);
widget->setGeometry(0, m_widgetY, 200, 200);
m_widgetY += 200;
lineEdit->setText("Hello world");
m_widgets[name] = widget;
m_lineEdits[name] = lineEdit;
m_spinBoxes[name] = spinBox;
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());
@ -430,22 +456,22 @@ public:
auto *treeView = new QTreeView(scrollArea);
treeView->setModel(model);
layout->addWidget(lineEdit);
layout->addWidget(spinBox);
layout->addWidget(scrollArea);
treeView->setAttribute(Qt::WA_NativeWindow);
scrollArea->setAttribute(Qt::WA_NativeWindow);
lineEdit->setAttribute(Qt::WA_NativeWindow);
spinBox->setAttribute(Qt::WA_NativeWindow);
widget->setAttribute(Qt::WA_NativeWindow);
}
private:
using TestWidgetPtr = std::shared_ptr<TestWidget>;
static WidgetStorage * s_instance;
std::map<std::string, TestWidgetPtr> m_widgets;
std::map<std::string, QLineEdit *> m_lineEdits;
int m_widgetY = 0;
static WidgetStorage *s_instance;
std::map<std::string, TestWidgetPtr> m_widgets;
std::map<std::string, TestSpinBox *> m_spinBoxes;
int m_widgetY = 0;
};
WidgetStorage *WidgetStorage::s_instance = nullptr;
@ -540,11 +566,17 @@ void createWidget(const std::string &name)
WidgetStorage::getInstance()->make(name);
}
void createNativeWidget(const std::string &name)
{
WidgetStorage::getInstance()->makeNative(name);
}
void showContextMenuWidget(const std::string &name)
{
WidgetStorage::getInstance()->showContextMenu(name);
}
void setWidgetNoFocusShow(const std::string &name)
{
auto w = WidgetStorage::getInstance()->findWidget(name);
@ -562,9 +594,9 @@ void showWidget(const std::string &name)
void hasWidgetFocus(const std::string &name)
{
bool focus = false;
auto le = WidgetStorage::getInstance()->findEdit(name);
if (le)
focus = le->hasFocus();
auto spinBox = WidgetStorage::getInstance()->findSpinBox(name);
if (spinBox)
focus = spinBox->hasFocus();
emscripten::val::global("window").call<void>("hasWidgetFocusCallback",
emscripten::val(focus));
@ -681,12 +713,17 @@ bool closeWindow(const std::string &title)
std::string colorToJs(int r, int g, int b)
{
return
"[{"
" r: " + std::to_string(r) + ","
" g: " + std::to_string(g) + ","
" b: " + std::to_string(b) + ""
"}]";
return "[{"
" r: "
+ std::to_string(r)
+ ","
" g: "
+ std::to_string(g)
+ ","
" b: "
+ std::to_string(b)
+ ""
"}]";
}
void getOpenGLColorAt_0_0(const std::string &windowTitle)
@ -721,6 +758,7 @@ EMSCRIPTEN_BINDINGS(qwasmwindow)
emscripten::function("createWidget", &createWidget);
emscripten::function("createNativeWidget", &createNativeWidget);
emscripten::function("showContextMenuWidget", &showContextMenuWidget);
emscripten::function("setWidgetNoFocusShow", &setWidgetNoFocusShow);
emscripten::function("showWidget", &showWidget);
emscripten::function("activateWidget", &activateWidget);