fix: weakly reference MenuModel from MenuController (#23778)

This commit is contained in:
Shelley Vohr 2020-05-27 13:54:52 -07:00 committed by GitHub
parent f78504515b
commit 7b0a84477a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 20 deletions

View file

@ -10,6 +10,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/mac/scoped_nsobject.h" #include "base/mac/scoped_nsobject.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
namespace electron { namespace electron {
@ -24,15 +25,13 @@ class ElectronMenuModel;
// as it only maintains weak references. // as it only maintains weak references.
@interface ElectronMenuController : NSObject <NSMenuDelegate> { @interface ElectronMenuController : NSObject <NSMenuDelegate> {
@protected @protected
electron::ElectronMenuModel* model_; // weak base::WeakPtr<electron::ElectronMenuModel> model_;
base::scoped_nsobject<NSMenu> menu_; base::scoped_nsobject<NSMenu> menu_;
BOOL isMenuOpen_; BOOL isMenuOpen_;
BOOL useDefaultAccelerator_; BOOL useDefaultAccelerator_;
base::OnceClosure closeCallback; base::OnceClosure closeCallback;
} }
@property(nonatomic, assign) electron::ElectronMenuModel* model;
// Builds a NSMenu from the pre-built model (must not be nil). Changes made // Builds a NSMenu from the pre-built model (must not be nil). Changes made
// to the contents of the model after calling this will not be noticed. // to the contents of the model after calling this will not be noticed.
- (id)initWithModel:(electron::ElectronMenuModel*)model - (id)initWithModel:(electron::ElectronMenuModel*)model
@ -46,6 +45,9 @@ class ElectronMenuModel;
// Programmatically close the constructed menu. // Programmatically close the constructed menu.
- (void)cancel; - (void)cancel;
- (electron::ElectronMenuModel*)model;
- (void)setModel:(electron::ElectronMenuModel*)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;

View file

@ -8,6 +8,7 @@
#include <utility> #include <utility>
#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/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
@ -87,6 +88,44 @@ NSMenu* MakeEmptySubmenu() {
} // namespace } // namespace
// This class stores a base::WeakPtr<electron::ElectronMenuModel> as an
// Objective-C object, which allows it to be stored in the representedObject
// field of an NSMenuItem.
@interface WeakPtrToElectronMenuModelAsNSObject : NSObject
+ (instancetype)weakPtrForModel:(electron::ElectronMenuModel*)model;
+ (electron::ElectronMenuModel*)getFrom:(id)instance;
- (instancetype)initWithModel:(electron::ElectronMenuModel*)model;
- (electron::ElectronMenuModel*)menuModel;
@end
@implementation WeakPtrToElectronMenuModelAsNSObject {
base::WeakPtr<electron::ElectronMenuModel> _model;
}
+ (instancetype)weakPtrForModel:(electron::ElectronMenuModel*)model {
return [[[WeakPtrToElectronMenuModelAsNSObject alloc] initWithModel:model]
autorelease];
}
+ (electron::ElectronMenuModel*)getFrom:(id)instance {
return
[base::mac::ObjCCastStrict<WeakPtrToElectronMenuModelAsNSObject>(instance)
menuModel];
}
- (instancetype)initWithModel:(electron::ElectronMenuModel*)model {
if ((self = [super init])) {
_model = model->GetWeakPtr();
}
return self;
}
- (electron::ElectronMenuModel*)menuModel {
return _model.get();
}
@end
// Menu item is located for ease of removing it from the parent owner // Menu item is located for ease of removing it from the parent owner
static base::scoped_nsobject<NSMenuItem> recentDocumentsMenuItem_; static base::scoped_nsobject<NSMenuItem> recentDocumentsMenuItem_;
@ -95,12 +134,18 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
@implementation ElectronMenuController @implementation ElectronMenuController
@synthesize model = model_; - (electron::ElectronMenuModel*)model {
return model_.get();
}
- (id)initWithModel:(electron::ElectronMenuModel*)model - (void)setModel:(electron::ElectronMenuModel*)model {
useDefaultAccelerator:(BOOL)use { model_ = model->GetWeakPtr();
}
- (instancetype)initWithModel:(electron::ElectronMenuModel*)model
useDefaultAccelerator:(BOOL)use {
if ((self = [super init])) { if ((self = [super init])) {
model_ = model; model_ = model->GetWeakPtr();
isMenuOpen_ = NO; isMenuOpen_ = NO;
useDefaultAccelerator_ = use; useDefaultAccelerator_ = use;
[self menu]; [self menu];
@ -115,8 +160,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// while its context menu is still open. // while its context menu is still open.
[self cancel]; [self cancel];
model_ = nil; model_ = nullptr;
[super dealloc]; [super dealloc];
} }
@ -137,7 +181,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
itemWithTitle:@"Electron"] submenu] itemWithTitle:openTitle] retain]); itemWithTitle:@"Electron"] submenu] itemWithTitle:openTitle] retain]);
} }
model_ = model; model_ = model->GetWeakPtr();
[menu_ removeAllItems]; [menu_ removeAllItems];
const int count = model->GetItemCount(); const int count = model->GetItemCount();
@ -153,7 +197,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
if (isMenuOpen_) { if (isMenuOpen_) {
[menu_ cancelTracking]; [menu_ cancelTracking];
isMenuOpen_ = NO; isMenuOpen_ = NO;
model_->MenuWillClose(); if (model_)
model_->MenuWillClose();
if (!closeCallback.is_null()) { if (!closeCallback.is_null()) {
base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback)); base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback));
} }
@ -290,8 +335,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// model. Setting the target to |self| allows this class to participate // model. Setting the target to |self| allows this class to participate
// in validation of the menu items. // in validation of the menu items.
[item setTag:index]; [item setTag:index];
NSValue* modelObject = [NSValue valueWithPointer:model]; [item setRepresentedObject:[WeakPtrToElectronMenuModelAsNSObject
[item setRepresentedObject:modelObject]; // Retains |modelObject|. weakPtrForModel:model]];
ui::Accelerator accelerator; ui::Accelerator accelerator;
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_, if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
&accelerator)) { &accelerator)) {
@ -331,9 +376,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
return NO; return NO;
NSInteger modelIndex = [item tag]; NSInteger modelIndex = [item tag];
electron::ElectronMenuModel* model = electron::ElectronMenuModel* model = [WeakPtrToElectronMenuModelAsNSObject
static_cast<electron::ElectronMenuModel*>( 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);
@ -352,8 +396,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
- (void)itemSelected:(id)sender { - (void)itemSelected:(id)sender {
NSInteger modelIndex = [sender tag]; NSInteger modelIndex = [sender tag];
electron::ElectronMenuModel* model = electron::ElectronMenuModel* model =
static_cast<electron::ElectronMenuModel*>( [WeakPtrToElectronMenuModelAsNSObject getFrom:[sender representedObject]];
[[sender representedObject] pointerValue]);
DCHECK(model); DCHECK(model);
if (model) { if (model) {
NSEvent* event = [NSApp currentEvent]; NSEvent* event = [NSApp currentEvent];
@ -369,7 +412,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
menu_.reset([[NSMenu alloc] initWithTitle:@""]); menu_.reset([[NSMenu alloc] initWithTitle:@""]);
[menu_ setDelegate:self]; [menu_ setDelegate:self];
if (model_) if (model_)
[self populateWithModel:model_]; [self populateWithModel:model_.get()];
return menu_.get(); return menu_.get();
} }
@ -379,7 +422,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
- (void)menuWillOpen:(NSMenu*)menu { - (void)menuWillOpen:(NSMenu*)menu {
isMenuOpen_ = YES; isMenuOpen_ = YES;
model_->MenuWillShow(); if (model_)
model_->MenuWillShow();
} }
- (void)menuDidClose:(NSMenu*)menu { - (void)menuDidClose:(NSMenu*)menu {

View file

@ -7,6 +7,7 @@
#include <map> #include <map>
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h" #include "base/observer_list.h"
#include "base/observer_list_types.h" #include "base/observer_list_types.h"
#include "ui/base/models/simple_menu_model.h" #include "ui/base/models/simple_menu_model.h"
@ -69,6 +70,10 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
void MenuWillClose() override; void MenuWillClose() override;
void MenuWillShow() override; void MenuWillShow() override;
base::WeakPtr<ElectronMenuModel> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
using SimpleMenuModel::GetSubmenuModelAt; using SimpleMenuModel::GetSubmenuModelAt;
ElectronMenuModel* GetSubmenuModelAt(int index); ElectronMenuModel* GetSubmenuModelAt(int index);
@ -80,6 +85,8 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
std::map<int, base::string16> sublabels_; // command id -> sublabel std::map<int, base::string16> sublabels_; // command id -> sublabel
base::ObserverList<Observer> observers_; base::ObserverList<Observer> observers_;
base::WeakPtrFactory<ElectronMenuModel> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ElectronMenuModel); DISALLOW_COPY_AND_ASSIGN(ElectronMenuModel);
}; };