Bug 1661516 [Wayland] Don't use context menu shift in nsMenuPopupFrame::SetPopupPosition(), r=jhorak
authorstransky <stransky@redhat.com>
Mon, 14 Jun 2021 18:01:04 +0000 (2021-06-14)
changeset 583046 7b82d177a6b979036f180329be6b029d690d9e0c
parent 583045 9f0fbb1431721c9eae68a3c94ae49a4d33fdb1f8
child 583047 177ac92fb734b80f07c04710ec70f0b89a073351
push id144921
push userstransky@redhat.com
push dateMon, 14 Jun 2021 18:22:00 +0000 (2021-06-14)
treeherderautoland@7b82d177a6b9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhorak
bugs1661516
milestone91.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 1661516 [Wayland] Don't use context menu shift in nsMenuPopupFrame::SetPopupPosition(), r=jhorak - Clear mAdjustOffsetForContextMenu at nsMenuPopupFrame when running on Wayland and use move-to-rect to produce the offset. - Implement nsWindow::WaylandPopupIsContextMenu() - Use mBonuds directly in NativeMoveResizeWaylandPopupCallback() instead of Gtk query. - Add some more loggin and code polishing. Differential Revision: https://phabricator.services.mozilla.com/D117283
layout/xul/nsMenuPopupFrame.cpp
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -69,16 +69,24 @@ using mozilla::dom::Document;
 using mozilla::dom::Element;
 using mozilla::dom::Event;
 using mozilla::dom::KeyboardEvent;
 
 int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1;
 
 DOMTimeStamp nsMenuPopupFrame::sLastKeyTime = 0;
 
+static bool IsWaylandDisplay() {
+#ifdef MOZ_WAYLAND
+  return mozilla::widget::GdkIsWaylandDisplay();
+#else
+  return false;
+#endif
+}
+
 // NS_NewMenuPopupFrame
 //
 // Wrapper for creating a new menu popup container
 //
 nsIFrame* NS_NewMenuPopupFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
   return new (aPresShell)
       nsMenuPopupFrame(aStyle, aPresShell->GetPresContext());
 }
