Bug 1539471 - [wayland] Track active popup windows on Wayland and use dynamic popup hierarchy, r=ashie
authorMartin Stransky <stransky@redhat.com>
Tue, 07 May 2019 11:28:04 +0000
changeset 531691 09970391563cc7f319b26c9634d0c4addbb80136
parent 531690 4a3a0cf2e77aeedd4542f8f05925304d926949a6
child 531692 882bba44f789d3189564d44134a452daf2aad3ed
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersashie
bugs1539471
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1539471 - [wayland] Track active popup windows on Wayland and use dynamic popup hierarchy, r=ashie In order to show all popups on Wayland we need to set popup parent runtime for popups which don't have fixed parent. For instance popup menus (fired after right button mouse click) can be issued on top of another popup and we need to follow that connection on Wayland. We track all open (active) popups to: - close all visible tooltip windows when we're going to open another tooltip - close concurrent popup on the same level when a new one is about to open - get latest active popup as a parent for a new tooltip windows - get latest active popup as a parent for a new popup menu without fixed parent Differential Revision: https://phabricator.services.mozilla.com/D29348
layout/xul/nsMenuPopupFrame.cpp
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -2497,14 +2497,14 @@ void nsMenuPopupFrame::CheckForAnchorCha
     SetPopupPosition(nullptr, true, false, true);
   }
 }
 
 nsIWidget* nsMenuPopupFrame::GetParentMenuWidget() {
   nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
   if (menuFrame) {
     nsMenuParent* parentPopup = menuFrame->GetMenuParent();
-    if (parentPopup && parentPopup->IsMenu()) {
+    if (parentPopup && (parentPopup->IsMenu() || parentPopup->IsMenuBar())) {
       return static_cast<nsMenuPopupFrame*>(parentPopup)->GetWidget();
     }
   }
   return nullptr;
 }
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -320,16 +320,17 @@ class CurrentX11TimeGetter {
 
 static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
 
 // The window from which the focus manager asks us to dispatch key events.
 static nsWindow* gFocusWindow = nullptr;
 static bool gBlockActivateEvent = false;
 static bool gGlobalsInitialized = false;
 static bool gRaiseWindows = true;
+static GList* gCurrentPopupWindows = nullptr;
 
 #if GTK_CHECK_VERSION(3, 4, 0)
 static uint32_t gLastTouchID = 0;
 #endif
 
 #define NS_WINDOW_TITLE_MAX_LENGTH 4095
 
 // If after selecting profile window, the startup fail, please refer to
@@ -1127,16 +1128,90 @@ void nsWindow::Move(double aX, double aY
 
   NotifyRollupGeometryChange();
 }
 
 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;
+  }
+}
+
+// Wayland keeps strong popup window hierarchy. We need to track active
+// (visible) popup windows and make sure we hide popup on the same level
+// before we open another one on that level. It means that every open
+// popup needs to have an unique parent.
+GtkWidget* nsWindow::ConfigureWaylandPopupWindows() {
+  // Check if we're already configured.
+  if (gCurrentPopupWindows && g_list_find(gCurrentPopupWindows, 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();
+
+  GtkWindow* parentWidget = nullptr;
+  if (gCurrentPopupWindows) {
+    if (mPopupType == ePopupTypeTooltip) {
+      // Attach tooltip window to the latest popup window
+      // to have both visible.
+      nsWindow* window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
+      parentWidget = GTK_WINDOW(window->GetGtkWidget());
+    } else {
+      nsIFrame* frame = GetFrame();
+      if (!frame) {
+        // We're not fully created yet - just return our default parent.
+        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) {
+        // 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());
+      } 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);
+          if (GTK_WINDOW(window->GetGtkWidget()) == parentWidget) {
+            break;
+          }
+          window->CloseWaylandWindow();
+        } while (gCurrentPopupWindows != nullptr);
+      }
+    }
+  }
+
+  if (parentWidget) {
+    gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWidget);
+  } else {
+    parentWidget = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+  }
+  gCurrentPopupWindows = g_list_prepend(gCurrentPopupWindows, this);
+  return GTK_WIDGET(parentWidget);
+}
+
 #ifdef DEBUG
 static void NativeMoveResizeWaylandPopupCallback(
     GdkWindow* window, const GdkRectangle* flipped_rect,
     const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y,
     void* unused) {
   LOG(("%s flipped %d %d\n", __FUNCTION__, flipped_rect->x, flipped_rect->y));
   LOG(("%s final %d %d\n", __FUNCTION__, final_rect->x, final_rect->y));
 }
