From 53d1ba45f65819992e4a4b2e4e3cc8e4f6d57fce Mon Sep 17 00:00:00 2001 From: Lukas <134181853+bo2themax@users.noreply.github.com> Date: Tue, 24 Mar 2026 00:48:05 +0100 Subject: [PATCH] gh-12782: Fix app menu items cut off on small screens (macOS) (gh-12832) Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com> --- src/zen/common/modules/ZenUIManager.mjs | 24 ++++++++ src/zen/tests/moz.build | 1 + src/zen/tests/popover/browser.toml | 12 ++++ .../browser_popover_height_constraint.js | 59 +++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 src/zen/tests/popover/browser.toml create mode 100644 src/zen/tests/popover/browser_popover_height_constraint.js diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index a2045ba05..84526694b 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -331,7 +331,31 @@ window.gZenUIManager = { this._popupTrackingElements.remove(element); }, + // On macOS, the app menu panel is displayed as a native NSPopover which + // silently clips content beyond the screen without informing Firefox's + // layout engine. This makes bottom menu items unreachable by scrolling. + // Setting max-height based on available screen space lets Firefox's layout + // handle the constraint, enabling proper overflow scrolling. + // See gh-12782 + _constrainNativePopoverHeight(panel) { + if (panel.id !== "appMenu-popup") { + return; + } + // NSPopover adds 13px of chrome on all sides (26px vertical total), + // measured via Accessibility Inspector on macOS 26 (Tahoe). + // Previous macOS versions have similar or smaller values, so this is a + // conservative upper bound. + const popoverChrome = 26; + panel.style.maxHeight = `${window.screen.availHeight - popoverChrome}px`; + }, + onPopupShowing(showEvent) { + if ( + AppConstants.platform === "macosx" && + Services.prefs.getBoolPref("widget.macos.native-context-menus", false) + ) { + this._constrainNativePopoverHeight(showEvent.target); + } for (const el of this._popupTrackingElements) { // target may be inside a shadow root, not directly under the element // we also ignore menus inside panels diff --git a/src/zen/tests/moz.build b/src/zen/tests/moz.build index 17ebbab07..57e3d564a 100644 --- a/src/zen/tests/moz.build +++ b/src/zen/tests/moz.build @@ -9,6 +9,7 @@ BROWSER_CHROME_MANIFESTS += [ "glance/browser.toml", "live-folders/browser.toml", "pinned/browser.toml", + "popover/browser.toml", "spaces/browser.toml", "split_view/browser.toml", "tabs/browser.toml", diff --git a/src/zen/tests/popover/browser.toml b/src/zen/tests/popover/browser.toml new file mode 100644 index 000000000..756cc7fa3 --- /dev/null +++ b/src/zen/tests/popover/browser.toml @@ -0,0 +1,12 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +prefs = ["zen.view.mac.show-three-dot-menu=true"] + +["browser_popover_height_constraint.js"] +run-if = [ + "os == 'mac'", # gh-12782 +] +prefs = ["widget.macos.native-context-menus=true"] diff --git a/src/zen/tests/popover/browser_popover_height_constraint.js b/src/zen/tests/popover/browser_popover_height_constraint.js new file mode 100644 index 000000000..1dd93d334 --- /dev/null +++ b/src/zen/tests/popover/browser_popover_height_constraint.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the app menu panel respects available screen height when +// constrained. Without the fix, the panel ignores the available screen +// height and lets NSPopover silently clip the content. +// See gh-12782 + +add_task(async function test_appmenu_respects_screen_constraint() { + const panel = document.getElementById("appMenu-popup"); + ok(panel, "appMenu-popup panel should exist"); + + // Mock a very small screen + const realAvailHeight = window.screen.availHeight; + const realAvailTop = window.screen.availTop; + Object.defineProperty(window.screen, "availHeight", { + value: 400, + configurable: true, + }); + Object.defineProperty(window.screen, "availTop", { + value: 0, + configurable: true, + }); + + try { + const popupShown = BrowserTestUtils.waitForEvent(panel, "popupshown"); + PanelUI.toggle({ type: "command" }); + await popupShown; + await new Promise(requestAnimationFrame); + + const scrollBody = panel.querySelector(".panel-subview-body"); + ok(scrollBody, "Panel should have a scrollable body"); + + // With the fix, the panel content should be constrained to fit + // within the mocked screen height. Without the fix, the body + // will be sized to the real screen height (~639px), ignoring + // the available screen constraint. + ok( + scrollBody.clientHeight < 450, + `Scroll body height (${scrollBody.clientHeight}) should be constrained ` + + `to fit within available screen height (400), not the real screen` + ); + + const popupHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden"); + PanelUI.toggle({ type: "command" }); + await popupHidden; + } finally { + Object.defineProperty(window.screen, "availHeight", { + value: realAvailHeight, + configurable: true, + }); + Object.defineProperty(window.screen, "availTop", { + value: realAvailTop, + configurable: true, + }); + } +});