Bug 1423598 - [Wayland] Use gdk_window_move_to_rect() to place popup windows, r=ashie
authorMartin Stransky <stransky@redhat.com>
Thu, 25 Apr 2019 11:44:44 +0000
changeset 530110 25176eee4a95239a5bf2c6558fe0ac46cd6df594
parent 530109 1720abb3a1b5872f0629f200268eb01fd15fa1d4
child 530111 24ad41213ce44bef9fe3d7f8af56ad9ca38e941f
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
bugs1423598
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 1423598 - [Wayland] Use gdk_window_move_to_rect() to place popup windows, r=ashie Differential Revision: https://phabricator.services.mozilla.com/D22962
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -152,16 +152,30 @@ const gint kEvents =
     GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
     GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK |
     GDK_BUTTON_RELEASE_MASK |
 #if GTK_CHECK_VERSION(3, 4, 0)
     GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK |
 #endif
     GDK_SCROLL_MASK | GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
 
+#if !GTK_CHECK_VERSION(3, 22, 0)
+typedef enum {
+  GDK_ANCHOR_FLIP_X = 1 << 0,
+  GDK_ANCHOR_FLIP_Y = 1 << 1,
+  GDK_ANCHOR_SLIDE_X = 1 << 2,
+  GDK_ANCHOR_SLIDE_Y = 1 << 3,
+  GDK_ANCHOR_RESIZE_X = 1 << 4,
+  GDK_ANCHOR_RESIZE_Y = 1 << 5,
+  GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
+  GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
+  GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
+} GdkAnchorHints;
+#endif
+
 /* utility functions */
 static bool is_mouse_in_window(GdkWindow *aWindow, gdouble aMouseX,
                                gdouble aMouseY);
 static nsWindow *get_window_for_gtk_widget(GtkWidget *widget);
 static nsWindow *get_window_for_gdk_window(GdkWindow *window);
 static GtkWidget *get_gtk_widget_for_gdk_window(GdkWindow *window);
 static GdkCursor *get_gtk_cursor(nsCursor aCursor);
 
@@ -1109,20 +1123,103 @@ void nsWindow::Move(double aX, double aY
 
   if (!mCreated) return;
 
   NativeMove();
 
   NotifyRollupGeometryChange();
 }
 
