Bug 1705842 - Allow opening empty menus, by putting a placeholder item in place. r=harry

This is exercised by one of the subtests of
browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js :

The menu is opened while it's empty, and then the content items are added
asynchronously while the menu is open.

If we try to open an NSMenu with zero NSMenuItems, macOS immediately closes the
menu after opening it. With a placeholder item it stays open as expected.

Differential Revision: https://phabricator.services.mozilla.com/D112458
This commit is contained in:
Markus Stange 2021-04-19 23:08:36 +00:00
parent 01f82d18dd
commit a29fcf0959
2 changed files with 38 additions and 6 deletions

View File

@ -203,6 +203,12 @@ class nsMenuX final : public nsMenuParentX,
// popuphiding/popuphidden events.
void FlushMenuClosedRunnable();
// Make sure the NSMenu contains at least one item, even if mVisibleItemsCount is zero.
// Otherwise it won't open.
void InsertPlaceholderIfNeeded();
// Remove the placeholder before adding an item to mNativeNSMenu.
void RemovePlaceholderIfPresent();
nsCOMPtr<nsIContent> mContent; // XUL <menu> or <menupopup>
// Contains nsMenuX and nsMenuItemX objects

View File

@ -97,12 +97,7 @@ nsMenuX::nsMenuX(nsMenuParentX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsI
NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one");
mMenuGroupOwner->RegisterForContentChanges(mContent, this);
if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent)) {
mVisible = false;
}
if (mContent->GetChildCount() == 0) {
mVisible = false;
}
mVisible = !nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
NSString* newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString
@ -214,6 +209,7 @@ void nsMenuX::AddMenuChild(MenuChild&& aChild) {
[](const RefPtr<nsMenuItemX>& aMenuItem) { return aMenuItem->NativeNSMenuItem(); });
if (isVisible) {
RemovePlaceholderIfPresent();
[mNativeMenu addItem:nativeItem];
++mVisibleItemsCount;
}
@ -690,10 +686,38 @@ void nsMenuX::RebuildMenu() {
}
} // for each menu item
InsertPlaceholderIfNeeded();
gConstructingMenu = false;
mNeedsRebuild = false;
}
void nsMenuX::InsertPlaceholderIfNeeded() {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if ([mNativeMenu numberOfItems] == 0) {
MOZ_RELEASE_ASSERT(mVisibleItemsCount == 0);
NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
item.enabled = NO;
item.view = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 150, 1)] autorelease];
[mNativeMenu addItem:item];
[item release];
}
NS_OBJC_END_TRY_ABORT_BLOCK;
}
void nsMenuX::RemovePlaceholderIfPresent() {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (mVisibleItemsCount == 0 && [mNativeMenu numberOfItems] == 1) {
// Remove the placeholder.
[mNativeMenu removeItemAtIndex:0];
}
NS_OBJC_END_TRY_ABORT_BLOCK;
}
void nsMenuX::SetRebuild(bool aNeedsRebuild) {
if (!gConstructingMenu) {
mNeedsRebuild = aNeedsRebuild;
@ -975,6 +999,7 @@ void nsMenuX::MenuChildChangedVisibility(const MenuChild& aChild, bool aIsVisibl
if (aIsVisible) {
MOZ_RELEASE_ASSERT(!nativeItem.menu,
"The native item should not be in a menu while it is hidden");
RemovePlaceholderIfPresent();
NSInteger insertionPoint = CalculateNativeInsertionPoint(aChild);
[mNativeMenu insertItem:nativeItem atIndex:insertionPoint];
mVisibleItemsCount++;
@ -983,6 +1008,7 @@ void nsMenuX::MenuChildChangedVisibility(const MenuChild& aChild, bool aIsVisibl
"The native item should be in this menu while it is visible");
[mNativeMenu removeItem:nativeItem];
mVisibleItemsCount--;
InsertPlaceholderIfNeeded();
}
NS_OBJC_END_TRY_ABORT_BLOCK;