blob: dcf1319087bf0a466128d81743c68949df7fb963 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/extensions/browser_action_button.h"
#import <Cocoa/Cocoa.h>
#include <memory>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/ui/browser_window.h"
#import "chrome/browser/ui/cocoa/app_menu/app_menu_controller.h"
#import "chrome/browser/ui/cocoa/browser_window_controller.h"
#import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h"
#import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h"
#import "chrome/browser/ui/cocoa/test/run_loop_testing.h"
#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
#include "chrome/browser/ui/global_error/global_error.h"
#include "chrome/browser/ui/global_error/global_error_service.h"
#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
#include "chrome/browser/ui/toolbar/media_router_action.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "extensions/common/extension_builder.h"
#include "ui/base/cocoa/cocoa_base_utils.h"
#import "ui/events/test/cocoa_test_event_utils.h"
// A helper class to wait for a menu to open and close.
@interface MenuWatcher : NSObject
- (id)initWithController:(MenuController*)controller;
@property(nonatomic, assign) base::Closure openClosure;
@property(nonatomic, assign) base::Closure closeClosure;
@end
namespace {
using ActionType = extensions::ExtensionBuilder::ActionType;
const int kMenuPadding = 26;
// A simple error class that has a menu item.
class MenuError : public GlobalError {
public:
MenuError() {}
~MenuError() override {}
bool HasMenuItem() override { return true; }
int MenuItemCommandID() override {
// An arbitrary high id so that it's not taken.
return 65536;
}
base::string16 MenuItemLabel() override {
const char kErrorMessage[] =
"This is a really long error message that will cause the app menu "
"to have increased width";
return base::ASCIIToUTF16(kErrorMessage);
}
void ExecuteMenuItem(Browser* browser) override {}
bool HasBubbleView() override { return false; }
bool HasShownBubbleView() override { return false; }
void ShowBubbleView(Browser* browser) override {}
GlobalErrorBubbleViewBase* GetBubbleView() override { return nullptr; }
private:
DISALLOW_COPY_AND_ASSIGN(MenuError);
};
// Returns the center point for a particular |view|.
NSPoint GetCenterPoint(NSView* view) {
NSWindow* window = [view window];
NSScreen* screen = [window screen];
DCHECK(screen);
// Converts the center position of the view into the coordinates accepted
// by ui_controls methods.
NSRect bounds = [view bounds];
NSPoint center = NSMakePoint(NSMidX(bounds), NSMidY(bounds));
center = [view convertPoint:center toView:nil];
center = ui::ConvertPointFromWindowToScreen(window, center);
return NSMakePoint(center.x, [screen frame].size.height - center.y);
}
// Moves the mouse (synchronously) to the center of the given |view|.
void MoveMouseToCenter(NSView* view) {
NSPoint centerPoint = GetCenterPoint(view);
base::RunLoop runLoop;
ui_controls::SendMouseMoveNotifyWhenDone(
centerPoint.x, centerPoint.y, runLoop.QuitClosure());
runLoop.Run();
}
// Simulates a right-click on the action button in the overflow menu.
void DispatchClickOnOverflowedAction(BrowserActionButton* action_button) {
NSEvent* event = nil; // The event doesn't matter when sending directly.
[action_button rightMouseDown:event];
}
// ui_controls:: methods don't play nice when there is an open menu (like the
// app menu). Instead, ClickOnOverflowedAction() simulates a right click by
// feeding the event directly to the button. In regular interaction, this is
// dispatched after menuDidClose:. To simulate that, start the menu closing
// (with no action), but invoke the action on the button directly when the close
// is observed.
void ClickOnOverflowedAction(
base::scoped_nsobject<MenuWatcher> app_menu_watcher,
AppMenuController* app_menu_controller) {
// The app menu should start as open (since that's where the overflowed
// actions are).
EXPECT_TRUE([app_menu_controller isMenuOpen]);
BrowserActionsController* overflow_controller =
[app_menu_controller browserActionsController];
ASSERT_TRUE(overflow_controller);
BrowserActionButton* action_button = [overflow_controller buttonWithIndex:0];
// The action should be attached to a superview.
EXPECT_TRUE([action_button superview]);
base::Closure invoke_action = base::Bind(&DispatchClickOnOverflowedAction,
base::Unretained(action_button));
[app_menu_watcher setCloseClosure:invoke_action];
// Close the app menu.
[app_menu_controller cancel];
}
} // namespace
// A simple helper menu delegate that will keep track of if a menu is opened,
// and closes them immediately (which is useful because message loops with
// menus open in Cocoa don't play nicely with testing).
@interface MenuHelper : NSObject<NSMenuDelegate> {
// Whether or not a menu has been opened. This can be reset so the helper can
// be used multiple times.
BOOL menuOpened_;
// The closure to run when the menu opens, if any.
base::Closure openClosure_;
// A function to be called to verify state while the menu is open.
base::Closure verify_;
}
@property(nonatomic, assign) BOOL menuOpened;
@property(nonatomic, assign) base::Closure openClosure;
@property(nonatomic, assign) base::Closure verify;
@end
@implementation MenuHelper
- (void)menuWillOpen:(NSMenu*)menu {
menuOpened_ = YES;
if (!openClosure_.is_null())
openClosure_.Run();
if (!verify_.is_null())
verify_.Run();
[menu cancelTracking];
}
@synthesize menuOpened = menuOpened_;
@synthesize openClosure = openClosure_;
@synthesize verify = verify_;
@end
@implementation MenuWatcher {
// The MenuController for the menu this object is watching.
MenuController* menuController_;
// The closure to run when the menu opens, if any.
base::Closure openClosure_;
// The closure to run when the menu closes, if any.
base::Closure closeClosure_;
}
@synthesize openClosure = openClosure_;
@synthesize closeClosure = closeClosure_;
- (id)initWithController:(MenuController*)controller {
if (self = [super init]) {
menuController_ = controller;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(menuDidOpen:)
name:kMenuControllerMenuWillOpenNotification
object:menuController_];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(menuDidClose:)
name:kMenuControllerMenuDidCloseNotification
object:menuController_];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)menuDidClose:(NSNotification*)notification {
if (!closeClosure_.is_null()) {
// Run |closeClosure_| synchronously since it may depend on objects that are
// torn down once the menu is closed and execution has returned to the main
// run loop.
closeClosure_.Run();
closeClosure_.Reset();
}
}
- (void)menuDidOpen:(NSNotification*)notification {
if (!openClosure_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::ResetAndReturn(&openClosure_));
}
}
@end
class BrowserActionButtonUiTest : public ExtensionBrowserTest {
protected:
BrowserActionButtonUiTest() {}
~BrowserActionButtonUiTest() override {}
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
toolbarController_ =
[[BrowserWindowController
browserWindowControllerForWindow:browser()->
window()->GetNativeWindow()]
toolbarController];
ASSERT_TRUE(toolbarController_);
appMenuController_ = [toolbarController_ appMenuController];
model_ = ToolbarActionsModel::Get(profile());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionBrowserTest::SetUpCommandLine(command_line);
ToolbarActionsBar::disable_animations_for_testing_ = true;
}
void TearDownOnMainThread() override {
ToolbarActionsBar::disable_animations_for_testing_ = false;
ExtensionBrowserTest::TearDownOnMainThread();
}
// Opens the app menu and the context menu of the overflowed action, and
// checks that the menus get opened/closed properly.
void OpenAppMenuAndActionContextMenu(MenuHelper* context_menu_helper) {
// Move the mouse over the app menu button.
MoveMouseToCenter(appMenuButton());
// No menu yet (on the browser action).
EXPECT_FALSE([context_menu_helper menuOpened]);
base::RunLoop run_loop;
base::scoped_nsobject<MenuWatcher> app_menu_watcher(
[[MenuWatcher alloc] initWithController:appMenuController()]);
base::Closure close_with_action =
base::Bind(&ClickOnOverflowedAction, app_menu_watcher,
base::Unretained(appMenuController()));
[app_menu_watcher setOpenClosure:close_with_action];
// Quit the RunLoop below when the context menu opens. This dictates that
// the MenuHelper's verify action has also run.
[context_menu_helper setOpenClosure:run_loop.QuitClosure()];
// Show the app menu.
ui_controls::SendMouseEvents(ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP);
run_loop.Run();
// The menu opened on the main bar's action button rather than the
// overflow's since Cocoa does not support running a menu within a menu.
EXPECT_TRUE([context_menu_helper menuOpened]);
}
ToolbarController* toolbarController() { return toolbarController_; }
AppMenuController* appMenuController() { return appMenuController_; }
ToolbarActionsModel* model() { return model_; }
NSView* appMenuButton() { return [toolbarController_ appMenuButton]; }
private:
ToolbarController* toolbarController_ = nil;
AppMenuController* appMenuController_ = nil;
ToolbarActionsModel* model_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(BrowserActionButtonUiTest);
};
// Verifies that the action is "popped out" of overflow; that is, it is visible
// on the main bar, and is set as the popped out action on the controlling
// ToolbarActionsBar.
void CheckActionIsPoppedOut(BrowserActionsController* actionsController,
BrowserActionButton* actionButton) {
EXPECT_EQ([actionsController containerView], [actionButton superview]);
EXPECT_EQ([actionButton viewController],
[actionsController toolbarActionsBar]->popped_out_action());
// Since the button is popped out for a popup or context menu, it should be
// highlighted.
EXPECT_TRUE([actionButton isHighlighted]);
}
// Test that opening a context menu works for both actions on the main bar and
// actions in the overflow menu.
IN_PROC_BROWSER_TEST_F(BrowserActionButtonUiTest,
ContextMenusOnMainAndOverflow) {
// Add an extension with a browser action.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("browser_action")
.SetAction(ActionType::BROWSER_ACTION)
.Build();
extension_service()->AddExtension(extension.get());
ASSERT_EQ(1u, model()->toolbar_items().size());
BrowserActionsController* actionsController =
[toolbarController() browserActionsController];
BrowserActionButton* actionButton = [actionsController buttonWithIndex:0];
ASSERT_TRUE(actionButton);
// Stub out the action button's normal context menu with a fake one so we
// can track when it opens.
base::scoped_nsobject<NSMenu> testContextMenu(
[[NSMenu alloc] initWithTitle:@""]);
base::scoped_nsobject<MenuHelper> menuHelper([[MenuHelper alloc] init]);
[testContextMenu setDelegate:menuHelper.get()];
[actionButton setTestContextMenu:testContextMenu.get()];
// Right now, the action button should be visible (attached to a superview).
EXPECT_TRUE([actionButton superview]);
// Move the mouse to the center of the action button, preparing to click.
MoveMouseToCenter(actionButton);
{
// No menu should yet be open.
EXPECT_FALSE([menuHelper menuOpened]);
base::RunLoop runLoop;
ui_controls::SendMouseEventsNotifyWhenDone(
ui_controls::RIGHT,
ui_controls::DOWN | ui_controls::UP,
runLoop.QuitClosure());
runLoop.Run();
// The menu should have opened from the click.
EXPECT_TRUE([menuHelper menuOpened]);
EXPECT_FALSE([actionButton isHighlighted]);
}
// Reset the menu helper so we can use it again.
[menuHelper setMenuOpened:NO];
[menuHelper setVerify:base::Bind(
CheckActionIsPoppedOut, actionsController, actionButton)];
// Shrink the visible count to be 0. This should hide the action button.
model()->SetVisibleIconCount(0);
EXPECT_EQ(nil, [actionButton superview]);
OpenAppMenuAndActionContextMenu(menuHelper.get());
}
IN_PROC_BROWSER_TEST_F(BrowserActionButtonUiTest,
MediaRouterActionContextMenuInOverflow) {
model()->AddComponentAction(
ComponentToolbarActionsFactory::kMediaRouterActionId);
ASSERT_EQ(1u, model()->toolbar_items().size());
BrowserActionButton* actionButton =
[[toolbarController() browserActionsController] buttonWithIndex:0];
ASSERT_TRUE(actionButton);
// Stub out the action button's normal context menu with a fake one so we
// can track when it opens.
base::scoped_nsobject<NSMenu> testContextMenu(
[[NSMenu alloc] initWithTitle:@""]);
base::scoped_nsobject<MenuHelper> menuHelper([[MenuHelper alloc] init]);
[testContextMenu setDelegate:menuHelper.get()];
[actionButton setTestContextMenu:testContextMenu.get()];
model()->SetActionVisibility(
ComponentToolbarActionsFactory::kMediaRouterActionId, false);
OpenAppMenuAndActionContextMenu(menuHelper.get());
ToolbarActionsBar* actionsBar =
[[toolbarController() browserActionsController] toolbarActionsBar];
// The action should be back in the overflow.
EXPECT_FALSE(
actionsBar->IsActionVisibleOnMainBar(actionsBar->GetActions()[0]));
}
// Checks the layout of the overflow bar in the app menu.
void CheckAppMenuLayout(ToolbarController* toolbarController,
int overflowStartIndex,
const std::string& error_message,
const base::Closure& closure) {
AppMenuController* appMenuController =
[toolbarController appMenuController];
// The app menu should start as open (since that's where the overflowed
// actions are).
EXPECT_TRUE([appMenuController isMenuOpen]) << error_message;
BrowserActionsController* overflowController =
[appMenuController browserActionsController];
ASSERT_TRUE(overflowController) << error_message;
ToolbarActionsBar* overflowBar = [overflowController toolbarActionsBar];
BrowserActionsContainerView* overflowContainer =
[overflowController containerView];
NSMenu* menu = [appMenuController menu];
// The overflow container should be within the bounds of the app menu, as
// should its parents.
int menu_width = [menu size].width;
NSRect frame = [overflowContainer frame];
// The container itself should be indented in the menu.
EXPECT_GT(NSMinX(frame), 0) << error_message;
// Hierarchy: The overflow container is owned by two different views in the
// app menu. Each superview should start at 0 in the x-axis.
EXPECT_EQ(0, NSMinX([[overflowContainer superview] frame])) << error_message;
EXPECT_EQ(0, NSMinX([[[overflowContainer superview] superview] frame])) <<
error_message;
// The overflow container should fully fit in the app menu, including the
// space taken away for padding, and should have its desired size.
EXPECT_LE(NSWidth(frame), menu_width - kMenuPadding) << error_message;
EXPECT_EQ(NSWidth(frame), overflowBar->GetFullSize().width())
<< error_message;
// Every button that has an index lower than the overflow start index (i.e.,
// every button on the main toolbar) should not be attached to a view.
for (int i = 0; i < overflowStartIndex; ++i) {
BrowserActionButton* button = [overflowController buttonWithIndex:i];
EXPECT_FALSE([button superview]) << error_message;
EXPECT_GE(0, NSMaxX([button frame])) << error_message;
}
// Every other button should be attached to a view, and should be at the
// proper bounds. Calculating each button's proper bounds here would just be
// a duplication of the logic in the method, but we can test that each button
// a) is within the overflow container's bounds, and
// b) doesn't intersect with another button.
// If both of those are true, then we're probably good.
for (NSUInteger i = overflowStartIndex;
i < [overflowController buttonCount]; ++i) {
BrowserActionButton* button = [overflowController buttonWithIndex:i];
EXPECT_TRUE([button superview]) << error_message;
EXPECT_TRUE(NSContainsRect([overflowContainer bounds], [button frame])) <<
error_message;
for (NSUInteger j = 0; j < i; ++j) {
EXPECT_FALSE(
NSContainsRect([[overflowController buttonWithIndex:j] frame],
[button frame])) << error_message;
}
}
// Close the app menu.
[appMenuController cancel];
EXPECT_FALSE([appMenuController isMenuOpen]) << error_message;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, closure);
}
// Tests the layout of the overflow container in the app menu.
// Disabled due to crashing flakily, see crbug.com/602203.
IN_PROC_BROWSER_TEST_F(BrowserActionButtonUiTest,
DISABLED_TestOverflowContainerLayout) {
// Add a bunch of extensions - enough to trigger multiple rows in the overflow
// menu.
const int kNumExtensions = 12;
for (int i = 0; i < kNumExtensions; ++i) {
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder(base::StringPrintf("extension%d", i))
.SetAction(ActionType::BROWSER_ACTION)
.Build();
extension_service()->AddExtension(extension.get());
}
ASSERT_EQ(kNumExtensions, static_cast<int>(model()->toolbar_items().size()));
// A helper function to open the app menu and call the check function.
auto resizeAndActivateAppMenu = [this](int visible_count,
const std::string& error_message) {
model()->SetVisibleIconCount(kNumExtensions - visible_count);
MoveMouseToCenter(appMenuButton());
{
base::RunLoop runLoop;
// Click on the app menu, and pass in a callback to continue the test in
// CheckAppMenuLayout (due to the blocking nature of Cocoa menus,
// passing in runLoop.QuitClosure() is not sufficient here.)
ui_controls::SendMouseEventsNotifyWhenDone(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP,
base::Bind(&CheckAppMenuLayout,
base::Unretained(toolbarController()),
kNumExtensions - visible_count,
error_message,
runLoop.QuitClosure()));
runLoop.Run();
}
};
// Test the layout with gradually more extensions hidden.
for (int i = 1; i <= kNumExtensions; ++i)
resizeAndActivateAppMenu(i, base::StringPrintf("Normal: %d", i));
// Adding a global error adjusts the app menu size, and has been known to mess
// up the overflow container's bounds (crbug.com/511326).
GlobalErrorService* error_service =
GlobalErrorServiceFactory::GetForProfile(profile());
error_service->AddGlobalError(base::MakeUnique<MenuError>());
// It's probably excessive to test every level of the overflow here. Test
// having all actions overflowed, some actions overflowed, and one action
// overflowed.
resizeAndActivateAppMenu(kNumExtensions, "GlobalError Full");
resizeAndActivateAppMenu(kNumExtensions / 2, "GlobalError Half");
resizeAndActivateAppMenu(1, "GlobalError One");
}
void AddExtensionWithMenuOpen(ToolbarController* toolbarController,
ExtensionService* extensionService,
const base::Closure& closure) {
AppMenuController* appMenuController =
[toolbarController appMenuController];
EXPECT_TRUE([appMenuController isMenuOpen]);
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("extension")
.SetAction(ActionType::BROWSER_ACTION)
.Build();
extensionService->AddExtension(extension.get());
base::RunLoop().RunUntilIdle();
// Close the app menu.
[appMenuController cancel];
EXPECT_FALSE([appMenuController isMenuOpen]);
closure.Run();
}
// Test adding an extension while the app menu is open. Regression test for
// crbug.com/561237.
IN_PROC_BROWSER_TEST_F(BrowserActionButtonUiTest,
AddExtensionWithMenuOpen) {
// Add an extension to ensure the overflow menu is present.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("original extension")
.SetAction(ActionType::BROWSER_ACTION)
.Build();
extension_service()->AddExtension(extension.get());
ASSERT_EQ(1, static_cast<int>(model()->toolbar_items().size()));
model()->SetVisibleIconCount(0);
MoveMouseToCenter(appMenuButton());
base::RunLoop runLoop;
// Click on the app menu, and pass in a callback to continue the test in
// AddExtensionWithMenuOpen (due to the blocking nature of Cocoa menus,
// passing in runLoop.QuitClosure() is not sufficient here.)
base::scoped_nsobject<MenuWatcher> menuWatcher(
[[MenuWatcher alloc] initWithController:appMenuController()]);
[menuWatcher setOpenClosure:
base::Bind(&AddExtensionWithMenuOpen,
base::Unretained(toolbarController()), extension_service(),
runLoop.QuitClosure())];
ui_controls::SendMouseEvents(ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP);
runLoop.Run();
}
// Test that activating an action that doesn't want to run on the page via the
// mouse and the keyboard works.
IN_PROC_BROWSER_TEST_F(BrowserActionButtonUiTest,
OpenMenuOnDisabledActionWithMouseOrKeyboard) {
// Add an extension with a page action.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("page action")
.SetAction(ActionType::PAGE_ACTION)
.Build();
extension_service()->AddExtension(extension.get());
ASSERT_EQ(1u, model()->toolbar_items().size());
BrowserActionsController* actionsController =
[toolbarController() browserActionsController];
BrowserActionButton* actionButton = [actionsController buttonWithIndex:0];
ASSERT_TRUE(actionButton);
// Stub out the action button's normal context menu with a fake one so we
// can track when it opens.
base::scoped_nsobject<NSMenu> testContextMenu(
[[NSMenu alloc] initWithTitle:@""]);
base::scoped_nsobject<MenuHelper> menuHelper([[MenuHelper alloc] init]);
[testContextMenu setDelegate:menuHelper.get()];
[actionButton setTestContextMenu:testContextMenu.get()];
// The button should be attached.
EXPECT_TRUE([actionButton superview]);
// Move the mouse and click on the button. The menu should open.
MoveMouseToCenter(actionButton);
{
EXPECT_FALSE([menuHelper menuOpened]);
base::RunLoop runLoop;
ui_controls::SendMouseEventsNotifyWhenDone(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP,
runLoop.QuitClosure());
runLoop.Run();
EXPECT_TRUE([menuHelper menuOpened]);
}
// Reset the menu helper so we can use it again.
[menuHelper setMenuOpened:NO];
// Send the 'space' key to the button with it as the first responder. The menu
// should open.
[[actionButton window] makeFirstResponder:actionButton];
EXPECT_TRUE([[actionButton window] firstResponder] == actionButton);
{
EXPECT_FALSE([menuHelper menuOpened]);
base::RunLoop runLoop;
ui_controls::SendKeyPressNotifyWhenDone([actionButton window],
ui::VKEY_SPACE, false, false, false,
false, runLoop.QuitClosure());
runLoop.Run();
EXPECT_TRUE([menuHelper menuOpened]);
}
}
void CloseAppMenu(AppMenuController* appMenuController,
const base::Closure& quitClosure) {
EXPECT_TRUE([appMenuController isMenuOpen]);
[appMenuController cancel];
quitClosure.Run();
}
// Tests opening the app menu with an overflow section needed, then closing it,
// removing the need for the overflow section, and re-opening it.
// Regression test for crbug.com/603241.
IN_PROC_BROWSER_TEST_F(BrowserActionButtonUiTest,
TestReopeningAppMenuWithOverflowNotNeeded) {
// Add an extension with a browser action and overflow it in the menu.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("browser_action")
.SetAction(ActionType::BROWSER_ACTION)
.Build();
extension_service()->AddExtension(extension.get());
ASSERT_EQ(1u, model()->toolbar_items().size());
model()->SetVisibleIconCount(0);
// Move the mouse over the app menu button.
MoveMouseToCenter(appMenuButton());
auto openAndCloseAppMenu = [](AppMenuController* controller) {
base::RunLoop runLoop;
base::scoped_nsobject<MenuWatcher> menuWatcher(
[[MenuWatcher alloc] initWithController:controller]);
[menuWatcher setOpenClosure:
base::Bind(&CloseAppMenu,
base::Unretained(controller),
runLoop.QuitClosure())];
ui_controls::SendMouseEvents(ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP);
runLoop.Run();
};
openAndCloseAppMenu(appMenuController());
// Move the extension back to the main bar, so an overflow bar is no longer
// needed. Then open and close the app menu a couple times.
// This tests that the menu properly cleans up after itself when an overflow
// was present, and is no longer (fix for crbug.com/603241).
model()->SetVisibleIconCount(1);
openAndCloseAppMenu(appMenuController());
openAndCloseAppMenu(appMenuController());
}