+bool nsWindow::IsWaylandPopup() {
+  return !mIsX11Display && mIsTopLevel && mWindowType == eWindowType_popup;
+}
+
+#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));
+}
+#endif
+
+void nsWindow::NativeMoveResizeWaylandPopup(GdkPoint *aPosition,
+                                            GdkRectangle *aSize) {
+  // Available as of GTK 3.24+
+  static auto sGdkWindowMoveToRect = (void (*)(
+      GdkWindow *, const GdkRectangle *, GdkGravity, GdkGravity, GdkAnchorHints,
+      gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
+
+  if (aSize) {
+    gtk_window_resize(GTK_WINDOW(mShell), aSize->width, aSize->height);
+  }
+
+  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));
+  }
+
+  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};
+  if (aSize) {
+    rect.width = aSize->width;
+    rect.height = aSize->height;
+  }
+
+  LOG(("%s [%p] request position %d,%d\n", __FUNCTION__, (void *)this,
+       aPosition->x, aPosition->y));
+  if (aSize) {
+    LOG(("  request size %d,%d\n", aSize->width, aSize->height));
+  }
+  LOG(("  request result %d %d\n", rect.x, rect.y));
+#ifdef DEBUG
+  g_signal_connect(gdkWindow, "moved-to-rect",
+                   G_CALLBACK(NativeMoveResizeWaylandPopupCallback), this);
+#endif
+
+  GdkGravity rectAnchor = GDK_GRAVITY_NORTH_WEST;
+  GdkGravity menuAnchor = GDK_GRAVITY_NORTH_WEST;
+  if (GetTextDirection() == GTK_TEXT_DIR_RTL) {
+    rectAnchor = GDK_GRAVITY_NORTH_EAST;
+    menuAnchor = GDK_GRAVITY_NORTH_EAST;
+  }
+
+  GdkAnchorHints hints = GdkAnchorHints(GDK_ANCHOR_SLIDE | GDK_ANCHOR_FLIP);
+  if (aSize) {
+    hints = GdkAnchorHints(hints | GDK_ANCHOR_RESIZE);
+  }
+
+  sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints, 0, 0);
+}
+
 void nsWindow::NativeMove() {
   GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
 
-  if (mIsTopLevel) {
+  LOG(("nsWindow::NativeMove [%p] %d %d\n", (void *)this, point.x, point.y));
+
+  if (IsWaylandPopup()) {
+    NativeMoveResizeWaylandPopup(&point, nullptr);
+  } else if (mIsTopLevel) {
     gtk_window_move(GTK_WINDOW(mShell), point.x, point.y);
   } else if (mGdkWindow) {
     gdk_window_move(mGdkWindow, point.x, point.y);
   }
 }
 
 void nsWindow::SetZIndex(int32_t aZIndex) {
   nsIWidget *oldPrev = GetPrevSibling();
@@ -3403,21 +3500,16 @@ nsresult nsWindow::Create(nsIWidget *aPa
       if (mWindowType == eWindowType_dialog) {
         SetDefaultIcon();
         gtk_window_set_wmclass(GTK_WINDOW(mShell), "Dialog",
                                gdk_get_program_class());
         gtk_window_set_type_hint(GTK_WINDOW(mShell),
                                  GDK_WINDOW_TYPE_HINT_DIALOG);
         gtk_window_set_transient_for(GTK_WINDOW(mShell), topLevelParent);
       } else if (mWindowType == eWindowType_popup) {
-        // With popup windows, we want to control their position, so don't
-        // wait for the window manager to place them (which wouldn't
-        // happen with override-redirect windows anyway).
-        NativeMove();
-
         gtk_window_set_wmclass(GTK_WINDOW(mShell), "Popup",
                                gdk_get_program_class());
 
         if (aInitData->mNoAutoHide) {
           // ... but the window manager does not decorate this window,
           // nor provide a separate taskbar icon.
           if (mBorderStyle == eBorderStyle_default) {
             gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
@@ -3460,18 +3552,28 @@ nsresult nsWindow::Create(nsIWidget *aPa
             default:
               gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
               break;
           }
         }
         gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
 
         if (topLevelParent) {
+          LOG(("nsWindow::Create [%p] Set popup parent %p\n", (void *)this,
+               topLevelParent));
           gtk_window_set_transient_for(GTK_WINDOW(mShell), topLevelParent);
         }
+
+        // We need realized mShell at NativeMove().
+        gtk_widget_realize(mShell);
+
+        // With popup windows, we want to control their position, so don't
+        // wait for the window manager to place them (which wouldn't
+        // happen with override-redirect windows anyway).
+        NativeMove();
       } else {  // must be eWindowType_toplevel
         SetDefaultIcon();
         gtk_window_set_wmclass(GTK_WINDOW(mShell), "Toplevel",
                                gdk_get_program_class());
 
         // each toplevel window gets its own window group
         GtkWindowGroup *group = gtk_window_group_new();
         gtk_window_group_add_window(group, GTK_WINDOW(mShell));
@@ -3905,33 +4007,37 @@ void nsWindow::NativeMoveResize() {
   }
 
   GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
   GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
 
   LOG(("nsWindow::NativeMoveResize [%p] %d %d %d %d\n", (void *)this, topLeft.x,
        topLeft.y, size.width, size.height));
 
-  if (mIsTopLevel) {
-    // x and y give the position of the window manager frame top-left.
-    gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
-    // This sets the client window size.
-    MOZ_ASSERT(size.width > 0 && size.height > 0,
-               "Can't resize window smaller than 1x1.");
-    gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
-  } else if (mContainer) {
-    GtkAllocation allocation;
-    allocation.x = topLeft.x;
-    allocation.y = topLeft.y;
-    allocation.width = size.width;
-    allocation.height = size.height;
-    gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation);
-  } else if (mGdkWindow) {
-    gdk_window_move_resize(mGdkWindow, topLeft.x, topLeft.y, size.width,
-                           size.height);
+  if (IsWaylandPopup()) {
+    NativeMoveResizeWaylandPopup(&topLeft, &size);
+  } else {
+    if (mIsTopLevel) {
+      // x and y give the position of the window manager frame top-left.
+      gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+      // This sets the client window size.
+      MOZ_ASSERT(size.width > 0 && size.height > 0,
+                 "Can't resize window smaller than 1x1.");
+      gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+    } else if (mContainer) {
+      GtkAllocation allocation;
+      allocation.x = topLeft.x;
+      allocation.y = topLeft.y;
+      allocation.width = size.width;
+      allocation.height = size.height;
+      gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation);
+    } else if (mGdkWindow) {
+      gdk_window_move_resize(mGdkWindow, topLeft.x, topLeft.y, size.width,
+                             size.height);
+    }
   }
 
 #ifdef MOZ_X11
   // Notify the GtkCompositorWidget of a ClientSizeChange
   // This is different than OnSizeAllocate to catch initial sizing
   if (mCompositorWidgetDelegate) {
     mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
   }
@@ -3949,19 +4055,21 @@ void nsWindow::NativeShow(bool aAction) 
     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 (!mIsX11Display && mWindowType == eWindowType_popup) {
+      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);
         }
       }
       gtk_widget_show(mShell);
     } else if (mContainer) {
       gtk_widget_show(GTK_WIDGET(mContainer));
     } else if (mGdkWindow) {
       gdk_window_show_unraised(mGdkWindow);
@@ -5439,19 +5547,19 @@ static gboolean key_press_event_cb(GtkWi
   // Keyboard repeat can cause key press events to queue up when there are
   // slow event handlers (bug 301029).  Throttle these events by removing
   // consecutive pending duplicate KeyPress events to the same window.
   // We use the event time of the last one.
   // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
   // are generated only when the key is physically released.
 #  define NS_GDKEVENT_MATCH_MASK 0x1FFF  // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
   // Our headers undefine X11 KeyPress - let's redefine it here.
-#ifndef KeyPress
-  #define KeyPress 2
-#endif
+#  ifndef KeyPress
+#    define KeyPress 2
+#  endif
   GdkDisplay *gdkDisplay = gtk_widget_get_display(widget);
   if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
     Display *dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
     while (XPending(dpy)) {
       XEvent next_event;
       XPeekEvent(dpy, &next_event);
       GdkWindow *nextGdkWindow =
           gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
@@ -6814,23 +6922,37 @@ void nsWindow::ForceTitlebarRedraw(void)
 
   frame = FindTitlebarFrame(frame);
   if (frame) {
     nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(),
                                     RestyleHint{0}, nsChangeHint_RepaintFrame);
   }
 }
 
-GtkWindow* nsWindow::GetPopupParentWindow()
-{
-  nsView* view = nsView::GetViewFor(this);
+GtkWindow *nsWindow::GetPopupParentWindow() {
+  nsView *view = nsView::GetViewFor(this);
   if (!view) {
     return nullptr;
   }
-  nsIFrame* frame = view->GetFrame();
+  nsIFrame *frame = view->GetFrame();
   if (!frame) {
     return nullptr;
   }
-  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(frame);
-  nsWindow* window =
-    static_cast<nsWindow*>(menuPopupFrame->GetParentMenuWidget());
+  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();
+  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
@@ -455,16 +455,20 @@ class nsWindow final : public nsBaseWidg
   void ClearCachedResources();
   nsIWidgetListener* GetListener();
 
   void UpdateClientOffsetForCSDWindow();
 
   nsWindow* GetTransientForWindowIfPopup();
   bool IsHandlingTouchSequence(GdkEventSequence* aSequence);
 
+  void NativeMoveResizeWaylandPopup(GdkPoint* aPosition, GdkRectangle* aSize);
+
+  GtkTextDirection GetTextDirection();
+
 #ifdef MOZ_X11
   typedef enum {GTK_WIDGET_COMPOSIDED_DEFAULT = 0,
                 GTK_WIDGET_COMPOSIDED_DISABLED = 1,
                 GTK_WIDGET_COMPOSIDED_ENABLED = 2} WindowComposeRequest;
 
   void SetCompositorHint(WindowComposeRequest aState);
 #endif
   nsCString mGtkWindowTypeName;
@@ -603,16 +607,18 @@ class nsWindow final : public nsBaseWidg
 
   virtual int32_t RoundsWidgetCoordinatesTo() override;
 
   void ForceTitlebarRedraw();
 
   // This is used by Wayland backend to keep strict popup window hierarchy.
   GtkWindow* GetPopupParentWindow();
 
+  bool IsWaylandPopup();
+
   /**
    * |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
    * the widget is destroyed, it's released.  All child windows refer its