blob: fc9c6ac02fde22d0b8437e2cd0e291a13ef7db2e [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.
#include "base/location.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/extensions/extension_action_test_util.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.h"
#include "chrome/browser/ui/views/toolbar/app_menu.h"
#include "chrome/browser/ui/views/toolbar/app_menu_button.h"
#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
#include "chrome/browser/ui/views/toolbar/extension_toolbar_menu_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "extensions/common/feature_switch.h"
#include "extensions/test/extension_test_message_listener.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/submenu_view.h"
namespace {
AppMenuButton* GetAppButtonFromBrowser(Browser* browser) {
return BrowserView::GetBrowserViewForBrowser(browser)
->toolbar()
->app_menu_button();
}
// Tests clicking on an overflowed toolbar action. This is called when the app
// menu is open, and handles actually clicking on the action.
// |button| specifies the mouse button to click with.
void TestOverflowedToolbarAction(Browser* browser,
ui_controls::MouseButton button) {
// A bunch of plumbing to safely get at the overflowed toolbar action.
AppMenuButton* app_menu_button = GetAppButtonFromBrowser(browser);
EXPECT_TRUE(app_menu_button->IsMenuShowing());
AppMenu* app_menu = app_menu_button->app_menu_for_testing();
ASSERT_TRUE(app_menu);
ExtensionToolbarMenuView* menu_view =
app_menu->extension_toolbar_for_testing();
ASSERT_TRUE(menu_view);
BrowserActionsContainer* overflow_container =
menu_view->container_for_testing();
ASSERT_TRUE(overflow_container);
ToolbarActionView* action_view =
overflow_container->GetToolbarActionViewAt(0);
EXPECT_TRUE(action_view->visible());
// Click on the toolbar action to activate it.
gfx::Point action_view_loc =
ui_test_utils::GetCenterInScreenCoordinates(action_view);
ui_controls::SendMouseMove(action_view_loc.x(), action_view_loc.y());
EXPECT_TRUE(ui_test_utils::SendMouseEventsSync(
button, ui_controls::DOWN | ui_controls::UP));
}
// Tests the context menu of an overflowed action.
void TestWhileContextMenuOpen(bool* did_test_while_menu_open,
Browser* browser,
ToolbarActionView* context_menu_action) {
*did_test_while_menu_open = true;
views::MenuItemView* menu_root = context_menu_action->menu_for_testing();
ASSERT_TRUE(menu_root);
ASSERT_TRUE(menu_root->GetSubmenu());
EXPECT_TRUE(menu_root->GetSubmenu()->IsShowing());
views::MenuItemView* first_menu_item =
menu_root->GetSubmenu()->GetMenuItemAt(0);
ASSERT_TRUE(first_menu_item);
// Make sure we're showing the right context menu.
EXPECT_EQ(base::UTF8ToUTF16("Browser Action Popup"),
first_menu_item->title());
EXPECT_TRUE(first_menu_item->enabled());
// Get the overflow container.
AppMenuButton* app_menu_button = GetAppButtonFromBrowser(browser);
AppMenu* app_menu = app_menu_button->app_menu_for_testing();
ASSERT_TRUE(app_menu);
ExtensionToolbarMenuView* menu_view =
app_menu->extension_toolbar_for_testing();
ASSERT_TRUE(menu_view);
BrowserActionsContainer* overflow_container =
menu_view->container_for_testing();
ASSERT_TRUE(overflow_container);
// Get the first action on the second row of the overflow container.
int second_row_index = overflow_container->toolbar_actions_bar()
->platform_settings()
.icons_per_overflow_menu_row;
ToolbarActionView* second_row_action =
overflow_container->GetToolbarActionViewAt(second_row_index);
EXPECT_TRUE(second_row_action->visible());
EXPECT_TRUE(second_row_action->enabled());
gfx::Point action_view_loc =
ui_test_utils::GetCenterInScreenCoordinates(second_row_action);
gfx::Point action_view_loc_in_menu_item_bounds = action_view_loc;
views::View::ConvertPointFromScreen(first_menu_item,
&action_view_loc_in_menu_item_bounds);
// Regression test for crbug.com/538414: The first menu item is overlapping
// the second row action button. With crbug.com/538414, the click would go to
// the menu button, instead of the menu item.
EXPECT_TRUE(
first_menu_item->HitTestPoint(action_view_loc_in_menu_item_bounds));
// Click on the first menu item (which shares bounds, but overlaps, the second
// row action).
ui_controls::SendMouseMove(action_view_loc.x(), action_view_loc.y());
ui_controls::SendMouseEventsNotifyWhenDone(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP, base::Closure());
// Test resumes in the main test body.
}
// Posts a task to test the context menu.
void OnContextMenuWillShow(bool* did_test_while_menu_open,
Browser* browser,
ToolbarActionView* toolbar_action_view) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&TestWhileContextMenuOpen, did_test_while_menu_open,
browser, toolbar_action_view));
}
} // namespace
class ToolbarActionViewInteractiveUITest : public ExtensionBrowserTest {
protected:
ToolbarActionViewInteractiveUITest();
~ToolbarActionViewInteractiveUITest() override;
// ExtensionBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override;
void TearDownOnMainThread() override;
private:
// Override the extensions-action-redesign switch.
std::unique_ptr<extensions::FeatureSwitch::ScopedOverride> feature_override_;
DISALLOW_COPY_AND_ASSIGN(ToolbarActionViewInteractiveUITest);
};
ToolbarActionViewInteractiveUITest::ToolbarActionViewInteractiveUITest() {}
ToolbarActionViewInteractiveUITest::~ToolbarActionViewInteractiveUITest() {}
void ToolbarActionViewInteractiveUITest::SetUpCommandLine(
base::CommandLine* command_line) {
ExtensionBrowserTest::SetUpCommandLine(command_line);
// We do this before the rest of the setup because it can affect how the views
// are constructed.
feature_override_.reset(new extensions::FeatureSwitch::ScopedOverride(
extensions::FeatureSwitch::extension_action_redesign(), true));
ToolbarActionsBar::disable_animations_for_testing_ = true;
}
void ToolbarActionViewInteractiveUITest::TearDownOnMainThread() {
ToolbarActionsBar::disable_animations_for_testing_ = false;
}
#if defined(USE_OZONE) || defined(OS_WIN)
// ozone bringup - http://crbug.com/401304
// flaky on Windows - http://crbug.com/638692
#define MAYBE_TestClickingOnOverflowedAction DISABLED_TestClickingOnOverflowedAction
#else
#define MAYBE_TestClickingOnOverflowedAction TestClickingOnOverflowedAction
#endif
// Tests clicking on an overflowed extension action.
IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest,
MAYBE_TestClickingOnOverflowedAction) {
// Load an extension.
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("ui").AppendASCII("browser_action_popup")));
base::RunLoop().RunUntilIdle(); // Ensure the extension is fully loaded.
// Reduce visible count to 0 so that the action is overflowed.
ToolbarActionsModel::Get(profile())->SetVisibleIconCount(0);
// When the extension is activated, it will send a message that its popup
// opened. Listen for the message.
ExtensionTestMessageListener listener("Popup opened", false);
AppMenuButton* app_menu_button = GetAppButtonFromBrowser(browser());
// Click on the app button.
gfx::Point app_button_loc =
ui_test_utils::GetCenterInScreenCoordinates(app_menu_button);
ui_controls::SendMouseMove(app_button_loc.x(), app_button_loc.y());
EXPECT_TRUE(ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP));
base::RunLoop().RunUntilIdle();
TestOverflowedToolbarAction(browser(), ui_controls::LEFT);
base::RunLoop().RunUntilIdle();
// The app menu should no longer be showing.
EXPECT_FALSE(app_menu_button->IsMenuShowing());
// And the extension should have been activated.
listener.WaitUntilSatisfied();
}
// TODO(jonross): determine cause of new flake, and restore previous MAYBE
// conditions. Temporarily disabling due to number of flakes (crbug.com/639010)
// Tests the context menus of overflowed extension actions.
IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest,
DISABLED_TestContextMenuOnOverflowedAction) {
views::MenuController::TurnOffMenuSelectionHoldForTest();
// Load an extension that has a home page (important for the context menu's
// first item being enabled).
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("ui").AppendASCII("browser_action_popup")));
base::RunLoop().RunUntilIdle(); // Ensure the extension is fully loaded.
// Aaaannnnd... Load a bunch of other extensions so that the overflow menu
// is spread across multiple rows.
for (int i = 0; i < 15; ++i) {
scoped_refptr<const extensions::Extension> extension =
extensions::extension_action_test_util::CreateActionExtension(
base::IntToString(i),
extensions::extension_action_test_util::BROWSER_ACTION);
extension_service()->AddExtension(extension.get());
}
ASSERT_EQ(16u, browser()
->window()
->GetToolbarActionsBar()
->toolbar_actions_unordered()
.size());
// Reduce visible count to 0 so that all actions are overflowed.
ToolbarActionsModel::Get(profile())->SetVisibleIconCount(0);
// Set a callback for the context menu showing.
bool did_test_while_menu_open = false;
base::Callback<void(ToolbarActionView*)> context_menu_callback(
base::Bind(&OnContextMenuWillShow, &did_test_while_menu_open, browser()));
ToolbarActionView::set_context_menu_callback_for_testing(
&context_menu_callback);
AppMenuButton* app_menu_button = GetAppButtonFromBrowser(browser());
// Click on the app button, and then right-click on the first toolbar action.
gfx::Point app_button_loc =
ui_test_utils::GetCenterInScreenCoordinates(app_menu_button);
ui_controls::SendMouseMove(app_button_loc.x(), app_button_loc.y());
EXPECT_TRUE(ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP));
base::RunLoop().RunUntilIdle();
TestOverflowedToolbarAction(browser(), ui_controls::RIGHT);
base::RunLoop().RunUntilIdle();
// Test is continued first in TestOverflowedToolbarAction() to right click on
// the action, followed by OnContextMenuWillShow() and
// TestWhileContextMenuOpen().
// Make sure we did all the expected tests.
EXPECT_TRUE(did_test_while_menu_open);
// We should have navigated to the extension's home page, which is google.com.
EXPECT_EQ(
GURL("https://www.google.com/"),
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
}
// Tests that clicking on the toolbar action a second time when the action is
// already open results in closing the popup, and doesn't re-open it.
IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest,
DoubleClickToolbarActionToClose) {
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("ui").AppendASCII("browser_action_popup")));
base::RunLoop().RunUntilIdle(); // Ensure the extension is fully loaded.
BrowserActionsContainer* browser_actions_container =
BrowserView::GetBrowserViewForBrowser(browser())
->toolbar()
->browser_actions();
ToolbarActionsBar* toolbar_actions_bar =
browser_actions_container->toolbar_actions_bar();
ToolbarActionView* toolbar_action_view =
browser_actions_container->GetToolbarActionViewAt(0);
// When the extension is activated, it will send a message that its popup
// opened. Listen for the message.
ExtensionTestMessageListener listener("Popup opened", false);
// Click on the action, and wait for the popup to fully load.
EXPECT_TRUE(ui_test_utils::SendMouseMoveSync(
ui_test_utils::GetCenterInScreenCoordinates(toolbar_action_view)));
EXPECT_TRUE(ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP));
listener.WaitUntilSatisfied();
ExtensionActionViewController* view_controller =
static_cast<ExtensionActionViewController*>(
toolbar_action_view->view_controller());
EXPECT_EQ(view_controller, toolbar_actions_bar->popup_owner());
EXPECT_TRUE(view_controller->is_showing_popup());
{
// Click down on the action button; this should close the popup.
content::WindowedNotificationObserver observer(
extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
content::NotificationService::AllSources());
// For reasons unbeknownst to me, SendMouseEventsSync() with only a down
// event will cause Windows to hang. Using SendMouseEvents() and running all
// pending UI tasks seems to do the trick.
EXPECT_TRUE(ui_controls::SendMouseEvents(ui_controls::LEFT,
ui_controls::DOWN));
base::RunLoop loop;
ui_controls::RunClosureAfterAllPendingUIEvents(loop.QuitClosure());
loop.Run();
observer.Wait(); // Wait for the popup to fully close.
}
EXPECT_FALSE(view_controller->is_showing_popup());
EXPECT_EQ(nullptr, toolbar_actions_bar->popup_owner());
// Releasing the mouse shouldn't result in the popup being shown again.
EXPECT_TRUE(
ui_test_utils::SendMouseEventsSync(ui_controls::LEFT, ui_controls::UP));
EXPECT_FALSE(view_controller->is_showing_popup());
EXPECT_EQ(nullptr, toolbar_actions_bar->popup_owner());
}
#if defined(USE_OZONE) || defined(OS_WIN)
// ozone bringup - http://crbug.com/401304
// flaky on Windows - http://crbug.com/638692
#define MAYBE_ActivateOverflowedToolbarActionWithKeyboard \
DISABLED_ActivateOverflowedToolbarActionWithKeyboard
#else
#define MAYBE_ActivateOverflowedToolbarActionWithKeyboard \
ActivateOverflowedToolbarActionWithKeyboard
#endif
IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest,
MAYBE_ActivateOverflowedToolbarActionWithKeyboard) {
views::MenuController::TurnOffMenuSelectionHoldForTest();
// Load an extension with an action.
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("ui").AppendASCII("browser_action_popup")));
base::RunLoop().RunUntilIdle(); // Ensure the extension is fully loaded.
// Reduce visible count to 0 so that all actions are overflowed.
ToolbarActionsModel::Get(profile())->SetVisibleIconCount(0);
// Set up a listener for the extension being triggered.
ExtensionTestMessageListener listener("Popup opened", false);
// Open the app menu, navigate to the toolbar action, and activate it via the
// keyboard.
AppMenuButton* app_menu_button = GetAppButtonFromBrowser(browser());
gfx::Point app_button_loc =
ui_test_utils::GetCenterInScreenCoordinates(app_menu_button);
ui_controls::SendMouseMove(app_button_loc.x(), app_button_loc.y());
EXPECT_TRUE(ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(app_menu_button->IsMenuShowing());
gfx::NativeWindow native_window =
views::MenuController::GetActiveInstance()->owner()->GetNativeWindow();
// Send a key down event followed by the return key.
// The key down event targets the toolbar action in the app menu.
ui_controls::SendKeyPress(native_window, ui::VKEY_DOWN, false, false, false,
false);
// The triggering of the action and subsequent widget destruction occurs on
// the message loop. Wait for this all to complete.
base::RunLoop loop;
ui_controls::SendKeyPressNotifyWhenDone(native_window, ui::VKEY_RETURN, false,
false, false, false,
loop.QuitClosure());
loop.Run();
// The menu should be closed.
EXPECT_FALSE(app_menu_button->IsMenuShowing());
// And the extension should have been activated.
EXPECT_TRUE(listener.WaitUntilSatisfied());
}