From 3278a43751d266cce1402f756a9ae1dc31aa1c3e Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Sun, 3 May 2026 22:09:17 +0200
Subject: [PATCH] gh-13294: Fix macos crash with native popovers (gh-13546)
---
.../firefox/native_macos_popovers.patch | 146 ++++++++++--------
src/external-patches/manifest.json | 14 +-
src/zen/tabs/zen-tabs/vertical-tabs.css | 4 +-
3 files changed, 93 insertions(+), 71 deletions(-)
diff --git a/src/external-patches/firefox/native_macos_popovers.patch b/src/external-patches/firefox/native_macos_popovers.patch
index 8c23d2d7d..9029cc846 100644
--- a/src/external-patches/firefox/native_macos_popovers.patch
+++ b/src/external-patches/firefox/native_macos_popovers.patch
@@ -1,7 +1,7 @@
diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml
--- a/browser/base/content/main-popupset.inc.xhtml
+++ b/browser/base/content/main-popupset.inc.xhtml
-@@ -196,10 +196,11 @@
+@@ -192,10 +192,11 @@
-@@ -207,10 +208,11 @@
+@@ -203,10 +204,11 @@
-@@ -560,10 +562,11 @@
+@@ -610,10 +612,11 @@
type="arrow"
orient="vertical"
noautofocus="true"
@@ -40,7 +40,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
diff --git a/browser/components/asrouter/modules/FeatureCallout.sys.mjs b/browser/components/asrouter/modules/FeatureCallout.sys.mjs
--- a/browser/components/asrouter/modules/FeatureCallout.sys.mjs
+++ b/browser/components/asrouter/modules/FeatureCallout.sys.mjs
-@@ -1046,10 +1046,11 @@
+@@ -1054,10 +1054,11 @@
noautofocus="true"
flip="slide"
type="arrow"
@@ -70,22 +70,22 @@ diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/brows
diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
--- a/dom/xul/XULPopupElement.cpp
+++ b/dom/xul/XULPopupElement.cpp
-@@ -82,10 +82,14 @@
+@@ -80,10 +80,14 @@
+ }
void XULPopupElement::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos,
bool aIsContextMenu,
Event* aTriggerEvent) {
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ // TODO(cheff): At nsCocoaWindow::Show but we check for ShouldShowAsNSPopover
+ // to determine whether to use a native popover or not. This should sort of
+ // "replicate" that logic here, but it's a bit of a hacky way.
+ SetAttr(kNameSpaceID_None, nsGkAtoms::nonnativepopover, u"true"_ns, true);
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
pm->ShowPopupAtScreen(this, aXPos, aYPos, aIsContextMenu, aTriggerEvent);
}
}
-
-@@ -94,10 +98,14 @@
+@@ -93,10 +97,14 @@
int32_t aWidth, int32_t aHeight,
bool aIsContextMenu,
bool aAttributesOverride,
@@ -103,7 +103,7 @@ diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
-@@ -530,18 +530,10 @@
+@@ -528,18 +528,10 @@
// Move the popup to the position specified in its |left| and |top|
// attributes.
@@ -122,7 +122,7 @@ diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
public:
/**
* Return whether the popup direction should be RTL.
-@@ -550,10 +542,18 @@
+@@ -548,10 +540,18 @@
*
* Return whether the popup direction should be RTL.
*/
@@ -144,7 +144,7 @@ diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
-@@ -19477,10 +19477,19 @@
+@@ -19672,10 +19672,19 @@
value: true
mirror: always
@@ -167,7 +167,7 @@ diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/Sta
diff --git a/toolkit/themes/shared/global-shared.css b/toolkit/themes/shared/global-shared.css
--- a/toolkit/themes/shared/global-shared.css
+++ b/toolkit/themes/shared/global-shared.css
-@@ -85,10 +85,22 @@
+@@ -72,10 +72,22 @@
--menuitem-border-radius: var(--arrowpanel-menuitem-border-radius);
--menuitem-padding: var(--arrowpanel-menuitem-padding);
--menuitem-margin: var(--arrowpanel-menuitem-margin);
@@ -251,7 +251,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
-@@ -3,10 +3,13 @@
+@@ -4,10 +4,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCocoaWindow.h"
@@ -265,7 +265,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
#include "nsIDOMWindowUtils.h"
#include "nsILocalFileMac.h"
#include "CocoaCompositorWidget.h"
-@@ -4973,10 +4976,15 @@
+@@ -5031,10 +5034,15 @@
if (mWindowType == WindowType::Popup) {
SetPopupWindowLevel();
mWindow.backgroundColor = NSColor.clearColor;
@@ -281,7 +281,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// the active space. Does not work with multiple displays. See
// NeedsRecreateToReshow() for multi-display with multi-space workaround.
mWindow.collectionBehavior = mWindow.collectionBehavior |
-@@ -5178,10 +5186,57 @@
+@@ -5236,10 +5244,57 @@
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
@@ -339,54 +339,58 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (!mWindow) {
-@@ -5242,10 +5297,54 @@
+@@ -5300,10 +5355,58 @@
mWindow.contentView.needsDisplay = YES;
if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
[mWindow orderFront:nil];
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
-+ if (ShouldShowAsNSPopover()) {
++ if (ShouldShowAsNSPopover() && nativeParentWindow) {
+ nsMenuPopupFrame* popupFrame = GetPopupFrame();
-+ if (nativeParentWindow) {
-+ NSRectEdge preferredEdge =
-+ AlignmentPositionToNSRectEdge(popupFrame->GetAlignmentPosition());
-+ nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
-+ nsPresContext* pc = popupFrame->PresContext();
-+ int32_t appUnitsPerDevPixel = pc->AppUnitsPerDevPixel();
-+ mozilla::DesktopToLayoutDeviceScale desktopToLayoutScale =
-+ pc->DeviceContext()->GetDesktopToDeviceScale();
-+ mozilla::DesktopIntRect popupAnchorRectScaled =
-+ mozilla::DesktopIntRect::RoundOut(
-+ mozilla::LayoutDeviceRect::FromAppUnits(anchorRectAppUnits,
-+ appUnitsPerDevPixel) /
-+ desktopToLayoutScale);
-+ // Taking the now correctly scaled anchor rect and turning it into a
-+ // gecko rect this accounts for the y-axis inversion that cocoa needs,
-+ // as the origin is in the bottom left. This rect is in screen space
-+ NSRect cocoaScreenRect =
-+ nsCocoaUtils::GeckoRectToCocoaRect(popupAnchorRectScaled);
-+ // We take the screen space rect and convert it to window space
-+ // coordinates, as NSPopover requires the coordinates to be in view
-+ // space and inside the view. If the coordinates are outside our view,
-+ // the popover will fail silently
-+ NSRect windowRect =
-+ [nativeParentWindow convertRectFromScreen:cocoaScreenRect];
-+ NSView* parentView = [nativeParentWindow contentView];
-+ // We take the window space rect and convert it to view space for the
-+ // specific parent view
-+ NSRect positioningRect = [parentView convertRect:windowRect
-+ fromView:nil];
-+ BOOL shouldHideAnchor = NO;
-+ auto& element = popupFrame->PopupElement();
-+ if (element.GetBoolAttr(nsGkAtoms::hidepopovertail)) {
-+ shouldHideAnchor = YES;
-+ }
-+ [(PopupWindow*)mWindow showPopoverRelativeToRect:positioningRect
-+ ofView:parentView
-+ preferredEdge:preferredEdge
-+ hiddenAnchor:shouldHideAnchor];
-+ SyncPopoverBounds([(PopupWindow*)mWindow popover], popupFrame);
++ NSRectEdge preferredEdge =
++ AlignmentPositionToNSRectEdge(popupFrame->GetAlignmentPosition());
++ nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
++ nsPresContext* pc = popupFrame->PresContext();
++ int32_t appUnitsPerDevPixel = pc->AppUnitsPerDevPixel();
++ mozilla::DesktopToLayoutDeviceScale desktopToLayoutScale =
++ pc->DeviceContext()->GetDesktopToDeviceScale();
++ mozilla::DesktopIntRect popupAnchorRectScaled =
++ mozilla::DesktopIntRect::RoundOut(
++ mozilla::LayoutDeviceRect::FromAppUnits(anchorRectAppUnits,
++ appUnitsPerDevPixel) /
++ desktopToLayoutScale);
++ // Taking the now correctly scaled anchor rect and turning it into a
++ // gecko rect this accounts for the y-axis inversion that cocoa needs,
++ // as the origin is in the bottom left. This rect is in screen space
++ NSRect cocoaScreenRect =
++ nsCocoaUtils::GeckoRectToCocoaRect(popupAnchorRectScaled);
++ // We take the screen space rect and convert it to window space
++ // coordinates, as NSPopover requires the coordinates to be in view
++ // space and inside the view. If the coordinates are outside our view,
++ // the popover will fail silently
++ NSRect windowRect =
++ [nativeParentWindow convertRectFromScreen:cocoaScreenRect];
++ NSView* parentView = [nativeParentWindow contentView];
++ // We take the window space rect and convert it to view space for the
++ // specific parent view
++ NSRect positioningRect = [parentView convertRect:windowRect
++ fromView:nil];
++ BOOL shouldHideAnchor = NO;
++ auto& element = popupFrame->PopupElement();
++ if (element.GetBoolAttr(nsGkAtoms::hidepopovertail)) {
++ shouldHideAnchor = YES;
+ }
++ [(PopupWindow*)mWindow showPopoverRelativeToRect:positioningRect
++ ofView:parentView
++ preferredEdge:preferredEdge
++ hiddenAnchor:shouldHideAnchor];
++ SyncPopoverBounds([(PopupWindow*)mWindow popover], popupFrame);
++ if (mPopupLevel == PopupLevel::Parent) {
++ [nativeParentWindow addChildWindow:mWindow ordered:NSWindowAbove];
++ }
++
++ // Exit early here since the popover is now shown.
++ mWindow.isBeingShown = NO;
+ return;
+ }
// If our popup window is a non-native context menu, tell the OS (and
@@ -394,12 +398,13 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// close other programs' context menus when ours open.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*)mWindow isContextMenu]) {
-@@ -5316,10 +5415,15 @@
+@@ -5373,11 +5476,15 @@
+ // unhook it here before ordering it out. When you order out the child
// of a window it hides the parent window.
if (mWindowType == WindowType::Popup && nativeParentWindow) {
[nativeParentWindow removeChildWindow:mWindow];
}
-
+-
+ // Handle NSPopover hiding or traditional window hiding
+ if ([mWindow isKindOfClass:[PopupWindow class]] &&
+ [(PopupWindow*)mWindow usePopover]) {
@@ -410,7 +415,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// other programs) that a menu has closed.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*)mWindow isContextMenu]) {
-@@ -5366,10 +5470,28 @@
+@@ -5424,10 +5531,28 @@
return false;
}
return nsIWidget::ShouldUseOffMainThreadCompositing();
@@ -439,12 +444,12 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
return mWindow.isOpaque ? TransparencyMode::Opaque
: TransparencyMode::Transparent;
-@@ -6328,10 +6450,20 @@
+@@ -6378,10 +6503,19 @@
+
// We ignore aRepaint -- we have to call display:YES, otherwise the
// title bar doesn't immediately get repainted and is displayed in
// the wrong place, leading to a visual jump.
[mWindow setFrame:newFrame display:YES];
-
+ if (ShouldUseNSPopover() && [(PopupWindow*)mWindow usePopover]) {
+ [(PopupWindow*)mWindow updatePopoverContent];
+ // A popover won't resize by setting the frame
@@ -454,18 +459,18 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+ [[(PopupWindow*)mWindow popover] setContentSize:contentSize];
+ SyncPopoverBounds([(PopupWindow*)mWindow popover], GetPopupFrame());
+ }
-+
+
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
void nsCocoaWindow::Resize(const DesktopRect& aRect, bool aRepaint) {
- DoResize(aRect.x, aRect.y, aRect.width, aRect.height, aRepaint, false);
-@@ -8314,17 +8446,26 @@
+@@ -8393,18 +8527,31 @@
+ backing:bufferingType
defer:deferCreation];
if (!self) {
return nil;
}
-
+-
+ mPopover = nil;
+ mPopoverViewController = nil;
+ mUsePopover = NO;
@@ -477,6 +482,11 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
}
+- (void)dealloc {
++ if (mPopover) {
++ ChildViewMouseTracker::OnDestroyWindow(
++ mPopover.contentViewController.view.window);
++ }
++
+ [mPopover release];
+ [mPopoverViewController release];
+ [super dealloc];
@@ -487,7 +497,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// Return 0 in order to match what the system does for sheet windows and
// _NSPopoverWindows.
- (CGFloat)_backdropBleedAmount {
-@@ -8378,10 +8519,122 @@
+@@ -8460,10 +8607,122 @@
- (void)setIsContextMenu:(BOOL)flag {
mIsContextMenu = flag;
@@ -613,7 +623,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
-@@ -843,10 +843,15 @@
+@@ -829,10 +829,15 @@
virtual void SuppressAnimation(bool aSuppress) {}
/** Sets windows-specific mica backdrop on this widget. */
@@ -644,7 +654,7 @@ diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
Atom("highest", "highest"),
Atom("horizontal", "horizontal"),
Atom("hover", "hover"),
-@@ -757,10 +758,11 @@
+@@ -759,10 +760,11 @@
Atom("nohref", "nohref"),
Atom("noinitialselection", "noinitialselection"),
Atom("nomodule", "nomodule"),
diff --git a/src/external-patches/manifest.json b/src/external-patches/manifest.json
index ea0199ba6..671ab5007 100644
--- a/src/external-patches/manifest.json
+++ b/src/external-patches/manifest.json
@@ -10,7 +10,19 @@
// Specifically trying to target FeatureCallout.sys.mjs's change.
// IMPORTANT: Make sure Feature callouts STILL use native popopvers when
// syncing from upstream, as this is a critical part of the patch.
- "+ nonnativepopover=\"true\"": "+ "
+ "+ nonnativepopover=\"true\"": "+ ",
+
+ // Fix conflicts with upstream changes.
+ "--menuitem-border-radius: var(--panel-menuitem-border-radius)": "--menuitem-border-radius: var(--arrowpanel-menuitem-border-radius)",
+ "--menuitem-padding: var(--panel-menuitem-padding)": "--menuitem-padding: var(--arrowpanel-menuitem-padding)",
+ "--menuitem-margin: var(--panel-menuitem-margin)": "--menuitem-margin: var(--arrowpanel-menuitem-margin)",
+
+ " \n #include \"nsCocoaWindow.h\"\n \n #include \"nsISupportsPrimitives.h\"\n #include \"nsArrayUtils.h\"":
+ " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n \n #include \"nsCocoaWindow.h\"\n \n #include \"nsISupportsPrimitives.h\"\n #include \"nsArrayUtils.h\"",
+ " #include \"nsISupportsPrimitives.h\"\n": "",
+
+ " Atom(\"nonnative\", \"nonnative\"),\n": "",
+ "Atom(\"noscript\", \"noscript\"),": "Atom(\"noscript\", \"noscript\"),\n Atom(\"noshade\", \"noshade\"),"
}
},
{
diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css
index b0fdebe7a..2052aaa5e 100644
--- a/src/zen/tabs/zen-tabs/vertical-tabs.css
+++ b/src/zen/tabs/zen-tabs/vertical-tabs.css
@@ -318,7 +318,7 @@
&,
& .tab-content > image {
transition:
- scale 0.2s ease,
+ scale 0.1s ease,
var(--zen-tabbox-element-indent-transition);
}
}
@@ -1077,7 +1077,7 @@
#tabs-newtab-button {
max-height: var(--tab-min-height);
display: flex !important;
- transition: scale 0.2s ease;
+ transition: scale 0.1s ease;
#tabbrowser-tabs[movingtab] & {
transition: transform 0.1s ease;
}