Commit 5650735a authored by Trent Apted's avatar Trent Apted Committed by Commit Bot

Fix opening bookmarks from Chrome App menu.

This regressed in r518459 which added a step to clear out a stale
NSMenu tree once it could never be used again. But it turns out that
-[NSMenuDelegate menuDidClose:] is a bad signal for this. AppKit isn't
actually done with the menu at that point, and doesn't give the signal
we need.

To fix, keep the menu around as before, and improve test coverage.

Bug: 788430
Change-Id: Idbc19cb56e6e52d699d94518b2164d0bd776ce9d
Reviewed-on: https://chromium-review.googlesource.com/790090Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Trent Apted <tapted@chromium.org>
Cr-Commit-Position: refs/heads/master@{#519835}
parent 0578baaf
...@@ -184,6 +184,7 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver { ...@@ -184,6 +184,7 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver {
recentTabsMenuModelDelegate_.reset(); recentTabsMenuModelDelegate_.reset();
[self setModel:nullptr]; [self setModel:nullptr];
appMenuModel_.reset(); appMenuModel_.reset();
bookmarkMenuBridge_ = nullptr;
buttonViewController_.reset(); buttonViewController_.reset();
// The observers should most likely already be destroyed (since they're reset // The observers should most likely already be destroyed (since they're reset
...@@ -304,7 +305,6 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver { ...@@ -304,7 +305,6 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver {
} }
- (void)updateBookmarkSubMenu { - (void)updateBookmarkSubMenu {
DCHECK(!bookmarkMenuBridge_);
NSMenu* bookmarkMenu = [self bookmarkSubMenu]; NSMenu* bookmarkMenu = [self bookmarkSubMenu];
if (!bookmarkMenu) if (!bookmarkMenu)
return; // Guest profiles have no bookmarks menu. return; // Guest profiles have no bookmarks menu.
...@@ -317,6 +317,12 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver { ...@@ -317,6 +317,12 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver {
// which can't be reused across menu invocations. // which can't be reused across menu invocations.
bookmarkMenuBridge_ = std::make_unique<BookmarkMenuBridge>( bookmarkMenuBridge_ = std::make_unique<BookmarkMenuBridge>(
[self appMenuModel]->browser()->profile(), bookmarkMenu); [self appMenuModel]->browser()->profile(), bookmarkMenu);
// Note |bookmarkMenuBridge_| is useless after the menu closes, but it must
// exist when (and if) the menu action is sent. Unfortunately, AppKit sends
// menu actions after menuDidClose: and NSMenuDidEndTrackingNotification so
// |bookmarkMenuBridge_| is going to hang around until updateBookmarkSubMenu
// is called again.
} }
- (void)updateBrowserActionsSubmenu { - (void)updateBrowserActionsSubmenu {
...@@ -395,8 +401,6 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver { ...@@ -395,8 +401,6 @@ class ToolbarActionsBarObserverHelper : public ToolbarActionsBarObserver {
- (void)menuDidClose:(NSMenu*)menu { - (void)menuDidClose:(NSMenu*)menu {
[super menuDidClose:menu]; [super menuDidClose:menu];
bookmarkMenuBridge_ = nullptr;
// We don't need to observe changes to zoom or toolbar size when the menu is // We don't need to observe changes to zoom or toolbar size when the menu is
// closed, since we instantiate it with the proper value and recreate the menu // closed, since we instantiate it with the proper value and recreate the menu
// on each show. (We do this in -menuNeedsUpdate:, which is called when the // on each show. (We do this in -menuNeedsUpdate:, which is called when the
......
...@@ -11,10 +11,13 @@ ...@@ -11,10 +11,13 @@
#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 "chrome/app/chrome_command_ids.h" #include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/browser/sync/profile_sync_test_util.h"
#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/browser_list_observer.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h"
#include "chrome/browser/ui/cocoa/test/cocoa_profile_test.h" #include "chrome/browser/ui/cocoa/test/cocoa_profile_test.h"
#include "chrome/browser/ui/cocoa/test/run_loop_testing.h" #include "chrome/browser/ui/cocoa/test/run_loop_testing.h"
#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
...@@ -27,6 +30,7 @@ ...@@ -27,6 +30,7 @@
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h" #include "chrome/grit/theme_resources.h"
#include "chrome/test/base/testing_profile.h" #include "chrome/test/base/testing_profile.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/browser_sync/profile_sync_service.h" #include "components/browser_sync/profile_sync_service.h"
#include "components/browser_sync/profile_sync_service_mock.h" #include "components/browser_sync/profile_sync_service_mock.h"
#include "components/sync/base/sync_prefs.h" #include "components/sync/base/sync_prefs.h"
...@@ -47,6 +51,25 @@ using testing::_; ...@@ -47,6 +51,25 @@ using testing::_;
using testing::Invoke; using testing::Invoke;
using testing::Return; using testing::Return;
@implementation AppMenuController (TestingAPI)
- (BookmarkMenuBridge*)bookmarkMenuBridge {
return bookmarkMenuBridge_.get();
}
@end
@interface TestBookmarkMenuCocoaController : BookmarkMenuCocoaController
@property(nonatomic, readonly) const bookmarks::BookmarkNode* lastOpenedNode;
@end
@implementation TestBookmarkMenuCocoaController {
const bookmarks::BookmarkNode* lastOpenedNode_;
}
@synthesize lastOpenedNode = lastOpenedNode_;
- (void)openURLForNode:(const bookmarks::BookmarkNode*)node {
lastOpenedNode_ = node;
}
@end
namespace { namespace {
class MockAppMenuModel : public AppMenuModel { class MockAppMenuModel : public AppMenuModel {
...@@ -64,6 +87,23 @@ class DummyRouter : public sync_sessions::LocalSessionEventRouter { ...@@ -64,6 +87,23 @@ class DummyRouter : public sync_sessions::LocalSessionEventRouter {
void Stop() override {} void Stop() override {}
}; };
class BrowserRemovedObserver : public chrome::BrowserListObserver {
public:
BrowserRemovedObserver() { BrowserList::AddObserver(this); }
~BrowserRemovedObserver() override { BrowserList::RemoveObserver(this); }
void WaitUntilBrowserRemoved() { run_loop_.Run(); }
void OnBrowserRemoved(Browser* browser) override { run_loop_.Quit(); }
private:
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(BrowserRemovedObserver);
};
} // namespace
namespace test {
class AppMenuControllerTest : public CocoaProfileTest { class AppMenuControllerTest : public CocoaProfileTest {
public: public:
AppMenuControllerTest() { AppMenuControllerTest() {
...@@ -105,6 +145,17 @@ class AppMenuControllerTest : public CocoaProfileTest { ...@@ -105,6 +145,17 @@ class AppMenuControllerTest : public CocoaProfileTest {
new syncer::SyncErrorFactoryMock)); new syncer::SyncErrorFactoryMock));
} }
TestBookmarkMenuCocoaController* SetTestBookmarksMenuDelegate(
BookmarkMenuBridge* bridge) {
base::scoped_nsobject<TestBookmarkMenuCocoaController> test_controller(
[[TestBookmarkMenuCocoaController alloc] initWithBridge:bridge]);
bridge->ClearBookmarkMenu();
[bridge->BookmarkMenu() setDelegate:test_controller];
bridge->controller_ =
base::scoped_nsobject<BookmarkMenuCocoaController>(test_controller);
return test_controller;
}
void RegisterRecentTabs(RecentTabsBuilderTestHelper* helper) { void RegisterRecentTabs(RecentTabsBuilderTestHelper* helper) {
helper->ExportToSessionsSyncManager(manager_.get()); helper->ExportToSessionsSyncManager(manager_.get());
} }
...@@ -270,18 +321,51 @@ TEST_F(AppMenuControllerTest, RecentTabDeleteOrder) { ...@@ -270,18 +321,51 @@ TEST_F(AppMenuControllerTest, RecentTabDeleteOrder) {
// If the delete order is wrong then the test will crash on exit. // If the delete order is wrong then the test will crash on exit.
} }
class BrowserRemovedObserver : public chrome::BrowserListObserver { // Simulate opening a bookmark from the bookmark submenu.
public: TEST_F(AppMenuControllerTest, OpenBookmark) {
BrowserRemovedObserver() { BrowserList::AddObserver(this); } // Ensure there's at least one bookmark.
~BrowserRemovedObserver() override { BrowserList::RemoveObserver(this); } bookmarks::BookmarkModel* model =
void WaitUntilBrowserRemoved() { run_loop_.Run(); } BookmarkModelFactory::GetForBrowserContext(profile());
void OnBrowserRemoved(Browser* browser) override { run_loop_.Quit(); } ASSERT_TRUE(model);
const bookmarks::BookmarkNode* parent = model->bookmark_bar_node();
const bookmarks::BookmarkNode* about = model->AddURL(
parent, 0, base::ASCIIToUTF16("About"), GURL("chrome://chrome"));
private: EXPECT_FALSE([controller_ bookmarkMenuBridge]);
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(BrowserRemovedObserver); [controller_ menuNeedsUpdate:[controller_ menu]];
}; BookmarkMenuBridge* bridge = [controller_ bookmarkMenuBridge];
EXPECT_TRUE(bridge);
NSMenu* bookmarks_menu =
[[[controller_ menu] itemWithTitle:@"Bookmarks"] submenu];
EXPECT_TRUE(bookmarks_menu);
EXPECT_TRUE([bookmarks_menu delegate]);
// The bookmark item actions would do Browser::OpenURL(), which will fail in a
// unit test. So swap in a test delegate.
TestBookmarkMenuCocoaController* test_controller =
SetTestBookmarksMenuDelegate(bridge);
// The fixed items from the default model.
EXPECT_EQ(5u, [bookmarks_menu numberOfItems]);
// When AppKit shows the menu, the bookmark items are added (and a separator).
[[bookmarks_menu delegate] menuNeedsUpdate:bookmarks_menu];
EXPECT_EQ(7u, [bookmarks_menu numberOfItems]);
base::scoped_nsobject<NSMenuItem> item(
[[bookmarks_menu itemWithTitle:@"About"] retain]);
EXPECT_TRUE(item);
EXPECT_TRUE([item target]);
EXPECT_TRUE([item action]);
// Simulate how AppKit would click the item (menuDidClose happens first).
[controller_ menuDidClose:[controller_ menu]];
EXPECT_FALSE([test_controller lastOpenedNode]);
[[item target] performSelector:[item action] withObject:item];
EXPECT_EQ(about, [test_controller lastOpenedNode]);
}
// Test that AppMenuController can be destroyed after the Browser. // Test that AppMenuController can be destroyed after the Browser.
// This can happen because the AppMenuController's owner (ToolbarController) // This can happen because the AppMenuController's owner (ToolbarController)
...@@ -296,4 +380,4 @@ TEST_F(AppMenuControllerTest, DestroyedAfterBrowser) { ...@@ -296,4 +380,4 @@ TEST_F(AppMenuControllerTest, DestroyedAfterBrowser) {
// |controller_| is released in TearDown(). // |controller_| is released in TearDown().
} }
} // namespace } // namespace test
...@@ -20,6 +20,10 @@ namespace bookmarks { ...@@ -20,6 +20,10 @@ namespace bookmarks {
class BookmarkNode; class BookmarkNode;
} }
namespace test {
class AppMenuControllerTest;
}
// C++ controller for the bookmark menu; one per AppController (which // C++ controller for the bookmark menu; one per AppController (which
// means there is only one). When bookmarks are changed, this class // means there is only one). When bookmarks are changed, this class
// takes care of updating Cocoa bookmark menus. This is not named // takes care of updating Cocoa bookmark menus. This is not named
...@@ -82,6 +86,7 @@ class BookmarkMenuBridge : public bookmarks::BookmarkModelObserver { ...@@ -82,6 +86,7 @@ class BookmarkMenuBridge : public bookmarks::BookmarkModelObserver {
private: private:
friend class BookmarkMenuBridgeTest; friend class BookmarkMenuBridgeTest;
friend class test::AppMenuControllerTest;
void BuildRootMenu(); void BuildRootMenu();
......
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