@@ -545,22 +553,17 @@ void nsMenuPopupFrame::LayoutPopup(nsBox
   nsSize minSize = GetXULMinSize(aState);
   nsSize maxSize = GetXULMaxSize(aState);
 
   if (aSizedToPopup) {
     prefSize.width = aParentMenu->GetRect().width;
   }
   prefSize = XULBoundsCheck(minSize, prefSize, maxSize);
 
-#ifdef MOZ_WAYLAND
-  static bool inWayland = mozilla::widget::GdkIsWaylandDisplay();
-#else
-  static bool inWayland = false;
-#endif
-  if (inWayland) {
+  if (IsWaylandDisplay()) {
     // If prefSize it is not a whole number in css pixels we need round it up
     // to avoid reflow of the tooltips/popups and putting the text on two lines
     // (usually happens with 200% scale factor and font scale factor <> 1)
     // because GTK thrown away the decimals.
     int32_t appPerCSS = AppUnitsPerCSSPixel();
     if (prefSize.width % appPerCSS > 0) {
       prefSize.width += appPerCSS;
     }
@@ -900,17 +903,18 @@ void nsMenuPopupFrame::InitializePopupAt
   mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
   mXPos = 0;
   mYPos = 0;
   mFlip = FlipType_Default;
   mPopupAnchor = POPUPALIGNMENT_NONE;
   mPopupAlignment = POPUPALIGNMENT_NONE;
   mPosition = POPUPPOSITION_UNKNOWN;
   mIsContextMenu = aIsContextMenu;
-  mAdjustOffsetForContextMenu = aIsContextMenu;
+  // Wayland does menu adjustments at widget code
+  mAdjustOffsetForContextMenu = IsWaylandDisplay() ? false : aIsContextMenu;
   mIsNativeMenu = false;
   mAnchorType = MenuPopupAnchorType_Point;
   mPositionedOffset = 0;
 }
 
 void nsMenuPopupFrame::InitializePopupAsNativeContextMenu(
     nsIContent* aTriggerContent, int32_t aXPos, int32_t aYPos) {
   mTriggerContent = aTriggerContent;
@@ -919,17 +923,18 @@ void nsMenuPopupFrame::InitializePopupAs
   mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
   mXPos = 0;
   mYPos = 0;
   mFlip = FlipType_Default;
   mPopupAnchor = POPUPALIGNMENT_NONE;
   mPopupAlignment = POPUPALIGNMENT_NONE;
   mPosition = POPUPPOSITION_UNKNOWN;
   mIsContextMenu = true;
-  mAdjustOffsetForContextMenu = true;
+  // Wayland does menu adjustments at widget code
+  mAdjustOffsetForContextMenu = !IsWaylandDisplay();
   mIsNativeMenu = true;
   mAnchorType = MenuPopupAnchorType_Point;
   mPositionedOffset = 0;
 }
 
 void nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
                                              const nsAString& aPosition,
                                              const nsIntRect& aRect,
@@ -1492,17 +1497,17 @@ nsresult nsMenuPopupFrame::SetPopupPosit
     // readjust above/below or to the left/right.
     if (mAnchorContent || mAnchorType == MenuPopupAnchorType_Rect) {
       // move the popup according to the anchor and alignment. This will also
       // tell us which axis the popup is flush against in case we have to move
       // it around later. The AdjustPositionForAnchorAlign method accounts for
       // the popup's margin.
 
 #ifdef MOZ_WAYLAND
-      if (mozilla::widget::GdkIsWaylandDisplay()) {
+      if (IsWaylandDisplay()) {
         screenPoint = nsPoint(anchorRect.x, anchorRect.y);
         mAnchorRect = anchorRect;
       }
 #endif
       screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip);
     } else {
       // with no anchor, the popup is positioned relative to the root frame
       anchorRect = rootScreenRect;
@@ -1624,26 +1629,21 @@ nsresult nsMenuPopupFrame::SetPopupPosit
     } else {
       NS_WARNING("No widget associated with popup frame.");
     }
 #endif
     // shrink the the popup down if it is larger than the screen size
     if (mRect.width > screenRect.width) mRect.width = screenRect.width;
     if (mRect.height > screenRect.height) mRect.height = screenRect.height;
 
-      // We can't get the subsequent change of the popup position under
-      // waylande where gdk_window_move_to_rect is used to place them
-      // because we don't know the absolute position of the window on the
-      // screen.
-#ifdef MOZ_WAYLAND
-    static bool inWayland = mozilla::widget::GdkIsWaylandDisplay();
-#else
-    static bool inWayland = false;
-#endif
-    if (!inWayland) {
+    // We can't get the subsequent change of the popup position under
+    // waylande where gdk_window_move_to_rect is used to place them
+    // because we don't know the absolute position of the window on the
+    // screen.
+    if (!IsWaylandDisplay()) {
       // at this point the anchor (anchorRect) is within the available screen
       // area (screenRect) and the popup is known to be no larger than the
       // screen.
 
       // We might want to "slide" an arrow if the panel is of the correct type -
       // but we can only slide on one axis - the other axis must be "flipped or
       // resized" as normal.
       bool slideHorizontal = false, slideVertical = false;
@@ -1776,22 +1776,17 @@ LayoutDeviceIntRect nsMenuPopupFrame::Ge
   LayoutDeviceIntRect screenRectPixels;
 
   // determine the available screen space. It will be reduced by the OS chrome
   // such as menubars. It addition, for content shells, it will be the area of
   // the content rather than the screen.
   nsCOMPtr<nsIScreen> screen;
   nsCOMPtr<nsIScreenManager> sm(
       do_GetService("@mozilla.org/gfx/screenmanager;1"));
-#ifdef MOZ_WAYLAND
-  static bool inWayland = mozilla::widget::GdkIsWaylandDisplay();
-#else
-  static bool inWayland = false;
-#endif
-  if (sm && !inWayland) {
+  if (sm && !IsWaylandDisplay()) {
     // for content shells, get the screen where the root frame is located.
     // This is because we need to constrain the content to this content area,
     // so we should use the same screen. Otherwise, use the screen where the
     // anchor is located.
     DesktopToLayoutDeviceScale scale =
         PresContext()->DeviceContext()->GetDesktopToDeviceScale();
     DesktopRect rect =
         (mInContentShell ? aRootScreenRect : aAnchorRect) / scale;
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -379,18 +379,20 @@ static void UpdateLastInputEventTime(voi
 
 void GetWindowOrigin(GdkWindow* aWindow, int* aX, int* aY) {
   *aX = 0;
   *aY = 0;
 
   if (aWindow) {
     gdk_window_get_origin(aWindow, aX, aY);
   }
-
-  // TODO(bug 1655924): gdk_window_get_origin is can block waiting for the x
+  // GetWindowOrigin / gdk_window_get_origin is very fast on Wayland as the
+  // window position is cached by Gtk.
+
+  // TODO(bug 1655924): gdk_window_get_origin is can block waiting for the X
   // server for a long time, we would like to use the implementation below
   // instead. However, removing the synchronous x server queries causes a race
   // condition to surface, causing issues such as bug 1652743 and bug 1653711.
 #if 0
   *aX = 0;
   *aY = 0;
   if (!aWindow) {
     return;
@@ -456,25 +458,29 @@ nsWindow::nsWindow()
       mTransparencyBitmapForTitlebar(false),
       mHasAlphaVisual(false),
       mLastMotionPressure(0),
       mLastSizeMode(nsSizeMode_Normal),
       mBoundsAreValid(true),
       mPopupTrackInHierarchy(false),
       mPopupTrackInHierarchyConfigured(false),
       mPopupPosition(),
-      mTranslatedPopupPosition(),
+      mPopupAnchored(false),
+      mPopupContextMenu(false),
+      mRelativePopupPosition(),
+      mRelativePopupOffset(),
       mPopupMatchesLayout(false),
       mPopupChanged(false),
       mPopupTemporaryHidden(false),
       mPopupClosed(false),
+      mPopupLastAnchor(),
       mPreferredPopupRect(),
       mPreferredPopupRectFlushed(false),
-      mWaitingForMoveToRectCB(false),
-      mPendingSizeRect(LayoutDeviceIntRect(0, 0, 0, 0))
+      mWaitingForMoveToRectCallback(false),
+      mNewSizeAfterMoveToRect(LayoutDeviceIntRect(0, 0, 0, 0))
 #ifdef ACCESSIBILITY
       ,
       mRootAccessible(nullptr)
 #endif
 #ifdef MOZ_X11
       ,
       mXWindow(X11None),
       mXVisual(nullptr),
@@ -1213,31 +1219,32 @@ void nsWindow::Resize(double aX, double 
   ResizeInt(x, y, width, height, /* aMove */ true, aRepaint);
 }
 
 void nsWindow::Enable(bool aState) { mEnabled = aState; }
 
 bool nsWindow::IsEnabled() const { return mEnabled; }
 
 void nsWindow::Move(double aX, double aY) {
-  LOG(("nsWindow::Move [%p] %f %f\n", (void*)this, aX, aY));
-
   double scale =
       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
   int32_t x = NSToIntRound(aX * scale);
   int32_t y = NSToIntRound(aY * scale);
 
+  LOG(("nsWindow::Move [%p] to %d %d\n", (void*)this, x, y));
+
   if (mWindowType == eWindowType_toplevel ||
       mWindowType == eWindowType_dialog) {
     SetSizeMode(nsSizeMode_Normal);
   }
 
   // Since a popup window's x/y coordinates are in relation to to
   // the parent, the parent might have moved so we always move a
   // popup window.
+  LOG(("  bounds %d %d\n", mBounds.y, mBounds.y));
   if (x == mBounds.x && y == mBounds.y && mWindowType != eWindowType_popup) {
     return;
   }
 
   // XXX Should we do some AreBoundsSane check here?
 
   mBounds.x = x;
   mBounds.y = y;
@@ -1563,50 +1570,72 @@ void nsWindow::WaylandPopupHierarchyCalc
       auto size = popupFrame->GetSize();
       int32_t p2a =
           AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
       LOG_POPUP(("  popup [%p] layout [%d, %d] -> [%d x %d]", popup,
                  pos.x / p2a, pos.y / p2a, size.width / p2a,
                  size.height / p2a));
     }
 #endif
-    if (popup->mPopupAnchored) {
+    if (popup->mPopupContextMenu && !popup->mPopupAnchored) {
+      LOG_POPUP(("  popup [%p] is first context menu", popup));
+      static int menuOffsetX =
+          LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetHorizontal);
+      static int menuOffsetY =
+          LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetVertical);
+      popup->mRelativePopupPosition = popup->mPopupPosition;
+      mRelativePopupOffset.x = menuOffsetX;
+      mRelativePopupOffset.y = menuOffsetY;
+    } else if (popup->mPopupAnchored) {
       LOG_POPUP(("  popup [%p] is anchored", popup));
       if (!popup->mPopupMatchesLayout) {
         NS_WARNING("Anchored popup does not match layout!");
       }
-      popup->mTranslatedPopupPosition = popup->mPopupPosition;
+      popup->mRelativePopupPosition = popup->mPopupPosition;
     } else if (popup->mWaylandPopupPrev->mWaylandToplevel == nullptr) {
       LOG_POPUP(("  popup [%p] has toplevel as parent", popup));
-      popup->mTranslatedPopupPosition = popup->mPopupPosition;
+      popup->mRelativePopupPosition = popup->mPopupPosition;
     } else {
+      int parentX, parentY;
+      GetParentPosition(&parentX, &parentY);
+
       LOG_POPUP(("  popup [%p] uses transformed coordinates\n", popup));
-      popup->mTranslatedPopupPosition.x =
-          popup->mPopupPosition.x - popup->mWaylandPopupPrev->mPopupPosition.x;
-      popup->mTranslatedPopupPosition.y =
-          popup->mPopupPosition.y - popup->mWaylandPopupPrev->mPopupPosition.y;
+      LOG_POPUP(("    parent position [%d, %d]\n", parentX, parentY));
+      LOG_POPUP(("    popup position [%d, %d]\n", popup->mPopupPosition.x,
+                 popup->mPopupPosition.y));
+
+      popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parentX;
+      popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parentY;
     }
     LOG_POPUP(
-        ("  popup [%p] transformed popup coordinates [%d, %d] -> [%d, %d]",
+        ("  popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
          popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
-         popup->mTranslatedPopupPosition.x, popup->mTranslatedPopupPosition.y));
+         popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y));
     popup = popup->mWaylandPopupNext;
   }
 }
 
 // The MenuList popups are used as dropdown menus for example in WebRTC
 // microphone/camera chooser or autocomplete widgets.
 bool nsWindow::WaylandPopupIsMenu() {
   nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
   if (menuPopupFrame) {
     return mPopupType == ePopupTypeMenu && !menuPopupFrame->IsMenuList();
   }
   return false;
 }
 
+bool nsWindow::WaylandPopupIsContextMenu() {
+  nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+  if (!popupFrame) {
+    return false;
+  }
+  return popupFrame->IsContextMenu();
+}
+
 bool nsWindow::WaylandPopupIsPermanent() {
   nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
   if (!popupFrame) {
     // We can always hide popups without frames.
     return false;
   }
   return popupFrame->IsNoAutoHide();
 }
@@ -1624,58 +1653,65 @@ bool nsWindow::IsWidgetOverflowWindow() 
   if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
     nsCString nodeId;
     this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
     return nodeId.Equals("widget-overflow");
   }
   return false;
 }
 
+void nsWindow::GetParentPosition(int* aX, int* aY) {
+  *aX = *aY = 0;
+  GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+  if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+    NS_WARNING("Popup has no parent!");
+    return;
+  }
+  GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)), aX, aY);
+}
+
 #ifdef MOZ_LOGGING
 void nsWindow::LogPopupHierarchy() {
   LOG_POPUP(("Widget Popup Hierarchy:\n"));
   if (!mWaylandToplevel->mWaylandPopupNext) {
     LOG_POPUP(("    Empty\n"));
   } else {
     int indent = 4;
     nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
     while (popup) {
       nsPrintfCString indentString("%*s", indent, " ");
-      nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(popup->GetFrame());
       LOG_POPUP(
           ("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
            "Anchored %d Visible %d\n",
            indentString.get(), popup->GetWindowNodeName().get(),
            popup->GetPopupTypeName().get(), popup, popup->WaylandPopupIsMenu(),
-           popup->WaylandPopupIsPermanent(),
-           popupFrame && popupFrame->IsContextMenu(), popup->mPopupAnchored,
-           gtk_widget_is_visible(popup->mShell)));
+           popup->WaylandPopupIsPermanent(), popup->mPopupContextMenu,
+           popup->mPopupAnchored, gtk_widget_is_visible(popup->mShell)));
       indent += 4;
       popup = popup->mWaylandPopupNext;
     }
   }
 
   LOG_POPUP(("Layout Popup Hierarchy:\n"));
   AutoTArray<nsIWidget*, 5> widgetChain;
   GetLayoutPopupWidgetChain(&widgetChain);
   if (widgetChain.Length() == 0) {
     LOG_POPUP(("    Empty\n"));
   } else {
     for (unsigned long i = 0; i < widgetChain.Length(); i++) {
       nsWindow* window = static_cast<nsWindow*>(widgetChain[i]);
       nsPrintfCString indentString("%*s", (int)(i + 1) * 4, " ");
       if (window) {
-        nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(window->GetFrame());
         LOG_POPUP(
             ("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
              "Anchored %d Visible %d\n",
              indentString.get(), window->GetWindowNodeName().get(),
              window->GetPopupTypeName().get(), window,
              window->WaylandPopupIsMenu(), window->WaylandPopupIsPermanent(),
-             popupFrame && popupFrame->IsContextMenu(), window->mPopupAnchored,
+             window->mPopupContextMenu, window->mPopupAnchored,
              gtk_widget_is_visible(window->mShell)));
       } else {
         LOG_POPUP(("%s null window\n", indentString.get()));
       }
     }
   }
 }
 #endif
