author | stransky <stransky@redhat.com> |
Mon, 14 Jun 2021 18:01:04 +0000 (2021-06-14) | |
changeset 583046 | 7b82d177a6b979036f180329be6b029d690d9e0c |
parent 583045 | 9f0fbb1431721c9eae68a3c94ae49a4d33fdb1f8 |
child 583047 | 177ac92fb734b80f07c04710ec70f0b89a073351 |
push id | 144921 |
push user | stransky@redhat.com |
push date | Mon, 14 Jun 2021 18:22:00 +0000 (2021-06-14) |
treeherder | autoland@7b82d177a6b9 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jhorak |
bugs | 1661516 |
milestone | 91.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
|
--- 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