@@ -1156,23 +1231,17 @@ void nsWindow::NativeMoveResizeWaylandPo
   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) {
     gtk_window_move(GTK_WINDOW(mShell), aPosition->x, aPosition->y);
     return;
   }
 
-  GtkWindow* parentWindow = GetPopupParentWindow();
-  if (parentWindow) {
-    gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWindow);
-  } else {
-    parentWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
-  }
-
+  GtkWidget* parentWindow = ConfigureWaylandPopupWindows();
   LOG(("nsWindow::NativeMoveResizeWaylandPopup [%p] Set popup parent %p\n",
        (void*)this, parentWindow));
 
   int x_parent, y_parent;
   gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parentWindow)),
                         &x_parent, &y_parent);
 
   GdkRectangle rect = {aPosition->x - x_parent, aPosition->y - y_parent, 1, 1};
@@ -4044,53 +4113,57 @@ void nsWindow::NativeMoveResize() {
 #endif
 
   // Does it need to be shown because bounds were previously insane?
   if (mNeedsShow && mIsShown) {
     NativeShow(true);
   }
 }
 
+void nsWindow::CloseWaylandWindow() {
+#ifdef MOZ_WAYLAND
+  if (mContainer && moz_container_has_wl_egl_window(mContainer)) {
+    // Because wl_egl_window is destroyed on moz_container_unmap(),
+    // the current compositor cannot use it anymore. To avoid crash,
+    // destroy the compositor & recreate a new compositor on next
+    // expose event.
+    DestroyLayerManager();
+  }
+#endif
+
+  if (mWindowType == eWindowType_popup) {
+    gCurrentPopupWindows = g_list_remove(gCurrentPopupWindows, this);
+  }
+  gtk_widget_hide(mShell);
+}
+
 void nsWindow::NativeShow(bool aAction) {
   if (aAction) {
     // unset our flag now that our window has been shown
     mNeedsShow = false;
 
     if (mIsTopLevel) {
       // Set up usertime/startupID metadata for the created window.
       if (mWindowType != eWindowType_invisible) {
         SetUserTimeAndStartupIDForActivatedWindow(mShell);
       }
       // Update popup window hierarchy run-time on Wayland.
       if (IsWaylandPopup()) {
-        GtkWindow* parentWindow = GetPopupParentWindow();
-        if (parentWindow) {
-          LOG(("nsWindow::NativeShow [%p] Set popup parent %p\n", (void*)this,
-               parentWindow));
-          gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWindow);
-        }
+        ConfigureWaylandPopupWindows();
       }
       gtk_widget_show(mShell);
     } else if (mContainer) {
       gtk_widget_show(GTK_WIDGET(mContainer));
     } else if (mGdkWindow) {
       gdk_window_show_unraised(mGdkWindow);
     }
   } else {
-#ifdef MOZ_WAYLAND
-    if (mContainer && moz_container_has_wl_egl_window(mContainer)) {
-      // Because wl_egl_window is destroyed on moz_container_unmap(),
-      // the current compositor cannot use it anymore. To avoid crash,
-      // destroy the compositor & recreate a new compositor on next
-      // expose event.
-      DestroyLayerManager();
-    }
-#endif
-
-    if (mIsTopLevel) {
+    if (!mIsX11Display) {
+      CloseWaylandWindow();
+    } else if (mIsTopLevel) {
       // Workaround window freezes on GTK versions before 3.21.2 by
       // ensuring that configure events get dispatched to windows before
       // they are unmapped. See bug 1225044.
       if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
         GtkAllocation allocation;
         gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
 
         GdkEventConfigure event;
@@ -4104,17 +4177,16 @@ void nsWindow::NativeShow(bool aAction) 
         event.height = allocation.height;
 
         auto shellClass = GTK_WIDGET_GET_CLASS(mShell);
         for (unsigned int i = 0; i < mPendingConfigures; i++) {
           Unused << shellClass->configure_event(mShell, &event);
         }
         mPendingConfigures = 0;
       }
-
       gtk_widget_hide(mShell);
 
       ClearTransparencyBitmap();  // Release some resources
     } else if (mContainer) {
       gtk_widget_hide(GTK_WIDGET(mContainer));
     } else if (mGdkWindow) {
       gdk_window_hide(mGdkWindow);
     }
@@ -6898,59 +6970,45 @@ static nsIFrame* FindTitlebarFrame(nsIFr
 
     if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
       return foundFrame;
     }
   }
   return nullptr;
 }
 