@@ -1707,16 +1743,17 @@ bool nsWindow::WaylandPopupNeedsTrackInH
   if (!popupFrame) {
     return false;
   }
   mPopupTrackInHierarchyConfigured = true;
 
   // We have nsMenuPopupFrame so we can configure the popup now.
   mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
   mPopupAnchored = WaylandPopupIsAnchored();
+  mPopupContextMenu = WaylandPopupIsContextMenu();
 
   // See gdkwindow-wayland.c and
   // should_map_as_popup()/should_map_as_subsurface()
   GdkWindowTypeHint gtkTypeHint;
   switch (mPopupHint) {
     case ePopupTypeMenu:
       // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default.
       // We use this type for all menu popups.
@@ -1858,19 +1895,20 @@ void nsWindow::UpdateWaylandPopupHierarc
   nsWindow* popup = changedPopup;
   while (popup) {
     // We can use move_to_rect only when popups in popup hierarchy matches
     // layout hierarchy as move_to_rect request that parent/child
     // popups are adjacent.
     bool useMoveToRect = popup->mPopupMatchesLayout;
     if (useMoveToRect) {
       // We use move_to_rect when:
-      // Popup is anchored, i.e. it has an anchor defined by layout
-      // (mPopupAnchored). Popup isn't anchored but has toplevel as parent, i.e.
-      // it's first popup.
+      // - Popup is anchored, i.e. it has an anchor defined by layout
+      //   (mPopupAnchored).
+      // - Popup isn't anchored but it has toplevel as parent, i.e.
+      //   it's first popup.
       useMoveToRect = (popup->mPopupAnchored ||
                        (!popup->mPopupAnchored &&
                         popup->mWaylandPopupPrev->mWaylandToplevel == nullptr));
     }
     LOG_POPUP(
         ("  popup [%p] matches layout [%d] anchored [%d] first popup [%d] use "
          "move-to-rect %d\n",
          popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
@@ -1878,98 +1916,79 @@ void nsWindow::UpdateWaylandPopupHierarc
     popup->WaylandPopupMove(useMoveToRect);
     popup->mPopupChanged = false;
     popup = popup->mWaylandPopupNext;
   }
 
   changedPopup->WaylandPopupHierarchyShowTemporaryHidden();
 }
 
-static void NativeMoveResizeWaylandPopupCallback(
-    GdkWindow* window, const GdkRectangle* flipped_rect,
-    const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y,
-    void* aWindow) {
-  LOG_POPUP(
-      ("NativeMoveResizeWaylandPopupCallback [%p] flipped_x %d flipped_y %d\n",
-       aWindow, flipped_x, flipped_y));
-
-  LOG_POPUP(("  flipped_rect x=%d y=%d width=%d height=%d\n", flipped_rect->x,
-             flipped_rect->y, flipped_rect->width, flipped_rect->height));
-  LOG_POPUP(("  final_rect   x=%d y=%d width=%d height=%d\n", final_rect->x,
+static void NativeMoveResizeCallback(GdkWindow* window,
+                                     const GdkRectangle* flipped_rect,
+                                     const GdkRectangle* final_rect,
+                                     gboolean flipped_x, gboolean flipped_y,
+                                     void* aWindow) {
+  LOG_POPUP(("NativeMoveResizeCallback [%p] flipped_x %d flipped_y %d\n",
+             aWindow, flipped_x, flipped_y));
+  LOG_POPUP(("  new position [%d, %d] -> [%d x %d]", final_rect->x,
              final_rect->y, final_rect->width, final_rect->height));
   nsWindow* wnd = get_window_for_gdk_window(window);
 
-  wnd->NativeMoveResizeWaylandPopupCB(final_rect, flipped_x, flipped_y);
-}
-
-void nsWindow::NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize,
-                                              bool aFlippedX, bool aFlippedY) {
-  LOG_POPUP(("  orig mBounds x=%d y=%d width=%d height=%d\n", mBounds.x,
-             mBounds.y, mBounds.width, mBounds.height));
-
-  mWaitingForMoveToRectCB = false;
+  wnd->NativeMoveResizeWaylandPopupCallback(final_rect, flipped_x, flipped_y);
+}
+
+void nsWindow::NativeMoveResizeWaylandPopupCallback(
+    const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
+  mWaitingForMoveToRectCallback = false;
 
   // We ignore the callback position data because the another resize has been
   // called before the callback have been triggered.
-  if (mPendingSizeRect.height > 0 || mPendingSizeRect.width > 0) {
+  if (mNewSizeAfterMoveToRect.height > 0 || mNewSizeAfterMoveToRect.width > 0) {
     LOG_POPUP(
         ("  Another resize called during waiting for callback, calling "
          "Resize(%d, %d)\n",
-         mPendingSizeRect.width, mPendingSizeRect.height));
+         mNewSizeAfterMoveToRect.width, mNewSizeAfterMoveToRect.height));
     // Set the preferred size to zero to avoid wrong size of popup because the
     // mPreferredPopupRect is used in nsMenuPopupFrame to set dimensions
     mPreferredPopupRect = nsRect(0, 0, 0, 0);
 
     // We need to schedule another resize because the window has been resized
     // again before callback was called.
-    Resize(mPendingSizeRect.width, mPendingSizeRect.height, true);
+    Resize(mNewSizeAfterMoveToRect.width, mNewSizeAfterMoveToRect.height, true);
     DispatchResized();
-    mPendingSizeRect.width = mPendingSizeRect.height = 0;
-    return;
-  }
-
-  // TODO: use out widget hierarchy to calculate widget position/hierarchy.
-  GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
-  if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
-    NS_WARNING("Popup has no parent!");
+    mNewSizeAfterMoveToRect.width = mNewSizeAfterMoveToRect.height = 0;
     return;
   }
 
-  // The position of the menu in GTK is relative to it's parent window while
-  // in mBounds we have position relative to toplevel window. We need to check
-  // and update mBounds in the toplevel coordinates.
-  int x_parent, y_parent;
-  GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)), &x_parent,
-                  &y_parent);
-
-  LayoutDeviceIntRect newBounds(aFinalSize->x, aFinalSize->y, aFinalSize->width,
-                                aFinalSize->height);
-
-  newBounds.x = GdkCoordToDevicePixels(newBounds.x);
-  newBounds.y = GdkCoordToDevicePixels(newBounds.y);
+  int parentX, parentY;
+  GetParentPosition(&parentX, &parentY);
+
+  parentX = GdkCoordToDevicePixels(parentX);
+  parentY = GdkCoordToDevicePixels(parentY);
+
+  LOG_POPUP(("  orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
+             mBounds.width, mBounds.height));
+
+  LayoutDeviceIntRect newBounds(0, 0, aFinalSize->width, aFinalSize->height);
+  newBounds.x = GdkCoordToDevicePixels(aFinalSize->x) + parentX;
+  newBounds.y = GdkCoordToDevicePixels(aFinalSize->y) + parentY;
 
   double scale =
       BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
   int32_t newWidth = NSToIntRound(scale * newBounds.width);
   int32_t newHeight = NSToIntRound(scale * newBounds.height);
 
