Commit 1bbf19c5 authored by Elly Fong-Jones's avatar Elly Fong-Jones Committed by Commit Bot

cocoa: weakly reference MenuModel from MenuController

Before this CL, MenuController's contract required that the provided
MenuModel outlived a MenuController instance. As of 10.15, it's
impossible to fulfill that contract: AppKit seems to like keeping
MenuController instances (via [NSMenuItem target]) alive until the
next autorelease pool drain, and there's no way for the C++ code that
creates & manages a MenuModel to know when that will happen. See bug
998773 for more details about this change in 10.15.

As such, this CL has MenuController hold a WeakPtr to its MenuModel,
rather than a raw pointer, and handles the MenuModel being destroyed
before the MenuController.

One other possible approach would be to make MenuController own the
MenuModel, but that would be a very large change to the ownership of
MenuModel, and in particular would conflict with the fact that Views
(in the //ui/views sense) are often used as MenuModels but are owned
by their parent View.

Bug: 998835
Change-Id: Ieb3b8928a40d07edf1b9a4d394fb4adc0410fde5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1804538Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#697297}
parent 1f9f65ad
...@@ -25,9 +25,6 @@ UI_BASE_EXPORT ...@@ -25,9 +25,6 @@ UI_BASE_EXPORT
@interface MenuControllerCocoa @interface MenuControllerCocoa
: NSObject<NSMenuDelegate, NSUserInterfaceValidations> : NSObject<NSMenuDelegate, NSUserInterfaceValidations>
// The Model passed in to -initWithModel:.
@property(nonatomic, assign) ui::MenuModel* model;
// Whether to activate selected menu items via a posted task. This may allow the // Whether to activate selected menu items via a posted task. This may allow the
// selection to be handled earlier, whilst the menu is fading out. If the posted // selection to be handled earlier, whilst the menu is fading out. If the posted
// task wasn't processed by the time the action is normally sent, it will be // task wasn't processed by the time the action is normally sent, it will be
...@@ -54,6 +51,9 @@ UI_BASE_EXPORT ...@@ -54,6 +51,9 @@ UI_BASE_EXPORT
// Programmatically close the constructed menu. // Programmatically close the constructed menu.
- (void)cancel; - (void)cancel;
- (ui::MenuModel*)model;
- (void)setModel:(ui::MenuModel*)model;
// Access to the constructed menu if the complex initializer was used. If the // Access to the constructed menu if the complex initializer was used. If the
// default initializer was used, then this will create the menu on first call. // default initializer was used, then this will create the menu on first call.
- (NSMenu*)menu; - (NSMenu*)menu;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/cancelable_callback.h" #include "base/cancelable_callback.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/accelerator.h"
...@@ -44,6 +45,42 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -44,6 +45,42 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
} // namespace } // namespace
// This class stores a base::WeakPtr<ui::MenuModel> as an Objective-C object,
// which allows it to be stored in the representedObject field of an NSMenuItem.
@interface WeakPtrToMenuModelAsNSObject : NSObject
+ (instancetype)weakPtrForModel:(ui::MenuModel*)model;
+ (ui::MenuModel*)getFrom:(id)instance;
- (instancetype)initWithModel:(ui::MenuModel*)model;
- (ui::MenuModel*)menuModel;
@end
@implementation WeakPtrToMenuModelAsNSObject {
base::WeakPtr<ui::MenuModel> model_;
}
+ (instancetype)weakPtrForModel:(ui::MenuModel*)model {
return
[[[WeakPtrToMenuModelAsNSObject alloc] initWithModel:model] autorelease];
}
+ (ui::MenuModel*)getFrom:(id)instance {
return [base::mac::ObjCCastStrict<WeakPtrToMenuModelAsNSObject>(instance)
menuModel];
}
- (instancetype)initWithModel:(ui::MenuModel*)model {
if ((self = [super init])) {
model_ = model->AsWeakPtr();
}
return self;
}
- (ui::MenuModel*)menuModel {
return model_.get();
}
@end
// Internal methods. // Internal methods.
@interface MenuControllerCocoa () @interface MenuControllerCocoa ()
// Called before the menu is to be displayed to update the state (enabled, // Called before the menu is to be displayed to update the state (enabled,
...@@ -83,7 +120,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -83,7 +120,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
@end @end
@implementation MenuControllerCocoa { @implementation MenuControllerCocoa {
ui::MenuModel* model_; // Weak. base::WeakPtr<ui::MenuModel> model_;
base::scoped_nsobject<NSMenu> menu_; base::scoped_nsobject<NSMenu> menu_;
BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank
BOOL isMenuOpen_; BOOL isMenuOpen_;
...@@ -91,10 +128,17 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -91,10 +128,17 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
std::unique_ptr<base::CancelableClosure> postedItemSelectedTask_; std::unique_ptr<base::CancelableClosure> postedItemSelectedTask_;
} }
@synthesize model = model_;
@synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_; @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
@synthesize postItemSelectedAsTask = postItemSelectedAsTask_; @synthesize postItemSelectedAsTask = postItemSelectedAsTask_;
- (ui::MenuModel*)model {
return model_.get();
}
- (void)setModel:(ui::MenuModel*)model {
model_ = model->AsWeakPtr();
}
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
return self; return self;
...@@ -103,7 +147,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -103,7 +147,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
- (instancetype)initWithModel:(ui::MenuModel*)model - (instancetype)initWithModel:(ui::MenuModel*)model
useWithPopUpButtonCell:(BOOL)useWithCell { useWithPopUpButtonCell:(BOOL)useWithCell {
if ((self = [super init])) { if ((self = [super init])) {
model_ = model; model_ = model->AsWeakPtr();
useWithPopUpButtonCell_ = useWithCell; useWithPopUpButtonCell_ = useWithCell;
[self menu]; [self menu];
} }
...@@ -117,14 +161,15 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -117,14 +161,15 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
// while its context menu is still open. // while its context menu is still open.
[self cancel]; [self cancel];
model_ = NULL; model_ = nullptr;
[super dealloc]; [super dealloc];
} }
- (void)cancel { - (void)cancel {
if (isMenuOpen_) { if (isMenuOpen_) {
[menu_ cancelTracking]; [menu_ cancelTracking];
model_->MenuWillClose(); if (model_)
model_->MenuWillClose();
isMenuOpen_ = NO; isMenuOpen_ = NO;
} }
} }
...@@ -190,8 +235,8 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -190,8 +235,8 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
// in validation of the menu items. // in validation of the menu items.
[item setTag:index]; [item setTag:index];
[item setTarget:self]; [item setTarget:self];
NSValue* modelObject = [NSValue valueWithPointer:model]; [item setRepresentedObject:[WeakPtrToMenuModelAsNSObject
[item setRepresentedObject:modelObject]; // Retains |modelObject|. weakPtrForModel:model]];
// On the Mac, context menus never have accelerators. Menus constructed // On the Mac, context menus never have accelerators. Menus constructed
// for context use have useWithPopUpButtonCell_ set to NO. // for context use have useWithPopUpButtonCell_ set to NO.
if (useWithPopUpButtonCell_) { if (useWithPopUpButtonCell_) {
...@@ -216,8 +261,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -216,8 +261,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
NSInteger modelIndex = [item tag]; NSInteger modelIndex = [item tag];
ui::MenuModel* model = ui::MenuModel* model =
static_cast<ui::MenuModel*>( [WeakPtrToMenuModelAsNSObject getFrom:[(id)item representedObject]];
[[(id)item representedObject] pointerValue]);
DCHECK(model); DCHECK(model);
if (model) { if (model) {
BOOL checked = model->IsItemCheckedAt(modelIndex); BOOL checked = model->IsItemCheckedAt(modelIndex);
...@@ -303,8 +347,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -303,8 +347,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
NSInteger modelIndex = [sender tag]; NSInteger modelIndex = [sender tag];
ui::MenuModel* model = ui::MenuModel* model =
static_cast<ui::MenuModel*>( [WeakPtrToMenuModelAsNSObject getFrom:[sender representedObject]];
[[sender representedObject] pointerValue]);
DCHECK(model); DCHECK(model);
if (model) if (model)
model->ActivatedAt(modelIndex, uiEventFlags); model->ActivatedAt(modelIndex, uiEventFlags);
...@@ -313,7 +356,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -313,7 +356,7 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
- (NSMenu*)menu { - (NSMenu*)menu {
if (!menu_ && model_) { if (!menu_ && model_) {
menu_.reset([[self menuFromModel:model_] retain]); menu_.reset([[self menuFromModel:model_.get()] retain]);
[menu_ setDelegate:self]; [menu_ setDelegate:self];
// If this is to be used with a NSPopUpButtonCell, add an item at the 0th // If this is to be used with a NSPopUpButtonCell, add an item at the 0th
// position that's empty. Doing it after the menu has been constructed won't // position that's empty. Doing it after the menu has been constructed won't
...@@ -334,13 +377,15 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) { ...@@ -334,13 +377,15 @@ bool MenuHasVisibleItems(const ui::MenuModel* model) {
- (void)menuWillOpen:(NSMenu*)menu { - (void)menuWillOpen:(NSMenu*)menu {
isMenuOpen_ = YES; isMenuOpen_ = YES;
model_->MenuWillShow(); // Note: |model_| may trigger -[self dealloc]. if (model_)
model_->MenuWillShow(); // Note: |model_| may trigger -[self dealloc].
} }
- (void)menuDidClose:(NSMenu*)menu { - (void)menuDidClose:(NSMenu*)menu {
if (isMenuOpen_) { if (isMenuOpen_) {
isMenuOpen_ = NO; isMenuOpen_ = NO;
model_->MenuWillClose(); // Note: |model_| may trigger -[self dealloc]. if (model_)
model_->MenuWillClose(); // Note: |model_| may trigger -[self dealloc].
} }
} }
......
...@@ -281,7 +281,6 @@ TEST_F(MenuControllerTest, BasicCreation) { ...@@ -281,7 +281,6 @@ TEST_F(MenuControllerTest, BasicCreation) {
NSString* title = [itemTwo title]; NSString* title = [itemTwo title];
EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title)); EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title));
EXPECT_EQ(2, [itemTwo tag]); EXPECT_EQ(2, [itemTwo tag]);
EXPECT_EQ([[itemTwo representedObject] pointerValue], &model);
EXPECT_TRUE([[[menu menu] itemAtIndex:3] isSeparatorItem]); EXPECT_TRUE([[[menu menu] itemAtIndex:3] isSeparatorItem]);
} }
...@@ -315,7 +314,6 @@ TEST_F(MenuControllerTest, Submenus) { ...@@ -315,7 +314,6 @@ TEST_F(MenuControllerTest, Submenus) {
NSString* title = [submenuItem title]; NSString* title = [submenuItem title];
EXPECT_EQ(ASCIIToUTF16("sub-two"), base::SysNSStringToUTF16(title)); EXPECT_EQ(ASCIIToUTF16("sub-two"), base::SysNSStringToUTF16(title));
EXPECT_EQ(1, [submenuItem tag]); EXPECT_EQ(1, [submenuItem tag]);
EXPECT_EQ([[submenuItem representedObject] pointerValue], &submodel);
// Make sure the item after the submenu is correct and its represented // Make sure the item after the submenu is correct and its represented
// object is back to the top model. // object is back to the top model.
...@@ -323,7 +321,6 @@ TEST_F(MenuControllerTest, Submenus) { ...@@ -323,7 +321,6 @@ TEST_F(MenuControllerTest, Submenus) {
title = [item title]; title = [item title];
EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title)); EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title));
EXPECT_EQ(2, [item tag]); EXPECT_EQ(2, [item tag]);
EXPECT_EQ([[item representedObject] pointerValue], &model);
} }
TEST_F(MenuControllerTest, EmptySubmenu) { TEST_F(MenuControllerTest, EmptySubmenu) {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef UI_BASE_MODELS_MENU_MODEL_H_ #ifndef UI_BASE_MODELS_MENU_MODEL_H_
#define UI_BASE_MODELS_MENU_MODEL_H_ #define UI_BASE_MODELS_MENU_MODEL_H_
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "ui/base/models/menu_model_delegate.h" #include "ui/base/models/menu_model_delegate.h"
#include "ui/base/models/menu_separator_types.h" #include "ui/base/models/menu_separator_types.h"
...@@ -24,7 +25,7 @@ class Accelerator; ...@@ -24,7 +25,7 @@ class Accelerator;
class ButtonMenuItemModel; class ButtonMenuItemModel;
// An interface implemented by an object that provides the content of a menu. // An interface implemented by an object that provides the content of a menu.
class UI_BASE_EXPORT MenuModel { class UI_BASE_EXPORT MenuModel : public base::SupportsWeakPtr<MenuModel> {
public: public:
// The type of item. // The type of item.
enum ItemType { enum ItemType {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment