commit 63275687e99f6b31ebb0fe64a713f17e8a98a62f
parent 65007e8566b88457a96096561eb59bda4cec3c46
Author: Markus Stange <mstange.moz@gmail.com>
Date: Tue, 21 Oct 2025 21:55:01 +0000
Bug 1995621 - Enforce a maximum depth for native menu nesting on macOS. r=mac-reviewers,spohl
Differential Revision: https://phabricator.services.mozilla.com/D269452
Diffstat:
4 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/widget/cocoa/nsMenuBarX.h b/widget/cocoa/nsMenuBarX.h
@@ -118,6 +118,7 @@ class nsMenuBarX : public nsMenuParentX,
// nsMenuParentX
void MenuChildChangedVisibility(const MenuChild& aChild,
bool aIsVisible) override;
+ size_t NestingDepth() override { return 0; }
protected:
virtual ~nsMenuBarX();
diff --git a/widget/cocoa/nsMenuParentX.h b/widget/cocoa/nsMenuParentX.h
@@ -18,6 +18,9 @@ class nsMenuParentX {
public:
using MenuChild = mozilla::Variant<RefPtr<nsMenuX>, RefPtr<nsMenuItemX>>;
+ // The nesting depth; 0 for the root.
+ virtual size_t NestingDepth() = 0;
+
// XXXmstange double-check that this is still needed
virtual nsMenuBarX* AsMenuBar() { return nullptr; }
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h
@@ -175,6 +175,7 @@ class nsMenuX final : public nsMenuParentX,
// nsMenuParentX
void MenuChildChangedVisibility(const MenuChild& aChild,
bool aIsVisible) override;
+ size_t NestingDepth() override { return mNestingDepth; }
void Dump(uint32_t aIndent) const;
@@ -292,6 +293,8 @@ class nsMenuX final : public nsMenuParentX,
// items.
mozilla::Maybe<uint32_t> mHighlightedItemIndex;
+ size_t mNestingDepth = 0;
+
bool mIsEnabled = true;
bool mNeedsRebuild = true;
diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm
@@ -46,6 +46,11 @@ using namespace mozilla::dom;
static bool gConstructingMenu = false;
static bool gMenuMethodsSwizzled = false;
+// Protect against really deep menu nestings, for example from recursive
+// bookmark folders. This avoids hangs when the system enumerates the entire
+// menu tree.
+static const size_t kMaxMenuNestingDepth = 20;
+
int32_t nsMenuX::sIndexingMenuLevel = 0;
// TODO: It is unclear whether this is still needed.
@@ -86,7 +91,10 @@ static void SwizzleDynamicIndexingMethods() {
nsMenuX::nsMenuX(nsMenuParentX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner,
nsIContent* aContent)
- : mContent(aContent), mParent(aParent), mMenuGroupOwner(aMenuGroupOwner) {
+ : mContent(aContent),
+ mParent(aParent),
+ mMenuGroupOwner(aMenuGroupOwner),
+ mNestingDepth(aParent ? aParent->NestingDepth() + 1 : 0) {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
MOZ_COUNT_CTOR(nsMenuX);
@@ -121,6 +129,12 @@ nsMenuX::nsMenuX(nsMenuParentX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner,
keyEquivalent:@""];
mNativeMenuItem.submenu = mNativeMenu;
+ if (mNestingDepth > kMaxMenuNestingDepth) {
+ // If we're nested too deep, turn this item into a regular item without a
+ // submenu.
+ mNativeMenuItem.submenu = nil;
+ }
+
SetEnabled(!mContent->IsElement() ||
!mContent->AsElement()->AttrValueIs(
kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true,