-  // Convert newBounds to "absolute" coordinates (relative to toplevel)
-  newBounds.x += x_parent * GdkCeiledScaleFactor();
-  newBounds.y += y_parent * GdkCeiledScaleFactor();
-
-  LOG_POPUP(
-      ("  new mBounds  x=%d y=%d width=%d height=%d x_parent=%d y_parent=%d\n",
-       newBounds.x, newBounds.y, newWidth, newHeight, x_parent, y_parent));
+  LOG_POPUP(("  new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
+             newWidth, newHeight));
 
   bool needsPositionUpdate =
       (newBounds.x != mBounds.x || newBounds.y != mBounds.y);
   bool needsSizeUpdate =
       (newWidth != mBounds.width || newHeight != mBounds.height);
   // Update view
-
   if (needsSizeUpdate) {
     LOG_POPUP(("  needSizeUpdate\n"));
     // TODO: use correct monitor here?
     int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
     mPreferredPopupRect = nsRect(NSIntPixelsToAppUnits(newBounds.x, p2a),
                                  NSIntPixelsToAppUnits(newBounds.y, p2a),
                                  NSIntPixelsToAppUnits(newBounds.width, p2a),
                                  NSIntPixelsToAppUnits(newBounds.height, p2a));
@@ -1983,24 +2002,21 @@ void nsWindow::NativeMoveResizeWaylandPo
       presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::Resize,
                                   NS_FRAME_IS_DIRTY);
       // Force to trigger popup crop to fit the screen
       popupFrame->SetPopupPosition(nullptr, true, false);
     }
   }
 
   if (needsPositionUpdate) {
-    LOG_POPUP(("  needPositionUpdate\n"));
-    // The newBounds are in coordinates relative to the parent window/popup.
-    // The NotifyWindowMoved requires the coordinates relative to the toplevel.
-    // We use the gdk_window_get_origin to get correct coordinates.
-    // TODO: anchored/unanchored
-    gint x, y;
-    GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(mShell)), &x, &y);
-    NotifyWindowMoved(GdkCoordToDevicePixels(x), GdkCoordToDevicePixels(y));
+    LOG_POPUP(("  needPositionUpdate, new bounds [%d, %d]", newBounds.x,
+               newBounds.y));
+    mBounds.x = newBounds.x;
+    mBounds.y = newBounds.y;
+    NotifyWindowMoved(mBounds.x, mBounds.y);
   }
 }
 
 #ifdef MOZ_WAYLAND
 static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
   switch (aAlignment) {
     case POPUPALIGNMENT_NONE:
       return GDK_GRAVITY_NORTH_WEST;
@@ -2079,95 +2095,111 @@ void nsWindow::WaylandPopupMove(bool aUs
   LOG_POPUP(("nsWindow::WaylandPopupMove [%p]\n", (void*)this));
 
   // 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");
 
   GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
-
-  if (!sGdkWindowMoveToRect || !gdkWindow || !aUseMoveToRect) {
-    LOG_POPUP(("  use gtk_window_move(%d, %d)\n", mTranslatedPopupPosition.x,
-               mTranslatedPopupPosition.y));
-    gtk_window_move(GTK_WINDOW(mShell), mTranslatedPopupPosition.x,
-                    mTranslatedPopupPosition.y);
-    return;
-  }
+  nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 
   LOG_POPUP(("  original widget popup position [%d, %d]\n", mPopupPosition.x,
              mPopupPosition.y));
-  LOG_POPUP(("  translated widget popup position [%d, %d]\n",
-             mTranslatedPopupPosition.x, mTranslatedPopupPosition.y));
+  LOG_POPUP(("  relative widget popup position [%d, %d]\n",
+             mRelativePopupPosition.x, mRelativePopupPosition.y));
+  LOG_POPUP(("  relative widget popup offset [%d, %d]\n",
+             mRelativePopupOffset.x, mRelativePopupOffset.y));
+
+  if (!sGdkWindowMoveToRect || !gdkWindow || !aUseMoveToRect || !popupFrame) {
+    LOG_POPUP(("  use gtk_window_move(%d, %d)\n", mRelativePopupPosition.x,
+               mRelativePopupPosition.y));
+    gtk_window_move(GTK_WINDOW(mShell),
+                    mRelativePopupPosition.x + mRelativePopupOffset.x,
+                    mRelativePopupPosition.y + mRelativePopupOffset.y);
+    if (mRelativePopupOffset.x || mRelativePopupOffset.y) {
+      mBounds.x = (mRelativePopupPosition.x + mRelativePopupOffset.x) *
+                  FractionalScaleFactor();
+      mBounds.y = (mRelativePopupPosition.y + mRelativePopupOffset.y) *
+                  FractionalScaleFactor();
+      LOG_POPUP(("  setting new bounds [%d, %d]\n", mBounds.x, mBounds.y));
+      NotifyWindowMoved(mBounds.x, mBounds.y);
+    }
+    return;
+  }
 
   // Get anchor rectangle
   LayoutDeviceIntRect anchorRect(0, 0, 0, 0);
-  nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
 
   int32_t p2a;
   double devPixelsPerCSSPixel = StaticPrefs::layout_css_devPixelsPerPx();
   if (devPixelsPerCSSPixel > 0.0) {
     p2a = AppUnitsPerCSSPixel() / devPixelsPerCSSPixel * GdkCeiledScaleFactor();
   } else {
     p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
   }
-  if (popupFrame) {
-#ifdef MOZ_WAYLAND
-    nsRect anchorRectAppUnits = popupFrame->GetAnchorRect();
-    anchorRect = LayoutDeviceIntRect::FromUnknownRect(
-        anchorRectAppUnits.ToNearestPixels(p2a));
-#endif
-  }
-  LOG_POPUP(("  layout popup position [%d, %d]\n", anchorRect.x, anchorRect.y));
-
-#ifdef MOZ_WAYLAND
-  bool hasAnchorRect = true;
-#endif
-  if (anchorRect.width == 0) {
-    LOG_POPUP(("  No anchor rect given, use position for anchor"));
-    anchorRect.SetRect(mPopupPosition.x, mPopupPosition.y, 1, 1);
-#ifdef MOZ_WAYLAND
-    hasAnchorRect = false;
-#endif
-  }
-  LOG_POPUP(("  anchor x %d y %d width %d height %d (absolute coords)\n",
-             anchorRect.x, anchorRect.y, anchorRect.width, anchorRect.height));
+  nsRect anchorRectAppUnits = popupFrame->GetAnchorRect();
+  anchorRect = LayoutDeviceIntRect::FromUnknownRect(
+      anchorRectAppUnits.ToNearestPixels(p2a));
 
   // Anchor rect is in the toplevel coordinates but we need to transfer it to
   // the coordinates relative to the popup parent for the
   // gdk_window_move_to_rect
-  if (mWaylandPopupPrev->mWaylandToplevel != nullptr) {
-    anchorRect.x -= mWaylandPopupPrev->mPopupPosition.x;
-    anchorRect.y -= mWaylandPopupPrev->mPopupPosition.y;
-  }
-
-  GdkRectangle rect = {anchorRect.x, anchorRect.y, anchorRect.width,
-                       anchorRect.height};
-
-  LOG_POPUP(("  final popup position [%d, %d]\n", anchorRect.x, anchorRect.y));
+  LOG_POPUP(("  layout popup anchor [%d, %d] -> [%d, %d]\n", anchorRect.x,
+             anchorRect.y, anchorRect.width, anchorRect.height));
+
+  bool hasAnchorRect = true;
+  if (mRelativePopupOffset.x || mRelativePopupOffset.y) {
+    hasAnchorRect = false;
+    anchorRect.SetRect(mRelativePopupPosition.x - mRelativePopupOffset.x,
+                       mRelativePopupPosition.y - mRelativePopupOffset.y,
+                       mRelativePopupOffset.x * 2, mRelativePopupOffset.y * 2);
+    LOG_POPUP(("  Set anchor rect with offset [%d, %d] -> [%d x %d]",
+               anchorRect.x, anchorRect.y, anchorRect.width,
+               anchorRect.height));
+  } else if (anchorRect.width == 0) {
+    LOG_POPUP(("  No anchor rect given, use position for anchor [%d, %d]",
+               mRelativePopupPosition.x, mRelativePopupPosition.y));
+    hasAnchorRect = false;
+    anchorRect.SetRect(mRelativePopupPosition.x, mRelativePopupPosition.y, 1,
+                       1);
+  } else {
+    if (mWaylandPopupPrev->mWaylandToplevel != nullptr) {
+      int parentX, parentY;
+      GetParentPosition(&parentX, &parentY);
+      LOG_POPUP(("  subtract parent position [%d, %d]\n", parentX, parentY));
+      anchorRect.x -= parentX;
+      anchorRect.y -= parentY;
+    }
+  }
+
+  LOG_POPUP(("  final popup rect position [%d, %d] -> [%d x %d]\n",
+             anchorRect.x, anchorRect.y, anchorRect.width, anchorRect.height));
 
   // Get gravity and flip type
   GdkGravity rectAnchor = GDK_GRAVITY_NORTH_WEST;
   GdkGravity menuAnchor = GDK_GRAVITY_NORTH_WEST;
   FlipType flipType = FlipType_Default;
   int8_t position = -1;
-  if (popupFrame) {
-#ifdef MOZ_WAYLAND
+  if (mPopupContextMenu && !mPopupAnchored) {
+    if (GetTextDirection() == GTK_TEXT_DIR_RTL) {
+      rectAnchor = GDK_GRAVITY_SOUTH_WEST;
+      menuAnchor = GDK_GRAVITY_NORTH_EAST;
+
+    } else {
+      rectAnchor = GDK_GRAVITY_SOUTH_EAST;
+      menuAnchor = GDK_GRAVITY_NORTH_WEST;
+    }
+  } else {
     rectAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAnchor());
     menuAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAlignment());
     flipType = popupFrame->GetFlipType();
     position = popupFrame->GetAlignmentPosition();
-#endif
-  } else {
-    LOG_POPUP(("  NO ANCHOR INFO"));
-    if (GetTextDirection() == GTK_TEXT_DIR_RTL) {
-      rectAnchor = GDK_GRAVITY_NORTH_EAST;
-      menuAnchor = GDK_GRAVITY_NORTH_EAST;
-    }
-  }
+  }
+
   LOG_POPUP(("  parentRect gravity: %d anchor gravity: %d\n", rectAnchor,
              menuAnchor));
 
   // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE.
   // We want to SLIDE_X menu on the dual monitor setup rather than resize it
   // on the other monitor.
   GdkAnchorHints hints =
       GdkAnchorHints(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE);
