From 00ce86a90815443562bebd68277bda77972bc65d Mon Sep 17 00:00:00 2001 From: Even Oscar Andersen Date: Fri, 15 Mar 2024 22:28:59 +0100 Subject: [PATCH] wasm: disable spinbox context menu if not ASYNCIFY to avoid deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using exec will lock the application if asyncify is not used Fixes: QTBUG-120925 Change-Id: Ic8b3acc402f3ecbfb07fd8cb80013e02e2421402 Reviewed-by: Morten Johan Sørvig --- src/widgets/widgets/qabstractspinbox.cpp | 9 ++ tests/auto/wasm/selenium/qwasmwindow.py | 35 ++++++ .../wasm/selenium/tst_qwasmwindow_harness.cpp | 104 ++++++++++++------ 3 files changed, 115 insertions(+), 33 deletions(-) diff --git a/src/widgets/widgets/qabstractspinbox.cpp b/src/widgets/widgets/qabstractspinbox.cpp index f377275d231..b1930530422 100644 --- a/src/widgets/widgets/qabstractspinbox.cpp +++ b/src/widgets/widgets/qabstractspinbox.cpp @@ -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 +#ifdef Q_OS_WASM +# include +#endif #include #include #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 menu = d->edit->createStandardContextMenu(); diff --git a/tests/auto/wasm/selenium/qwasmwindow.py b/tests/auto/wasm/selenium/qwasmwindow.py index df2d39f5167..3d70f742034 100644 --- a/tests/auto/wasm/selenium/qwasmwindow.py +++ b/tests/auto/wasm/selenium/qwasmwindow.py @@ -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): diff --git a/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp b/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp index 75e23cecdaf..c6a8e615fcc 100644 --- a/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp +++ b/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,34 @@ #include #include +// 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(); + 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(); 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; - static WidgetStorage * s_instance; - std::map m_widgets; - std::map m_lineEdits; - int m_widgetY = 0; + static WidgetStorage *s_instance; + std::map m_widgets; + std::map 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("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);