From c3a1fe7bd754abc1fc1370e574567aa79b237e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Mon, 13 Nov 2023 11:08:31 +0100 Subject: [PATCH] Prevent reparenting of foreign window embedding container A foreign window used to embed a Qt window into it should not end up with changes to its own parent, as its only job is to give the embedded Qt window a parent handle. Pick-to: 6.6 Change-Id: If1bc89658fedf449d266bc0cc750c90b6a841a68 Reviewed-by: Richard Moe Gustavsen Reviewed-by: Oliver Wolff --- src/plugins/platforms/cocoa/qcocoawindow.mm | 5 +- src/plugins/platforms/ios/qioswindow.mm | 7 ++- .../gui/kernel/qwindow/tst_foreignwindow.cpp | 45 ++++++++++++++ tests/shared/nativewindow.h | 60 ++++++++++++++++--- 4 files changed, 108 insertions(+), 9 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 71d2cd8b2a..05b9f19fec 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -150,7 +150,10 @@ QCocoaWindow::~QCocoaWindow() QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; [m_nsWindow setContentView:nil]; - if ([m_view superview]) + + // Remove from superview only if we have a Qt window parent, + // as we don't want to affect window container foreign windows. + if (QPlatformWindow::parent()) [m_view removeFromSuperview]; // Make sure to disconnect observer in all case if view is valid diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm index 84a3f55f55..9f12122432 100644 --- a/src/plugins/platforms/ios/qioswindow.mm +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -91,7 +91,12 @@ QIOSWindow::~QIOSWindow() clearAccessibleCache(); quiview_cast(m_view).platformWindow = 0; - [m_view removeFromSuperview]; + + // Remove from superview only if we have a Qt window parent, + // as we don't want to affect window container foreign windows. + if (QPlatformWindow::parent()) + [m_view removeFromSuperview]; + [m_view release]; } diff --git a/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp index a43939a21b..ac0f78be2d 100644 --- a/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp @@ -25,6 +25,7 @@ private slots: void initialState(); void embedForeignWindow(); + void embedInForeignWindow(); }; void tst_ForeignWindow::fromWinId() @@ -94,5 +95,49 @@ void tst_ForeignWindow::embedForeignWindow() QTRY_COMPARE(nativeWindow.parentWinId(), originalParentWinId); } +void tst_ForeignWindow::embedInForeignWindow() +{ + // When a foreign window is used as a container to embed a Qt UI + // in a foreign window hierarchy, the foreign window merely acts + // as a parent, and should not be modified. + + { + // At a minimum, we must be able to reparent into the window + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr foreignWindow(QWindow::fromWinId(nativeWindow)); + + QWindow embeddedWindow; + embeddedWindow.setParent(foreignWindow.get()); + QTRY_VERIFY(nativeWindow.isParentOf(embeddedWindow.winId())); + } + + { + // The foreign window's native window should not be reparent as a + // result of creating the foreign window, adding and removing children, + // or destroying the foreign window. + + NativeWindow topLevelNativeWindow; + NativeWindow childNativeWindow; + childNativeWindow.setParent(topLevelNativeWindow); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + std::unique_ptr foreignWindow(QWindow::fromWinId(childNativeWindow)); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + QWindow embeddedWindow; + embeddedWindow.setParent(foreignWindow.get()); + QTRY_VERIFY(childNativeWindow.isParentOf(embeddedWindow.winId())); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + embeddedWindow.setParent(nullptr); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + foreignWindow.reset(); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + } +} + #include QTEST_MAIN(tst_ForeignWindow) diff --git a/tests/shared/nativewindow.h b/tests/shared/nativewindow.h index 087b7da0fc..8b5ad97836 100644 --- a/tests/shared/nativewindow.h +++ b/tests/shared/nativewindow.h @@ -20,23 +20,29 @@ class NativeWindow { Q_DISABLE_COPY(NativeWindow) public: +#if defined(Q_OS_MACOS) + using Handle = NSView*; +#elif defined(Q_OS_IOS) + using Handle = UIView*; +#elif defined(Q_OS_WIN) + using Handle = HWND; +#elif QT_CONFIG(xcb) + using Handle = xcb_window_t; +#endif + NativeWindow(); ~NativeWindow(); operator WId() const; WId parentWinId() const; + bool isParentOf(WId childWinId); + void setParent(WId parent); void setGeometry(const QRect &rect); QRect geometry() const; private: -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) - VIEW_BASE *m_handle = nullptr; -#elif defined(Q_OS_WIN) - HWND m_handle = nullptr; -#elif QT_CONFIG(xcb) - xcb_window_t m_handle = 0; -#endif + Handle m_handle = {}; }; #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) @@ -92,6 +98,20 @@ WId NativeWindow::parentWinId() const return WId(m_handle.superview); } +bool NativeWindow::isParentOf(WId childWinId) +{ + auto *subview = reinterpret_cast(childWinId); + return subview.superview == m_handle; +} + +void NativeWindow::setParent(WId parent) +{ + if (auto *superview = reinterpret_cast(parent)) + [superview addSubview:m_handle]; + else + [m_handle removeFromSuperview]; +} + #elif defined(Q_OS_WIN) NativeWindow::NativeWindow() @@ -141,6 +161,16 @@ WId NativeWindow::parentWinId() const return WId(GetAncestor(m_handle, GA_PARENT)); } +bool NativeWindow::isParentOf(WId childWinId) +{ + return GetAncestor(Handle(childWinId), GA_PARENT) == m_handle; +} + +void NativeWindow::setParent(WId parent) +{ + SetParent(m_handle, Handle(parent)); +} + #elif QT_CONFIG(xcb) struct Connection @@ -203,6 +233,22 @@ WId NativeWindow::parentWinId() const return tree->parent; } +bool NativeWindow::isParentOf(WId childWinId) +{ + xcb_query_tree_reply_t *tree = xcb_query_tree_reply( + connection, xcb_query_tree(connection, Handle(childWinId)), nullptr); + const auto cleanup = qScopeGuard([&]{ free(tree); }); + return tree->parent == m_handle; +} + +void NativeWindow::setParent(WId parent) +{ + xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data; + + xcb_reparent_window(connection, m_handle, + parent ? Handle(parent) : screen->root, 0, 0); +} + #endif #endif // NATIVEWINDOW_H