Bug 1550051 - [Wayland] Don't track invisible popup windows, r=jhorak

Recently we track and position all popups on Wayland which leads to
wrong placement when parent popup window is hidden.

In this patch we use plain gtk_window_move() for hidden popups and
we don't track them so they can't be used as a parent window.

Also implement nsWindow::HideWaylandPopupAndAllChildren() to clearly
hide a popup window with and its children windows.

Differential Revision: https://phabricator.services.mozilla.com/D31056

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Martin Stransky 2019-05-28 11:12:03 +00:00
Родитель 4ddb74ac0b
Коммит daf67c12bc
2 изменённых файлов: 77 добавлений и 42 удалений

Просмотреть файл

@ -326,7 +326,7 @@ static nsWindow* gFocusWindow = nullptr;
static bool gBlockActivateEvent = false;
static bool gGlobalsInitialized = false;
static bool gRaiseWindows = true;
static GList* gCurrentPopupWindows = nullptr;
static GList* gVisibleWaylandPopupWindows = nullptr;
#if GTK_CHECK_VERSION(3, 4, 0)
static uint32_t gLastTouchID = 0;
@ -1134,17 +1134,31 @@ bool nsWindow::IsWaylandPopup() {
return !mIsX11Display && mIsTopLevel && mWindowType == eWindowType_popup;
}
void nsWindow::CloseWaylandTooltips() {
GList* popup = gCurrentPopupWindows;
GList* next;
while (popup) {
// CloseWaylandWindow() manipulates the gCurrentPopupWindows list.
next = popup->next;
nsWindow* window = static_cast<nsWindow*>(popup->data);
if (window->mPopupType == ePopupTypeTooltip) {
window->CloseWaylandWindow();
}
popup = next;
void nsWindow::HideWaylandTooltips() {
while (gVisibleWaylandPopupWindows) {
nsWindow* window =
static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
if (window->mPopupType != ePopupTypeTooltip) break;
window->HideWaylandWindow();
gVisibleWaylandPopupWindows = g_list_delete_link(
gVisibleWaylandPopupWindows, gVisibleWaylandPopupWindows);
}
}
void nsWindow::HideWaylandPopupAndAllChildren() {
if (g_list_find(gVisibleWaylandPopupWindows, this) == nullptr) {
NS_WARNING("Popup window isn't in wayland popup list!");
return;
}
while (gVisibleWaylandPopupWindows) {
nsWindow* window =
static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
bool quit = gVisibleWaylandPopupWindows->data == this;
window->HideWaylandWindow();
gVisibleWaylandPopupWindows = g_list_delete_link(
gVisibleWaylandPopupWindows, gVisibleWaylandPopupWindows);
if (quit) break;
}
}
@ -1154,47 +1168,63 @@ void nsWindow::CloseWaylandTooltips() {
// popup needs to have an unique parent.
GtkWidget* nsWindow::ConfigureWaylandPopupWindows() {
// Check if we're already configured.
if (gCurrentPopupWindows && g_list_find(gCurrentPopupWindows, this)) {
if (gVisibleWaylandPopupWindows &&
g_list_find(gVisibleWaylandPopupWindows, this)) {
return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell)));
}
// If we're opening a new window we don't want to attach it to a tooltip
// as it's short lived temporary window.
CloseWaylandTooltips();
HideWaylandTooltips();
GtkWindow* parentWidget = nullptr;
if (gCurrentPopupWindows) {
if (gVisibleWaylandPopupWindows) {
if (mPopupType == ePopupTypeTooltip) {
// Attach tooltip window to the latest popup window
// to have both visible.
nsWindow* window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
nsWindow* window =
static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
parentWidget = GTK_WINDOW(window->GetGtkWidget());
} else {
nsMenuPopupFrame* menuPopupFrame = nullptr;
nsIFrame* frame = GetFrame();
if (!frame) {
// We're not fully created yet - just return our default parent.
if (frame) {
menuPopupFrame = do_QueryFrame(frame);
}
// The popup is not fully created yet (we're called from
// nsWindow::Create()) or we're toplevel popup without parent.
// In both cases just use parent which was passed to nsWindow::Create().
if (!menuPopupFrame) {
return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell)));
}
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(frame);
nsWindow* window =
static_cast<nsWindow*>(menuPopupFrame->GetParentMenuWidget());
if (!window) {
nsWindow* parentWindow =
static_cast<nsWindow*>(menuPopupFrame->GetParentMenuWidget());
if (!parentWindow) {
// We're toplevel popup menu attached to another menu. Just use our
// latest popup as a parent.
window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
parentWidget = GTK_WINDOW(window->GetGtkWidget());
parentWindow =
static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
parentWidget = GTK_WINDOW(parentWindow->GetGtkWidget());
} else {
// We're a regular menu in the same frame hierarchy.
// Close child popups which are on a different level.
parentWidget = GTK_WINDOW(window->GetGtkWidget());
do {
nsWindow* window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
// Close child popups on the same level as we can't have two popups
// with one parent on Wayland.
parentWidget = GTK_WINDOW(parentWindow->GetGtkWidget());
nsWindow* lastChildOnTheSameLevel = nullptr;
for (GList* popup = gVisibleWaylandPopupWindows; popup;
popup = popup->next) {
nsWindow* window =
static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
if (GTK_WINDOW(window->GetGtkWidget()) == parentWidget) {
break;
} else {
lastChildOnTheSameLevel = window;
}
window->CloseWaylandWindow();
} while (gCurrentPopupWindows != nullptr);
}
if (lastChildOnTheSameLevel) {
lastChildOnTheSameLevel->HideWaylandPopupAndAllChildren();
}
}
}
}
@ -1204,7 +1234,8 @@ GtkWidget* nsWindow::ConfigureWaylandPopupWindows() {
} else {
parentWidget = gtk_window_get_transient_for(GTK_WINDOW(mShell));
}
gCurrentPopupWindows = g_list_prepend(gCurrentPopupWindows, this);
gVisibleWaylandPopupWindows =
g_list_prepend(gVisibleWaylandPopupWindows, this);
return GTK_WIDGET(parentWidget);
}
@ -1230,9 +1261,12 @@ void nsWindow::NativeMoveResizeWaylandPopup(GdkPoint* aPosition,
}
GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
// gdk_window_move_to_rect() is not available, we don't have a valid GdkWindow
// (we're not realized yet) - try plain gtk_window_move() at least.
if (!sGdkWindowMoveToRect || !gdkWindow) {
// Use standard gtk_window_move() instead of gdk_window_move_to_rect() when:
// - gdk_window_move_to_rect() is not available
// - the widget doesn't have a valid GdkWindow
// - the widget hasn't beed positioned yet
if (!sGdkWindowMoveToRect || !gdkWindow || !AreBoundsSane()) {
gtk_window_move(GTK_WINDOW(mShell), aPosition->x, aPosition->y);
return;
}
@ -4119,7 +4153,7 @@ void nsWindow::NativeMoveResize() {
}
}
void nsWindow::CloseWaylandWindow() {
void nsWindow::HideWaylandWindow() {
#ifdef MOZ_WAYLAND
if (mContainer && moz_container_has_wl_egl_window(mContainer)) {
// Because wl_egl_window is destroyed on moz_container_unmap(),
@ -4129,10 +4163,6 @@ void nsWindow::CloseWaylandWindow() {
DestroyLayerManager();
}
#endif
if (mWindowType == eWindowType_popup) {
gCurrentPopupWindows = g_list_remove(gCurrentPopupWindows, this);
}
gtk_widget_hide(mShell);
}
@ -4158,7 +4188,11 @@ void nsWindow::NativeShow(bool aAction) {
}
} else {
if (!mIsX11Display) {
CloseWaylandWindow();
if (IsWaylandPopup()) {
HideWaylandPopupAndAllChildren();
} else {
HideWaylandWindow();
}
} else if (mIsTopLevel) {
// Workaround window freezes on GTK versions before 3.21.2 by
// ensuring that configure events get dispatched to windows before

Просмотреть файл

@ -612,8 +612,9 @@ class nsWindow final : public nsBaseWidget {
bool IsWaylandPopup();
GtkWidget* ConfigureWaylandPopupWindows();
void CloseWaylandTooltips();
void CloseWaylandWindow();
void HideWaylandWindow();
void HideWaylandTooltips();
void HideWaylandPopupAndAllChildren();
/**
* |mIMContext| takes all IME related stuff.