@@ -2178,18 +2210,17 @@ void nsWindow::WaylandPopupMove(bool aUs
     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
   }
   // slideVertical from nsMenuPopupFrame::SetPopupPosition
   if (position >= POPUPPOSITION_STARTBEFORE &&
       position <= POPUPPOSITION_ENDAFTER) {
     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
   }
 
-  if (popupFrame && rectAnchor == GDK_GRAVITY_CENTER &&
-      menuAnchor == GDK_GRAVITY_CENTER) {
+  if (rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER) {
     // only slide
     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
   } else {
     switch (flipType) {
       case FlipType_Both:
         hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
         break;
       case FlipType_Slide:
@@ -2202,48 +2233,20 @@ void nsWindow::WaylandPopupMove(bool aUs
         break;
     }
   }
   if (!WaylandPopupIsMenu()) {
     // we don't want to slide menus to fit the screen rather resize them
     hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
   }
 
-  // A workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1986
-  // gdk_window_move_to_rect() does not reposition visible windows.
-  bool applyGtkWorkaround = gtk_widget_is_visible(mShell);
-  if (applyGtkWorkaround) {
-    LOG_POPUP(
-        ("  temporary hide popup due to "
-         "https://gitlab.gnome.org/GNOME/gtk/issues/1986\n"));
-
-    // We can't apply the hide/show workaround as 'this' is not top popup
-    // window. If we hide this popup it will lead to Wayland protocol violation
-    // as child window of this popup will not have correct parent after
-    // the hide.
-    if (mWaylandPopupNext) {
-      LOG_POPUP(
-          ("  can't apply window move_to_rect workaround, we have child "
-           "popups!\n"));
-      applyGtkWorkaround = false;
-    }
-  }
-  if (applyGtkWorkaround) {
-    PauseCompositor();
-    gtk_widget_hide(mShell);
-  }
-
-  LOG_POPUP(("  requested rect: x: %d y: %d width: %d height: %d\n", rect.x,
-             rect.y, rect.width, rect.height));
-
   // Inspired by nsMenuPopupFrame::AdjustPositionForAnchorAlign
   nsPoint cursorOffset(0, 0);
-#ifdef MOZ_WAYLAND
   // Offset is already computed to the tooltips
-  if (hasAnchorRect && popupFrame && mPopupType != ePopupTypeTooltip) {
+  if (hasAnchorRect && mPopupType != ePopupTypeTooltip) {
     nsMargin margin(0, 0, 0, 0);
     popupFrame->StyleMargin()->GetMargin(margin);
     switch (popupFrame->GetPopupAlignment()) {
       case POPUPALIGNMENT_TOPRIGHT:
         cursorOffset.MoveBy(-margin.right, margin.top);
         break;
       case POPUPALIGNMENT_BOTTOMLEFT:
         cursorOffset.MoveBy(margin.left, -margin.bottom);
@@ -2252,28 +2255,41 @@ void nsWindow::WaylandPopupMove(bool aUs
         cursorOffset.MoveBy(-margin.right, -margin.bottom);
         break;
       case POPUPALIGNMENT_TOPLEFT:
       default:
         cursorOffset.MoveBy(margin.left, margin.top);
         break;
     }
   }
-#endif
-
-  if (!g_signal_handler_find(
-          gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
-          FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) {
+
+  if (!g_signal_handler_find(gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+                             FuncToGpointer(NativeMoveResizeCallback), this)) {
     g_signal_connect(gdkWindow, "moved-to-rect",
-                     G_CALLBACK(NativeMoveResizeWaylandPopupCallback), this);
+                     G_CALLBACK(NativeMoveResizeCallback), this);
   }
 
   LOG_POPUP(("  popup window cursor offset x: %d y: %d\n", cursorOffset.x / p2a,
              cursorOffset.y / p2a));
-  mWaitingForMoveToRectCB = true;
+  GdkRectangle rect = {anchorRect.x, anchorRect.y, anchorRect.width,
+                       anchorRect.height};
+
+  mWaitingForMoveToRectCallback = true;
+
+  // A workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1986
+  // gdk_window_move_to_rect() does not reposition visible windows.
+  // We can apply the hide/show workaround if 'this' is latest popup
+  // window.
+  bool applyGtkWorkaround = !mWaylandPopupNext && gtk_widget_is_visible(mShell);
+  if (applyGtkWorkaround) {
+    PauseCompositor();
+    gtk_widget_hide(mShell);
+  }
+
+  mPopupLastAnchor = anchorRect;
   sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints,
                        cursorOffset.x / p2a, cursorOffset.y / p2a);
 
   // We show the popup with the same configuration so no need to call
   // UpdateWaylandPopupHierarchy() before gtk_widget_show().
   if (applyGtkWorkaround) {
     gtk_widget_show(mShell);
   }
@@ -5563,17 +5579,18 @@ nsresult nsWindow::Create(nsIWidget* aPa
     LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow));
   } else if (mGdkWindow) {
     LOG(("\tmGdkWindow %p parent %p\n", mGdkWindow,
          gdk_window_get_parent(mGdkWindow)));
   }
 
   // resize so that everything is set to the right dimensions
   if (!mIsTopLevel) {
-    Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, false);
+    ResizeInt(mBounds.x, mBounds.y, mBounds.width, mBounds.height,
+              /* aMove */ false, /* aRepaint */ false);
   }
 
 #ifdef MOZ_X11
   if (GdkIsX11Display() && mGdkWindow) {
     mXWindow = gdk_x11_window_get_xid(mGdkWindow);
 
     GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
     mXVisual = gdk_x11_visual_get_xvisual(gdkVisual);
@@ -5691,19 +5708,19 @@ void nsWindow::NativeResize() {
 
   LOG(("nsWindow::NativeResize [%p] %d %d\n", (void*)this, size.width,
        size.height));
 
   if (mIsTopLevel) {
     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);
-    if (mWaitingForMoveToRectCB) {
+    if (mWaitingForMoveToRectCallback) {
       LOG_POPUP(("  waiting for move to rect, schedulling "));
-      mPendingSizeRect = mBounds;
+      mNewSizeAfterMoveToRect = mBounds;
     }
   } else if (mContainer) {
     GtkWidget* widget = GTK_WIDGET(mContainer);
     GtkAllocation allocation, prev_allocation;
     gtk_widget_get_allocation(widget, &prev_allocation);
     allocation.x = prev_allocation.x;
     allocation.y = prev_allocation.y;
     allocation.width = size.width;
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -370,18 +370,18 @@ class nsWindow final : public nsBaseWidg
    * the XDG_CURRENT_DESKTOP environment variable.
    */
   static GtkWindowDecoration GetSystemGtkWindowDecoration();
 
   static bool HideTitlebarByDefault();
   static bool GetTopLevelWindowActiveState(nsIFrame* aFrame);
   static bool TitlebarUseShapeMask();
   bool IsRemoteContent() { return HasRemoteContent(); }
-  void NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize,
-                                      bool aFlippedX, bool aFlippedY);
+  void NativeMoveResizeWaylandPopupCallback(const GdkRectangle* aFinalSize,
+                                            bool aFlippedX, bool aFlippedY);
   static bool IsToplevelWindowTransparent();
 
 #ifdef MOZ_WAYLAND
   bool GetCSDDecorationOffset(int* aDx, int* aDy);
   void SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
   static nsWindow* GetFocusedWindow();
   void WaylandDragWorkaround(GdkEventButton* aEvent);
 
@@ -610,20 +610,23 @@ class nsWindow final : public nsBaseWidg
   void ForceTitlebarRedraw();
   bool DoDrawTilebarCorners();
   bool IsChromeWindowTitlebar();
 
   void SetPopupWindowDecoration(bool aShowOnTaskbar);
 
   void ApplySizeConstraints(void);
 
+  void GetParentPosition(int* aX, int* aY);
+
   // Wayland Popup section
   bool WaylandPopupNeedsTrackInHierarchy();
   bool WaylandPopupIsAnchored();
   bool WaylandPopupIsMenu();
+  bool WaylandPopupIsContextMenu();
   bool WaylandPopupIsPermanent();
   bool IsWidgetOverflowWindow();
   void RemovePopupFromHierarchyList();
   void HideWaylandWindow();
   void HideWaylandPopupWindow(bool aTemporaryHidden, bool aRemoveFromPopupList);
   void HideWaylandToplevelWindow();
   void WaylandPopupHideTooltips();
   void AppendPopupToHierarchyList(nsWindow* aToplevelWindow);
@@ -692,19 +695,27 @@ class nsWindow final : public nsBaseWidg
    *  set by nsWindow::Move() or nsWindow::Resize().
    */
   GdkPoint mPopupPosition;
 
   /*  When popup is anchored, mPopupPosition is relative to its parent popup.
    */
   bool mPopupAnchored;
 
-  /*  Translated mPopupPosition against parent window when it's anchored.
+  /*  When popup is context menu.
+   */
+  bool mPopupContextMenu;
+
+  /*  mRelativePopupPosition is popup position calculated against parent window.
    */
-  GdkPoint mTranslatedPopupPosition;
+  GdkPoint mRelativePopupPosition;
+
+  /* mRelativePopupOffset is used by context menus.
+   */
+  GdkPoint mRelativePopupOffset;
 
   /*  Indicates that this popup matches layout setup so we can use
    *  parent popup coordinates reliably.
    */
   bool mPopupMatchesLayout;
 
   /*  Indicates that popup setup was changed and
    *  we need to recalculate popup coordinates.
@@ -714,39 +725,44 @@ class nsWindow final : public nsBaseWidg
   /*  Popup is hidden only as a part of hierarchy tree update.
    */
   bool mPopupTemporaryHidden;
 
   /*  Popup is going to be closed and removed.
    */
   bool mPopupClosed;
 
+  /* Last used anchor for move-to-rect.
+   */
+  LayoutDeviceIntRect mPopupLastAnchor;
+
   /* Toplevel window (first element) of linked list of wayland popups.
    * It's nullptr if we're the toplevel.
    */
   RefPtr<nsWindow> mWaylandToplevel;
 
   /* Next/Previous popups in Wayland popup hieararchy.
    */
   RefPtr<nsWindow> mWaylandPopupNext;
   RefPtr<nsWindow> mWaylandPopupPrev;
 
   /* Used by WaylandPopupMove() to track popup movement.
    *
    */
   nsRect mPreferredPopupRect;
   bool mPreferredPopupRectFlushed;
 
-  /* Set true when we call move-to-rect and before move-to-rect callback
-   * comes back another resize is issued. In such case we need to ignore
-   * size from move-to-rect callback callback and use size from the latest
-   * resize (mPendingSizeRect).
+  /* mWaitingForMoveToRectCallback is set when move-to-rect is called
+   * and we're waiting for move-to-rect callback.
+   *
+   * If another resize request comes between move-to-rect call and
+   * move-to-rect callback we store it to mNewSizeAfterMoveToRect.
    */
-  bool mWaitingForMoveToRectCB;
-  LayoutDeviceIntRect mPendingSizeRect;
+  bool mWaitingForMoveToRectCallback;
+  LayoutDeviceIntRect mNewSizeAfterMoveToRect;
 
   /**
    * |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