+nsIFrame* nsWindow::GetFrame(void) {
+  nsView* view = nsView::GetViewFor(this);
+  if (!view) {
+    return nullptr;
+  }
+  return view->GetFrame();
+}
+
 void nsWindow::ForceTitlebarRedraw(void) {
   MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
 
   if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
     return;
   }
-  nsView* view = nsView::GetViewFor(this);
-  if (!view) {
-    return;
-  }
-  nsIFrame* frame = view->GetFrame();
+
+  nsIFrame* frame = GetFrame();
   if (!frame) {
     return;
   }
 
   frame = FindTitlebarFrame(frame);
   if (frame) {
     nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(),
                                     RestyleHint{0}, nsChangeHint_RepaintFrame);
   }
 }
 
-GtkWindow* nsWindow::GetPopupParentWindow() {
-  nsView* view = nsView::GetViewFor(this);
-  if (!view) {
-    return nullptr;
-  }
-  nsIFrame* frame = view->GetFrame();
-  if (!frame) {
-    return nullptr;
-  }
-  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(frame);
-  nsWindow* window =
-      static_cast<nsWindow*>(menuPopupFrame->GetParentMenuWidget());
-  return window ? GTK_WINDOW(window->GetGtkWidget()) : nullptr;
-}
-
 GtkTextDirection nsWindow::GetTextDirection() {
-  nsView* view = nsView::GetViewFor(this);
-  if (!view) {
-    return GTK_TEXT_DIR_LTR;
-  }
-  nsIFrame* frame = view->GetFrame();
+  nsIFrame* frame = GetFrame();
   if (!frame) {
     return GTK_TEXT_DIR_LTR;
   }
 
   WritingMode wm = frame->GetWritingMode();
   bool isFrameRTL = !(wm.IsVertical() ? wm.IsVerticalLR() : wm.IsBidiLTR());
   return isFrameRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
 }
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -267,16 +267,17 @@ class nsWindow final : public nsBaseWidg
                                                 int32_t aVertical) override;
 
   MozContainer* GetMozContainer() { return mContainer; }
   // GetMozContainerWidget returns the MozContainer even for undestroyed
   // descendant windows
   GtkWidget* GetMozContainerWidget();
   GdkWindow* GetGdkWindow() { return mGdkWindow; }
   GtkWidget* GetGtkWidget() { return mShell; }
+  nsIFrame* GetFrame();
   bool IsDestroyed() { return mIsDestroyed; }
 
   void DispatchDragEvent(mozilla::EventMessage aMsg,
                          const LayoutDeviceIntPoint& aRefPoint, guint aTime);
   static void UpdateDragStatus(GdkDragContext* aDragContext,
                                nsIDragService* aDragService);
 
   WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
@@ -604,20 +605,20 @@ class nsWindow final : public nsBaseWidg
   void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
 
   void CleanLayerManagerRecursive();
 
   virtual int32_t RoundsWidgetCoordinatesTo() override;
 
   void ForceTitlebarRedraw();
 
-  // This is used by Wayland backend to keep strict popup window hierarchy.
-  GtkWindow* GetPopupParentWindow();
-
   bool IsWaylandPopup();
+  GtkWidget* ConfigureWaylandPopupWindows();
+  void CloseWaylandTooltips();
+  void CloseWaylandWindow();
 
   /**
    * |mIMContext| takes all IME related stuff.
    *
    * This is owned by the top-level nsWindow or the topmost child
    * nsWindow embedded in a non-Gecko widget.
    *
    * The instance is created when the top level widget is created.  And when