/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
1)InConfigureToplevelWindowNative()weregisteran"xdg-toplevel-realized" GTKsignalhandlerhandler_stats_updated(java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 40 2)BlockNativeShow()whilemWaitingToSessionRestoreisset.Wecan'tshow thetoplevelwindowuntilwehaveawindowrestoreIDprovidedbya MoveToWorkspace()call. 3)InMoveToWorkspace(),savethesessionIDandshowthewindow.Thatfires the"xdg-toplevel-realized"signalwhichgivesusthexdg_toplevelandwe restorewindowstateaccordingtothesessionID.
// This is an ugly workaround for // https://bugzilla.mozilla.org/show_bug.cgi?id=1622107 // We try to detect when Wayland compositor / gtk fails to deliver // info about finished D&D operations and cancel it on our own. // // The sequence is: // 1) D&D is started on both Wayland & Firefox side // 2) D&D is finished on Wayland side, Wayland doesn't deliver drag finish to // Firefox, Firefox D&D is still active. // 4) Firefox UI is blocked by the D&D from 1) // 5) We use mouse button to get mouse clicks and cancel the Firefox D&D // session. // void nsWindowWayland::WaylandDragWorkaround(GdkEventButton* aEvent) { // We track only left button state as Firefox performs D&D on left // button only. if (aEvent->button != 1 || aEvent->type != GDK_BUTTON_RELEASE) { return;
}
nsCOMPtr<nsIDragService> dragService =
do_GetService("@mozilla.org/widget/dragservice;1"); if (!dragService) { return;
}
nsCOMPtr<nsIDragSession> currentDragSession =
dragService->GetCurrentSession(this); if (!currentDragSession || static_cast<nsDragSession*>(currentDragSession.get())->IsActive()) { return;
}
LOGDRAG("WaylandDragWorkaround applied, quit D&D session");
NS_WARNING( "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland " "compositor?");
currentDragSession->EndDragSession(true, 0);
}
GdkDevice* device = gdk_device_manager_get_client_pointer(manager); if (!device) {
LOG("nsWindowWayland::LockNativePointer() - quit, could not find Wayland " "pointer " "to lock."); return;
}
wl_pointer* pointer = gdk_wayland_device_get_wl_pointer(device);
MOZ_ASSERT(pointer);
wl_surface* surface =
gdk_wayland_window_get_wl_surface(GetToplevelGdkWindow()); if (!surface) {
LOG("nsWindowWayland::LockNativePointer() - quit, toplevel surface is " "hidden."); /* Can be null when the window is hidden. *Thoughit'sunlikelythatalockrequestcomesinthatcase,be
* defensive. */ return;
}
static PopupSides SidesForPopupAlignment(int8_t aAlignment) { switch (aAlignment) { case POPUPALIGNMENT_NONE: break; case POPUPALIGNMENT_TOPLEFT: return {Some(eSideTop), Some(eSideLeft)}; case POPUPALIGNMENT_TOPRIGHT: return {Some(eSideTop), Some(eSideRight)}; case POPUPALIGNMENT_BOTTOMLEFT: return {Some(eSideBottom), Some(eSideLeft)}; case POPUPALIGNMENT_BOTTOMRIGHT: return {Some(eSideBottom), Some(eSideRight)}; case POPUPALIGNMENT_LEFTCENTER: return {Nothing(), Some(eSideLeft)}; case POPUPALIGNMENT_RIGHTCENTER: return {Nothing(), Some(eSideRight)}; case POPUPALIGNMENT_TOPCENTER: return {Some(eSideTop), Nothing()}; case POPUPALIGNMENT_BOTTOMCENTER: return {Some(eSideBottom), Nothing()};
} return {};
}
// We want to apply margins based on popup alignment (which would generally be // just an offset to apply to the popup). However, to deal with flipping // correctly, we apply the margin to the anchor when possible. struct ResolvedPopupMargin { // A margin to be applied to the anchor.
nsMargin mAnchorMargin; // An offset in app units to be applied to the popup for when we need to tell // GTK to center inside the anchor precisely (so we can't really do better in // presence of flips).
nsPoint mPopupOffset;
};
if (aAnchoredToPoint) { // Since GTK doesn't allow us to specify margins itself, when anchored to a // point we can just assume we'll be aligned correctly... This is kind of // annoying but alas. // // This calculation must match the relevant unanchored popup calculation in // nsMenuPopupFrame::SetPopupPosition(), which should itself be the inverse // inverse of nsMenuPopupFrame::MoveTo(). if (aIsContextMenu && aFrame->IsDirectionRTL()) {
offset.x = -margin.right;
} else {
offset.x = margin.left;
}
offset.y = margin.top; return {nsMargin(), offset};
}
auto popupSides = SidesForPopupAlignment(aPopupAlign); auto anchorSides = SidesForPopupAlignment(aAnchorAlign); // Matched sides: Invert the margin, so that we pull in the right direction. // Popup not aligned to any anchor side: We give up and use the offset, // applying the margin from the popup side. // Mismatched sides: We swap the margins so that we pull in the right // direction, e.g. margin-left: -10px should shrink 10px the _right_ of the // box, not the left of the box. if (popupSides.mHorizontal == anchorSides.mHorizontal) {
margin.left = -margin.left;
margin.right = -margin.right;
} elseif (!anchorSides.mHorizontal) { auto popupSide = *popupSides.mHorizontal;
offset.x += popupSide == eSideRight ? -margin.Side(popupSide)
: margin.Side(popupSide);
margin.left = margin.right = 0;
} else {
std::swap(margin.left, margin.right);
}
// Same logic as above, but in the vertical direction. if (popupSides.mVertical == anchorSides.mVertical) {
margin.top = -margin.top;
margin.bottom = -margin.bottom;
} elseif (!anchorSides.mVertical) { auto popupSide = *popupSides.mVertical;
offset.y += popupSide == eSideBottom ? -margin.Side(popupSide)
: margin.Side(popupSide);
margin.top = margin.bottom = 0;
} else {
std::swap(margin.top, margin.bottom);
}
// So we need to extract popup position from nsMenuPopupFrame() and duplicate // the layout work here.
LayoutDeviceIntRect anchorRect;
ResolvedPopupMargin popupMargin;
{
nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect(); // This is a somewhat hacky way of applying the popup margin. We don't know // if GTK will end up flipping the popup, in which case the offset we // compute is just wrong / applied to the wrong side. // // Instead, we tell it to anchor us at a smaller or bigger rect depending on // the margin, which achieves the same result if the popup is positioned // correctly, but doesn't misposition the popup when flipped across the // anchor.
popupMargin = ResolveMargin(popupFrame, popupAlign, anchorAlign,
anchorRectAppUnits.IsEmpty(), isTopContextMenu);
LOG(" layout popup CSS anchor (%d, %d) %s, margin %s offset %s\n",
popupAlign, anchorAlign, ToString(anchorRectAppUnits).c_str(),
ToString(popupMargin.mAnchorMargin).c_str(),
ToString(popupMargin.mPopupOffset).c_str());
anchorRectAppUnits.Inflate(popupMargin.mAnchorMargin);
LOG(" after margins %s\n", ToString(anchorRectAppUnits).c_str());
nscoord auPerDev = popupFrame->PresContext()->AppUnitsPerDevPixel();
anchorRect = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRectAppUnits,
auPerDev); if (anchorRect.width < 0) { auto w = -anchorRect.width;
anchorRect.width += w + 1;
anchorRect.x += w;
}
LOG(" final %s\n", ToString(anchorRect).c_str());
}
LOG(" relative popup rect position [%d, %d] -> [%d x %d]\n", anchorRect.x,
anchorRect.y, anchorRect.width, anchorRect.height);
// Get gravity and flip type
GdkGravity rectAnchor = PopupAlignmentToGdkGravity(anchorAlign);
GdkGravity menuAnchor = PopupAlignmentToGdkGravity(popupAlign);
// slideHorizontal from nsMenuPopupFrame::SetPopupPosition const int8_t position = popupFrame->GetAlignmentPosition(); // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE. constauto hints = GdkAnchorHints([&] { // We want tooltips to flip vertically or slide only. // See nsMenuPopupFrame::SetPopupPosition(). // https://searchfox.org/mozilla-central/rev/d0f5bc50aff3462c9d1546b88d60c5cb020eb15c/layout/xul/nsMenuPopupFrame.cpp#1603 if (mPopupType == PopupType::Tooltip) { return GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE;
} // We want to SLIDE_X menu on dual monitor setup rather than resize it // on the other monitor, so we always allow sliding horizontally. // slideVertical position check comes from the same variable in // nsMenuPopupFrame::SetPopupPosition. // // NOTE(emilio): It feels odd to not honor Gecko's FlipType more, but // historically we've done that... Maybe reconsider? But note that // flipping is tried before sliding, so it seems not too bad? constbool slideVertical =
(position >= POPUPPOSITION_STARTBEFORE &&
position <= POPUPPOSITION_ENDAFTER) ||
!anchored || popupFrame->GetFlipType() == FlipType::Slide ||
(rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER); return GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X |
(slideVertical ? GDK_ANCHOR_SLIDE_Y : 0) | GDK_ANCHOR_RESIZE;
}());
GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell)); if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
NS_WARNING("Popup has no parent!"); returnfalse;
}
GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)); if (!window) {
NS_WARNING("Popup parrent is not mapped!"); returnfalse;
}
// We can't have rectangle anchor with zero width/height. if (!aPopupAnchor->width) {
aPopupAnchor->width = 1;
} if (!aPopupAnchor->height) {
aPopupAnchor->height = 1;
}
GdkRectangle finalRect; if (!gdk_rectangle_intersect(aPopupAnchor, &parentWindowRect, &finalRect)) { returnfalse;
}
*aPopupAnchor = finalRect;
LOG(" anchor is correct %d,%d -> %d x %d", finalRect.x, finalRect.y,
finalRect.width, finalRect.height);
GdkWindow* gdkWindow = GetToplevelGdkWindow();
nsMenuPopupFrame* popupFrame = GetPopupFrame(); if (!gdkWindow || !popupFrame) {
LOG(" can't use move-to-rect due missing gdkWindow or popupFrame"); returnfalse;
}
if (popupFrame->IsConstrainedByLayout()) {
LOG(" can't use move-to-rect, flipped / constrained by layout"); returnfalse;
}
if (!mPopupMoveToRectParams.mAnchorSet) {
mPopupMoveToRectParams = WaylandPopupGetPositionFromLayout();
}
// Update popup layout coordinates from layout by recent popup hierarchy // (calculate correct position according to parent window) // and convert to Gtk coordinates.
DesktopIntRect anchorRect =
ToDesktopPixels(mPopupMoveToRectParams.mAnchorRect); if (!WaylandPopupIsFirst()) {
DesktopIntPoint parent = WaylandGetParentPosition();
LOG(" subtract parent position from anchor [%d, %d]\n", parent.x.value,
parent.y.value);
anchorRect.MoveBy(-parent);
}
*aPopupAnchor = GdkRectangle{anchorRect.x, anchorRect.y, anchorRect.width,
anchorRect.height};
LOG(" anchored to rectangle [%d, %d] -> [%d x %d]", aPopupAnchor->x,
aPopupAnchor->y, aPopupAnchor->width, aPopupAnchor->height);
if (!WaylandPopupAnchorAdjustForParentPopup(aPopupAnchor, aOffset)) {
LOG(" can't use move-to-rect, anchor is not placed inside of parent " "window"); returnfalse;
}
if (mPopupType == PopupType::Tooltip) { // Don't fiddle with tooltips type, just hide it before move-to-rect if (mPopupUseMoveToRect && gtk_widget_is_visible(mShell)) {
HideWaylandPopupWindow(/* aTemporaryHide */ true, /* aRemoveFromPopupList */ false);
}
LOG(" it's tooltip, quit"); return;
}
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1785185#c8 // gtk_window_move() needs GDK_WINDOW_TYPE_HINT_UTILITY popup type. // move-to-rect requires GDK_WINDOW_TYPE_HINT_POPUP_MENU popups type. // We need to set it before map event when popup is hidden. const GdkWindowTypeHint currentType =
gtk_window_get_type_hint(GTK_WINDOW(mShell)); const GdkWindowTypeHint requiredType = mPopupUseMoveToRect
? GDK_WINDOW_TYPE_HINT_POPUP_MENU
: GDK_WINDOW_TYPE_HINT_UTILITY;
if (!mPopupUseMoveToRect && currentType == requiredType) {
LOG(" type matches and we're not forced to hide it, quit."); return;
}
if (currentType != requiredType) {
LOG(" set type %s",
requiredType == GDK_WINDOW_TYPE_HINT_POPUP_MENU ? "MENU" : "UTILITY");
gtk_window_set_type_hint(GTK_WINDOW(mShell), requiredType);
}
}
// Plain popup move on Wayland - simply place popup on given location. // We can't just call gtk_window_move() as it's not effective on visible // popups. void nsWindowWayland::WaylandPopupMovePlain(int aX, int aY) {
LOG("nsWindowWayland::WaylandPopupMovePlain(%d, %d)", aX, aY);
// We can directly move only popups based on wl_subsurface type.
MOZ_DIAGNOSTIC_ASSERT(gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
GDK_WINDOW_TYPE_HINT_UTILITY ||
gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
GDK_WINDOW_TYPE_HINT_TOOLTIP);
gtk_window_move(GTK_WINDOW(mShell), aX, aY);
// gtk_window_move() can trick us. When widget is hidden gtk_window_move() // does not move the widget but sets new widget coordinates when widget // is mapped again. // // If popup used move-to-rect before // (GdkWindow has POSITION_METHOD_MOVE_TO_RECT set), popup will use // move-to-rect again when it's mapped and we'll get bogus move-to-rect // callback. // // gdk_window_move() sets position_method to POSITION_METHOD_MOVE_RESIZE // so we'll use plain move when popup is shown. if (!gtk_widget_get_mapped(mShell)) { if (GdkWindow* window = GetToplevelGdkWindow()) {
gdk_window_move(window, aX, aY);
}
}
}
void nsWindowWayland::WaylandPopupMoveImpl() { // Available as of GTK 3.24+ staticauto sGdkWindowMoveToRect = (void (*)(
GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
if (mPopupUseMoveToRect && !sGdkWindowMoveToRect) {
LOG("can't use move-to-rect due missing gdk_window_move_to_rect()");
mPopupUseMoveToRect = false;
}
LOG("nsWindowWayland::WaylandPopupMove");
LOG(" popup use move to rect %d", mPopupUseMoveToRect);
WaylandPopupPrepareForMove();
if (!mPopupUseMoveToRect) { auto pos = mLastMoveRequest - WaylandGetParentPosition();
WaylandPopupMovePlain(pos.x, pos.y); // Layout already should be aware of our bounds, since we didn't change it // from the widget side for flipping or so. return;
}
// Correct popup position now. It will be updated by gdk_window_move_to_rect() // anyway but we need to set it now to avoid a race condition here.
WaylandPopupRemoveNegativePosition();
// Wayland keeps strong popup window hierarchy. We need to track active // (visible) popup windows and make sure we hide popup on the same level // before we open another one on that level. It means that every open // popup needs to have an unique parent. void nsWindowWayland::UpdateWaylandPopupHierarchy() {
LOG("nsWindowWayland::UpdateWaylandPopupHierarchy\n");
// This popup hasn't been added to popup hierarchy yet so no need to // do any configurations. if (!IsInPopupHierarchy()) {
LOG(" popup isn't in hierarchy\n"); return;
}
// Hide all tooltips without the last one. Tooltip can't be popup parent.
mWaylandToplevel->WaylandPopupHideTooltips();
// See Bug 1709254 / https://gitlab.gnome.org/GNOME/gtk/-/issues/5092 // It's possible that Wayland compositor refuses to show // a popup although Gtk claims it's visible. // We don't know if the popup is shown or not. // To avoid application crash refuse to create any child of such invisible // popup and close any child of it now.
mWaylandToplevel->WaylandPopupCloseOrphanedPopups();
// Check if we have any remote content / overflow window in hierarchy. // We can't attach such widget on top of other popup.
mWaylandToplevel->CloseAllPopupsBeforeRemotePopup();
// Check if your popup hierarchy matches layout hierarchy. // For instance we should not connect hamburger menu on top // of context menu. // Close all popups from different layout chains if possible.
AutoTArray<nsIWidget*, 5> layoutPopupWidgetChain;
GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
// Now we have Popup hierarchy complete. // Find first unchanged (and still open) popup to start with hierarchy // changes.
nsWindowWayland* changedPopup = mWaylandToplevel->mWaylandPopupNext; while (changedPopup) { // Stop when parent of this popup was changed and we need to recalc // popup position. if (changedPopup->mPopupChanged) { break;
} // Stop when this popup is closed. if (changedPopup->mPopupClosed) { break;
}
changedPopup = changedPopup->mWaylandPopupNext;
}
// We don't need to recompute popup positions, quit now. if (!changedPopup) {
LOG(" changed Popup is null, quit.\n"); return;
}
LOG(" first changed popup [%p]\n", (void*)changedPopup);
// Hide parent popups if necessary (there are layout discontinuity) // reposition the popup and show them again.
changedPopup->WaylandPopupHierarchyHideTemporary();
// It's possible that changedPopup was removed from widget hierarchy, // in such case use child popup of the removed one if there's any. if (!changedPopup->IsInPopupHierarchy()) { if (!parentOfchangedPopup || !parentOfchangedPopup->mWaylandPopupNext) {
LOG(" last popup was removed, quit.\n"); return;
}
changedPopup = parentOfchangedPopup->mWaylandPopupNext;
}
nsWindowWayland* popup = changedPopup; while (popup) { constbool useMoveToRect = [&] { if (!StaticPrefs::widget_wayland_use_move_to_rect_AtStartup()) { returnfalse; // Not available.
} if (!popup->mPopupMatchesLayout) { // 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. returnfalse;
} if (!popup->WaylandPopupIsFirst() &&
!popup->mWaylandPopupPrev->WaylandPopupIsFirst() &&
!popup->mWaylandPopupPrev->mPopupUseMoveToRect) { // We can't use move-to-rect if there are more parents of // wl_subsurface popups types. // // It's because wl_subsurface is ignored by xdg_popup // (created by move-to-rect) so our popup scenario: // // toplevel -> xdg_popup(1) -> wl_subsurface(2) -> xdg_popup(3) // // looks for Wayland compositor as: // // toplevel -> xdg_popup(1) -> xdg_popup(3) // // If xdg_popup(1) and xdg_popup(3) are not connected // move-to-rect applied to xdg_popup(3) fails and we get missing popup. returnfalse;
} returntrue;
}();
popup->mPopupUseMoveToRect = useMoveToRect;
LOG(" popup [%p] matches layout [%d] anchored [%d] first popup [%d] use " "move-to-rect %d\n",
popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
popup->WaylandPopupIsFirst(), popup->mPopupUseMoveToRect);
if (popup->mPopupUseMoveToRect && !popup->mPopupMatchesLayout) {
gfxCriticalNote << "Wayland: Positioned popup with missing anchor!";
}
void nsWindowWayland::RemovePopupFromHierarchyList() { // We're already removed from the popup hierarchy if (!IsInPopupHierarchy()) { return;
}
mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext; if (mWaylandPopupNext) {
mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
mWaylandPopupNext->mPopupChanged = true;
}
mWaylandPopupNext = mWaylandPopupPrev = nullptr;
}
// Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates // see https://gitlab.gnome.org/GNOME/gtk/-/issues/4071 // as a workaround just fool around and place the popup temporary to 0,0. bool nsWindowWayland::WaylandPopupRemoveNegativePosition(int* aX, int* aY) { // https://gitlab.gnome.org/GNOME/gtk/-/issues/4071 applies to temporary // windows only
GdkWindow* window = GetToplevelGdkWindow(); if (!window || gdk_window_get_window_type(window) != GDK_WINDOW_TEMP) { returnfalse;
}
int x, y;
gtk_window_get_position(GTK_WINDOW(mShell), &x, &y); bool moveBack = (x < 0 && y < 0); if (moveBack) {
gtk_window_move(GTK_WINDOW(mShell), 0, 0); if (aX) {
*aX = x;
} if (aY) {
*aY = y;
}
}
gdk_window_get_geometry(window, &x, &y, nullptr, nullptr); if (x < 0 && y < 0) {
gdk_window_move(window, 0, 0);
}
return moveBack;
}
void nsWindowWayland::ShowWaylandPopupWindow() {
LOG("nsWindowWayland::ShowWaylandPopupWindow. Expected to see visible.");
MOZ_ASSERT(IsWaylandPopup());
if (!mPopupTrackInHierarchy) {
LOG(" popup is not tracked in popup hierarchy, show it now");
gtk_widget_show(mShell); return;
}
// Popup position was checked before gdk_window_move_to_rect() callback // so just show it. if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
LOG(" active move-to-rect callback, show it as is");
gtk_widget_show(mShell); return;
}
if (gtk_widget_is_visible(mShell)) {
LOG(" is already visible, quit"); return;
}
int x, y; bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
gtk_widget_show(mShell); if (moved) {
LOG(" move back to (%d, %d) and show", x, y);
gtk_window_move(GTK_WINDOW(mShell), x, y);
}
}
void nsWindowWayland::WaylandPopupMarkAsClosed() {
LOG("nsWindowWayland::WaylandPopupMarkAsClosed: [%p]\n", this);
mPopupClosed = true; // If we have any child popup window notify it about // parent switch. if (mWaylandPopupNext) {
mWaylandPopupNext->mPopupChanged = true;
}
}
// Hide all tooltips except the latest one. void nsWindowWayland::WaylandPopupHideTooltips() {
LOG("nsWindowWayland::WaylandPopupHideTooltips");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
void nsWindowWayland::WaylandPopupCloseOrphanedPopups() {
LOG("nsWindowWayland::WaylandPopupCloseOrphanedPopups");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
nsWindowWayland* popup = mWaylandPopupNext; bool dangling = false; while (popup) { if (!dangling && !MOZ_WL_SURFACE(popup->GetMozContainer())->IsVisible()) {
LOG(" popup [%p] is waiting to show, close all child popups", popup);
dangling = true;
} elseif (dangling) {
LOG(" popup [%p] is dangling, hide it", popup);
popup->WaylandPopupMarkAsClosed();
}
popup = popup->mWaylandPopupNext;
}
}
// We can't show popups with remote content or overflow popups // on top of regular ones. // If there's any remote popup opened, close all parent popups of it. void nsWindowWayland::CloseAllPopupsBeforeRemotePopup() {
LOG("nsWindowWayland::CloseAllPopupsBeforeRemotePopup");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
// Don't waste time when there's only one popup opened. if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) { return;
}
// Find the first opened remote content popup
nsWindowWayland* remotePopup = mWaylandPopupNext; while (remotePopup) { if (remotePopup->HasRemoteContent() ||
remotePopup->IsWidgetOverflowWindow()) {
LOG(" remote popup [%p]", remotePopup); break;
}
remotePopup = remotePopup->mWaylandPopupNext;
}
if (!remotePopup) { return;
}
// ...hide opened popups before the remote one.
nsWindowWayland* popup = mWaylandPopupNext; while (popup && popup != remotePopup) {
LOG(" hidding popup [%p]", popup);
popup->WaylandPopupMarkAsClosed();
popup = popup->mWaylandPopupNext;
}
}
// Compare 'this' popup position in Wayland widget hierarchy // (mWaylandPopupPrev/mWaylandPopupNext) with // 'this' popup position in layout hierarchy. // // When aMustMatchParent is true we also request // 'this' parents match, i.e. 'this' has the same parent in // both layout and widget hierarchy. bool nsWindowWayland::IsPopupInLayoutPopupChain(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) { int len = (int)aLayoutWidgetHierarchy->Length(); for (int i = 0; i < len; i++) { if (this == (*aLayoutWidgetHierarchy)[i]) { if (!aMustMatchParent) { returntrue;
}
// Find correct parent popup for 'this' according to widget // hierarchy. That means we need to skip closed popups.
nsWindowWayland* parentPopup = nullptr; if (mWaylandPopupPrev != mWaylandToplevel) {
parentPopup = mWaylandPopupPrev; while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
parentPopup = parentPopup->mWaylandPopupPrev;
}
}
if (i == 0) { // We found 'this' popups as a first popup in layout hierarchy. // It matches layout hierarchy if it's first widget also in // wayland widget hierarchy (i.e. parent is null). return parentPopup == nullptr;
}
// Configure Wayland popup. If true is returned we need to track popup // in popup hierarchy. Otherwise we just show it as is. bool nsWindowWayland::WaylandPopupConfigure() { if (mIsDragPopup) { returnfalse;
}
// Don't track popups without frame
nsMenuPopupFrame* popupFrame = GetPopupFrame(); if (!popupFrame) { returnfalse;
}
// Popup state can be changed, see Bug 1728952. bool permanentStateMatches =
mPopupTrackInHierarchy == !WaylandPopupIsPermanent();
// Popup permanent state (noautohide attribute) can change during popup life. if (mPopupTrackInHierarchyConfigured && permanentStateMatches) { return mPopupTrackInHierarchy;
}
// Configure persistent popup params only once. // WaylandPopupIsAnchored() can give it wrong value after // nsMenuPopupFrame::MoveTo() call which we use in move-to-rect callback // to position popup after wayland position change. if (!mPopupTrackInHierarchyConfigured) {
mPopupAnchored = WaylandPopupIsAnchored();
mPopupContextMenu = WaylandPopupIsContextMenu();
}
// Permanent state changed and popup is mapped. // We need to switch popup type but that's done when popup is mapped // by Gtk so we need to unmap the popup here. // It will be mapped again by gtk_widget_show(). if (!permanentStateMatches && mIsMapped) {
LOG(" permanent state change from %d to %d, unmapping",
mPopupTrackInHierarchy, !WaylandPopupIsPermanent());
gtk_widget_unmap(mShell);
}
mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
LOG(" tracked in hierarchy %d\n", mPopupTrackInHierarchy);
// See gdkwindow-wayland.c and // should_map_as_popup()/should_map_as_subsurface()
GdkWindowTypeHint gtkTypeHint; switch (mPopupType) { case PopupType::Menu: // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default. // We use this type for all menu popups.
gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
LOG(" popup type Menu"); break; case PopupType::Tooltip:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
LOG(" popup type Tooltip"); break; default:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
LOG(" popup type Utility"); break;
}
if (!mPopupTrackInHierarchy) { // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface // by default.
LOG(" not tracked in popup hierarchy, switch to Utility");
gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
}
gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
void nsWindowWayland::AddWindowToPopupHierarchy() {
LOG("nsWindowWayland::AddWindowToPopupHierarchy\n"); if (!GetPopupFrame()) {
LOG(" Window without frame cannot be added as popup!\n"); return;
}
// Check if we're already in the hierarchy if (!IsInPopupHierarchy()) {
mWaylandToplevel =
nsWindowWayland::FromWidget(GetTopLevelWidget())->AsWayland(); if (mWaylandToplevel) {
AppendPopupToHierarchyList(mWaylandToplevel);
}
}
}
// Hide popups which are not in popup chain. void nsWindowWayland::WaylandPopupHierarchyHideByLayout(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
LOG("nsWindowWayland::WaylandPopupHierarchyHideByLayout");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
// Hide all popups which are not in layout popup chain
nsWindowWayland* popup = mWaylandPopupNext; while (popup) { // Don't check closed popups and drag source popups and tooltips. if (!popup->mPopupClosed && popup->mPopupType != PopupType::Tooltip &&
!popup->mSourceDragContext) { if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy, /* aMustMatchParent */ false)) {
LOG(" hidding popup [%p]", popup);
popup->WaylandPopupMarkAsClosed();
}
}
popup = popup->mWaylandPopupNext;
}
}
DesktopIntPoint nsWindowWayland::WaylandGetParentPosition() const {
MOZ_ASSERT(IsPopup()); auto* window = GetEffectiveParent(); if (NS_WARN_IF(!window) || !window->IsPopup()) { return {0, 0};
} // If our parent is a popup, offset to our toplevel bounds (note that in // Wayland there's no global coordinate space, so our "screen" offset is // really relative to the origin of our toplevel).
DesktopIntPoint offset = window->WidgetToScreenOffsetUnscaled();
LOG("nsWindowWayland::WaylandGetParentPosition() [%d, %d]\n", offset.x.value,
offset.y.value); return offset;
}
#ifdef MOZ_LOGGING void nsWindowWayland::LogPopupHierarchy() { if (!LOG_ENABLED()) { return;
}
// When popup is repositioned by widget code, we need to notify // layout about it. It's because we control popup placement // on widget on Wayland so layout may have old popup size/coordinates. void nsWindowWayland::WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize) {
LOG("nsWindowWayland::WaylandPopupPropagateChangesToLayout()");
void nsWindowWayland::NativeMoveResizeWaylandPopupCallback( const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) { // We're getting move-to-rect callback without move-to-rect call. // That indicates a compositor bug. It happens when a window is hidden and // shown again before move-to-rect callback is fired. // It may lead to incorrect popup placement as we may call // gtk_window_move() between hide & show. // See Bug 1777919, 1789581. #if MOZ_LOGGING if (!mWaitingForMoveToRectCallback) {
LOG(" Bogus move-to-rect callback! Expect wrong popup coordinates.");
} #endif
// Popup was moved between move-to-rect call and move-to-rect callback // and the coordinates from move-to-rect callback are outdated. if (movedByLayout || resizedByLayout) {
LOG(" Another move/resize called during waiting for callback\n");
mMovedAfterMoveToRect = false;
mResizedAfterMoveToRect = false; // Fire another round of move/resize to reflect latest request from layout.
NativeMoveResize(movedByLayout, resizedByLayout); return;
}
// With fractional scaling, our devPx->Gdk->devPx conversion might not // perfectly round-trip. Compare gdk rects to check whether size or positions // have changed from what we'd request otherwise, in order to avoid // flickering. constauto currentRect = mClientArea; auto scale = GdkCeiledScaleFactor(); auto IsSubstantiallyDifferent = [=](gint a, gint b) { return std::abs(a - b) > scale;
};
LOG(" orig gdk [%d, %d] -> [%d x %d]", currentRect.x, currentRect.y,
currentRect.width, currentRect.height);
LOG(" new gdk [%d, %d] -> [%d x %d]\n", finalGdkRect.x, finalGdkRect.y,
finalGdkRect.width, finalGdkRect.height);
LOG(" new mClientArea [%d, %d] -> [%d x %d]", newClientArea.x,
newClientArea.y, newClientArea.width, newClientArea.height);
if (!needsSizeUpdate && !needsPositionUpdate) {
LOG(" Size/position is the same, quit."); return;
} if (needsSizeUpdate) { // Wayland compositor changed popup size request from layout. // Set the constraints to use them in nsMenuPopupFrame::SetPopupPosition(). // Beware that gtk_window_resize() requests sizes asynchronously and so // newClientArea might not have the size from the most recent // gtk_window_resize(). if (newClientArea.width < mLastSizeRequest.width) {
mMoveToRectPopupSize.width = newClientArea.width;
} if (newClientArea.height < mLastSizeRequest.height) {
mMoveToRectPopupSize.height = newClientArea.height;
}
LOG(" mMoveToRectPopupSize set to [%d, %d]", mMoveToRectPopupSize.width,
mMoveToRectPopupSize.height);
}
// Check mClientArea size auto scaledSize = ToLayoutDevicePixels(mClientArea); if (mCompositorSession &&
!wr::WindowSizeSanityCheck(scaledSize.width, scaledSize.height)) {
gfxCriticalNoteOnce << "Invalid mClientArea in PopupCallback " << scaledSize
<< " size state " << mSizeMode;
}
WaylandPopupPropagateChangesToLayout(needsPositionUpdate, needsSizeUpdate);
}
// Position the popup directly by gtk_window_move() and try to keep it // on screen by just moving it in scope of it's parent window. // // It's used when we position noautihode popup and we don't use xdg_positioner. // See Bug 1718867 void nsWindowWayland::WaylandPopupSetDirectPosition() { const DesktopIntRect newRect(mLastMoveRequest, mLastSizeRequest);
if (mIsDragPopup) {
gtk_window_move(GTK_WINDOW(mShell), newRect.x, newRect.y);
gtk_window_resize(GTK_WINDOW(mShell), newRect.width, newRect.height); // DND window is placed inside container so we need to make hard size // request to ensure parent container is resized too.
gtk_widget_set_size_request(GTK_WIDGET(mShell), newRect.width,
newRect.height); return;
}
int parentWidth = gdk_window_get_width(gdkWindow); int popupWidth = newRect.width;
int x;
gdk_window_get_position(gdkWindow, &x, nullptr);
auto pos = newRect.TopLeft(); // If popup is bigger than main window just center it. if (popupWidth > parentWidth) {
pos.x = -(parentWidth - popupWidth) / 2 + x;
} else { if (pos.x < x) { // Stick with left window edge if it's placed too left
pos.x = x;
} elseif (pos.x + popupWidth > parentWidth + x) { // Stick with right window edge otherwise
pos.x = parentWidth + x - popupWidth;
}
}
LOG(" set position [%d, %d]\n", pos.x.value, pos.y.value);
gtk_window_move(GTK_WINDOW(mShell), pos.x, pos.y);
LOG(" set size [%d, %d]\n", newRect.width, newRect.height);
gtk_window_resize(GTK_WINDOW(mShell), newRect.width, newRect.height);
// Compositor may be confused by windows with width/height = 0 // and positioning such windows leads to Bug 1555866. if (!AreBoundsSane()) {
LOG(" Bounds are not sane (width: %d height: %d)\n",
mLastSizeRequest.width, mLastSizeRequest.height); return;
}
// It's safe to expect the popup position is handled onwards. if (aMove) {
mWaylandApplyPopupPositionBeforeShow = false;
}
MOZ_ASSERT(mClientMargin.IsAllZero());
if (mWaitingForMoveToRectCallback) {
LOG(" waiting for move to rect, scheduling"); // mClientArea position must not be overwritten before it is applied. // OnShellConfigureEvent() will not set mClientArea to an old position for // GTK_WINDOW_POPUP.
MOZ_ASSERT(gtk_window_get_window_type(GTK_WINDOW(mShell)) ==
GTK_WINDOW_POPUP);
mMovedAfterMoveToRect = aMove;
mResizedAfterMoveToRect = aResize; return;
}
bool trackedInHierarchy = WaylandPopupConfigure(); // Read popup position from layout if it was moved. // This position is used by move-to-rect method as we need anchor and other // info to place popup correctly. // We need WaylandPopupConfigure() to be called before to have all needed // popup info in place (mainly the anchored flag). if (aMove) {
mPopupMoveToRectParams = WaylandPopupGetPositionFromLayout();
} if (!trackedInHierarchy) {
WaylandPopupSetDirectPosition(); return;
}
if (aResize) {
LOG(" set size [%d, %d]\n", rect.width, rect.height);
gtk_window_resize(GTK_WINDOW(mShell), rect.width, rect.height);
}
if (!aMove && WaylandPopupFitsToplevelWindow()) { // Popup position has not been changed and its position/size fits // parent window so no need to reposition the window.
LOG(" fits parent window size, just resize\n"); return;
}
// Mark popup as changed as we're updating position/size.
mPopupChanged = true;
// Ensure that KeymapWrapper is created on Wayland as we need it for // keyboard focus tracking.
KeymapWrapper::EnsureInstance();
mSurfaceProvider.Initialize(this);
// Initialize the window specific VsyncSource early in order to avoid races // with BrowserParent::UpdateVsyncParentVsyncDispatcher(). // Only use for toplevel windows for now, see bug 1619246.
if (StaticPrefs::widget_wayland_vsync_enabled_AtStartup() &&
IsTopLevelWidget()) {
LOG_VSYNC(" create WaylandVsyncSource");
mWaylandVsyncSource = new WaylandVsyncSource(this);
mWaylandVsyncSource->Init();
mWaylandVsyncDispatcher = new VsyncDispatcher(mWaylandVsyncSource);
mWaylandVsyncSource->EnableVSyncSource();
}
if (WaylandPipEnabled()) { // This avoids GTK from managing our top level surface role. // We'll handle it manually in OnMap().
gdk_wayland_window_set_use_custom_surface(GetToplevelGdkWindow());
}
// Shut down our local vsync source // Also drops reference to nsWindow::mSurface. if (mWaylandVsyncSource) {
mWaylandVsyncSource->Shutdown();
mWaylandVsyncSource = nullptr;
}
mWaylandVsyncDispatcher = nullptr;
UnlockNativePointer();
}
void nsWindowWayland::NativeShow(bool aAction) { if (aAction) { // unset our flag now that our window has been shown
mNeedsShow = true;
if (mWaitingToSessionRestore) {
LOG("nsWindowWayland::NativeShow() waiting to session restore, quit."); if (nsAppShell::UpdateAndGetSessionState() == eSessionRestoring) { return;
}
mWaitingToSessionRestore = false;
NS_WARNING("Wayland session restore failed!");
}
auto removeShow = MakeScopeExit([&] { mNeedsShow = false; });
LOG("nsWindowWayland::NativeShow show\n");
if (IsWaylandPopup()) {
mPopupClosed = false; constbool trackedInHierarchy = WaylandPopupConfigure(); if (trackedInHierarchy) {
AddWindowToPopupHierarchy();
} if (mWaylandApplyPopupPositionBeforeShow) { // NOTE(emilio): This will end up calling UpdateWaylandPopupHierarchy if // needed.
NativeMoveResize(/* move */ true, /* resize */ false);
} elseif (trackedInHierarchy) {
UpdateWaylandPopupHierarchy();
} if (mPopupClosed) { return;
}
ShowWaylandPopupWindow();
} else {
ShowWaylandToplevelWindow();
}
SetUserTimeAndStartupTokenForActivatedWindow();
auto token = std::move(mWindowActivationTokenFromEnv); if (!token.IsEmpty()) {
FocusWaylandWindow(token.get());
} elseif (!IsPopup()) {
TransferFocusTo();
}
} else {
LOG("nsWindow::NativeShow hide\n"); if (IsWaylandPopup()) { // We can't close tracked popups directly as they may have visible // child popups. Just mark is as closed and let // UpdateWaylandPopupHierarchy() do the job. if (IsInPopupHierarchy()) {
WaylandPopupMarkAsClosed();
UpdateWaylandPopupHierarchy();
} else { // Close untracked popups directly.
HideWaylandPopupWindow(/* aTemporaryHide */ false, /* aRemoveFromPopupList */ true);
}
} else {
HideWaylandToplevelWindow();
}
}
}
// Apply workaround for Mutter compositor bug (mzbz#1777269). // // When we open a popup window (tooltip for instance) attached to // GDK_WINDOW_TYPE_HINT_UTILITY parent popup, Mutter compositor sends bogus // leave/enter events to the GDK_WINDOW_TYPE_HINT_UTILITY popup. // That leads to immediate tooltip close. As a workaround ignore these // bogus events. // // We need to check two affected window types: // // - toplevel window with at least two child popups where the first one is // GDK_WINDOW_TYPE_HINT_UTILITY. // - GDK_WINDOW_TYPE_HINT_UTILITY popup with a child popup // // We need to mask two bogus leave/enter sequences: // 1) Leave (popup) -> Enter (toplevel) // 2) Leave (toplevel) -> Enter (popup) // // TODO: persistent (non-tracked) popups with tooltip/child popups? // bool nsWindowWayland::ApplyEnterLeaveMutterWorkaround() { // Leave (toplevel) case if (mWindowType == WindowType::TopLevel && mWaylandPopupNext &&
mWaylandPopupNext->mWaylandPopupNext &&
gtk_window_get_type_hint(GTK_WINDOW(mWaylandPopupNext->GetGtkWidget())) ==
GDK_WINDOW_TYPE_HINT_UTILITY) {
LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave toplevel"); returntrue;
} // Leave (popup) case if (IsWaylandPopup() && mWaylandPopupNext &&
gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
GDK_WINDOW_TYPE_HINT_UTILITY) {
LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave popup"); returntrue;
} returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.