fix(reland): allow disabling all NSMenuItems (#48830)

* fix: allow disabling all `NSMenuItems` (#48598)

fix: allow disabling all NSMenuItems

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* fix: add guard for type

Co-authored-by: George Xu <george.xu@slack-corp.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: George Xu <george.xu@slack-corp.com>
This commit is contained in:
trop[bot] 2025-11-07 10:36:49 +01:00 committed by GitHub
commit 3fb81955bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 88 additions and 50 deletions

View file

@ -90,6 +90,7 @@ bool MenuHasVisibleItems(const electron::ElectronMenuModel* model) {
// "(empty)" into the submenu. Matches Windows behavior.
NSMenu* MakeEmptySubmenu() {
NSMenu* submenu = [[NSMenu alloc] initWithTitle:@""];
submenu.autoenablesItems = NO;
NSString* empty_menu_title =
l10n_util::GetNSString(IDS_APP_MENU_EMPTY_SUBMENU);
@ -231,6 +232,9 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
// be invoked recursively.
- (NSMenu*)menuFromModel:(electron::ElectronMenuModel*)model {
NSMenu* menu = [[NSMenu alloc] initWithTitle:@""];
// We manually manage enabled/disabled/hidden state for every item,
// including Cocoa role-based selectors.
menu.autoenablesItems = NO;
const int count = model->GetItemCount();
for (int index = 0; index < count; index++) {
@ -240,6 +244,7 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
[self addItemToMenu:menu atIndex:index fromModel:model];
}
menu.delegate = self;
return menu;
}
@ -294,9 +299,11 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
if ([items count] == 0)
return MakeEmptySubmenu();
NSMenu* menu = [[NSMenu alloc] init];
menu.autoenablesItems = NO;
NSArray* services = [NSSharingService sharingServicesForItems:items];
for (NSSharingService* service in services)
[menu addItem:[self menuItemForService:service withItems:items]];
[menu setDelegate:self];
return menu;
}
@ -353,27 +360,22 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
std::u16string title = u"Services";
NSString* sub_label = l10n_util::FixUpWindowsStyleLabel(title);
[item setTarget:nil];
[item setAction:nil];
item.target = nil;
item.action = nil;
NSMenu* submenu = [[NSMenu alloc] initWithTitle:sub_label];
[item setSubmenu:submenu];
item.submenu = submenu;
[NSApp setServicesMenu:submenu];
} else if (role == u"sharemenu") {
SharingItem sharing_item;
model->GetSharingItemAt(index, &sharing_item);
[item setTarget:nil];
[item setAction:nil];
item.target = nil;
item.action = nil;
[item setSubmenu:[self createShareMenuForItem:sharing_item]];
} else if (type == electron::ElectronMenuModel::TYPE_SUBMENU &&
model->IsVisibleAt(index)) {
// We need to specifically check that the submenu top-level item has been
// enabled as it's not validated by validateUserInterfaceItem
if (!model->IsEnabledAt(index))
[item setEnabled:NO];
// Recursively build a submenu from the sub-model at this index.
[item setTarget:nil];
[item setAction:nil];
item.target = nil;
item.action = nil;
electron::ElectronMenuModel* submenuModel =
static_cast<electron::ElectronMenuModel*>(
model->GetSubmenuModelAt(index));
@ -388,8 +390,12 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
}
}
[submenu setTitle:[item title]];
[item setSubmenu:submenu];
submenu.title = item.title;
item.submenu = submenu;
item.tag = index;
item.representedObject =
[WeakPtrToElectronMenuModelAsNSObject weakPtrForModel:model];
submenu.delegate = self;
// Set submenu's role.
if ((role == u"window" || role == u"windowmenu") && [submenu numberOfItems])
@ -404,9 +410,9 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
// the model so hierarchical menus check the correct index in the correct
// model. Setting the target to |self| allows this class to participate
// in validation of the menu items.
[item setTag:index];
[item setRepresentedObject:[WeakPtrToElectronMenuModelAsNSObject
weakPtrForModel:model]];
item.tag = index;
item.representedObject =
[WeakPtrToElectronMenuModelAsNSObject weakPtrForModel:model];
ui::Accelerator accelerator;
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
&accelerator)) {
@ -434,20 +440,20 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
ui::MacKeyCodeForWindowsKeyCode(accelerator.key_code(), modifier_mask,
nullptr, &character);
}
[item setKeyEquivalent:[NSString stringWithFormat:@"%C", character]];
[item setKeyEquivalentModifierMask:modifier_mask];
item.keyEquivalent = [NSString stringWithFormat:@"%C", character];
item.keyEquivalentModifierMask = modifier_mask;
}
[(id)item
setAllowsKeyEquivalentWhenHidden:(model->WorksWhenHiddenAt(index))];
// Set menu item's role.
[item setTarget:self];
item.target = self;
if (!role.empty()) {
for (const Role& pair : kRolesMap) {
if (role == base::ASCIIToUTF16(pair.role)) {
[item setTarget:nil];
[item setAction:pair.selector];
item.target = nil;
item.action = pair.selector;
break;
}
}
@ -457,6 +463,43 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
return item;
}
- (void)applyStateToMenuItem:(NSMenuItem*)item {
id represented = item.representedObject;
if (!represented)
return;
if (![represented
isKindOfClass:[WeakPtrToElectronMenuModelAsNSObject class]]) {
NSLog(@"representedObject is not a WeakPtrToElectronMenuModelAsNSObject");
return;
}
electron::ElectronMenuModel* model =
[WeakPtrToElectronMenuModelAsNSObject getFrom:represented];
if (!model)
return;
NSInteger index = item.tag;
int count = model->GetItemCount();
if (index < 0 || index >= count)
return;
item.hidden = !model->IsVisibleAt(index);
item.enabled = model->IsEnabledAt(index);
item.state = model->IsItemCheckedAt(index) ? NSControlStateValueOn
: NSControlStateValueOff;
}
// Recursively refreshes the menu tree starting from |menu|, applying the
// model state to each menu item.
- (void)refreshMenuTree:(NSMenu*)menu {
for (NSMenuItem* item in menu.itemArray) {
[self applyStateToMenuItem:item];
if (item.submenu)
[self refreshMenuTree:item.submenu];
}
}
// Adds an item or a hierarchical menu to the item at the |index|,
// associated with the entry in the model identified by |modelIndex|.
- (void)addItemToMenu:(NSMenu*)menu
@ -466,32 +509,6 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
atIndex:index];
}
// Called before the menu is to be displayed to update the state (enabled,
// radio, etc) of each item in the menu.
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
SEL action = [item action];
if (action == @selector(performShare:))
return YES;
if (action != @selector(itemSelected:))
return NO;
NSInteger modelIndex = [item tag];
electron::ElectronMenuModel* model = [WeakPtrToElectronMenuModelAsNSObject
getFrom:[(id)item representedObject]];
DCHECK(model);
if (model) {
BOOL checked = model->IsItemCheckedAt(modelIndex);
DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
[(id)item
setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
[(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
return model->IsEnabledAt(modelIndex);
}
return NO;
}
// Called when the user chooses a particular menu item. |sender| is the menu
// item chosen.
- (void)itemSelected:(id)sender {
@ -526,10 +543,11 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
menu_ = menu;
} else {
menu_ = [[NSMenu alloc] initWithTitle:@""];
menu_.autoenablesItems = NO;
if (model_)
[self populateWithModel:model_.get()];
}
[menu_ setDelegate:self];
menu_.delegate = self;
return menu_;
}
@ -539,6 +557,7 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
- (void)menuWillOpen:(NSMenu*)menu {
isMenuOpen_ = YES;
[self refreshMenuTree:menu];
if (model_)
model_->MenuWillShow();
}