blob: defabae6a3e23fd8559db36abaae68690c68104e [file] [log] [blame]
// Copyright (c) 2013 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 "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/app_menu_constants.h"
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/test/ash_test_base.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/chromeos/arc/icon_decode_request.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
#include "chrome/browser/ui/ash/fake_tablet_mode_controller.h"
#include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
#include "chrome/browser/ui/ash/launcher/arc_launcher_context_menu.h"
#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/extension_launcher_context_menu.h"
#include "chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h"
#include "chrome/browser/ui/ash/tablet_mode_client.h"
#include "chrome/test/base/testing_profile.h"
#include "components/arc/metrics/arc_metrics_constants.h"
#include "components/arc/test/fake_app_instance.h"
#include "components/exo/shell_surface_util.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/display.h"
#include "ui/views/widget/widget.h"
namespace {
bool IsItemPresentInMenu(ui::MenuModel* menu, int command_id) {
ui::MenuModel* model = menu;
int index = 0;
return ui::MenuModel::GetModelAndIndexForCommandId(command_id, &model,
&index);
}
bool IsItemEnabledInMenu(ui::MenuModel* menu, int command_id) {
ui::MenuModel* model = menu;
int index = 0;
return ui::MenuModel::GetModelAndIndexForCommandId(command_id, &model,
&index) &&
menu->IsEnabledAt(index);
}
std::string GetAppNameInShelfGroup(uint32_t task_id) {
return base::StringPrintf("AppInShelfGroup%d", task_id);
}
class LauncherContextMenuTest : public ash::AshTestBase {
protected:
LauncherContextMenuTest() = default;
~LauncherContextMenuTest() override = default;
void SetUp() override {
arc_test_.SetUp(&profile_);
session_manager_ = std::make_unique<session_manager::SessionManager>();
ash::AshTestBase::SetUp();
model_ = std::make_unique<ash::ShelfModel>();
launcher_controller_ =
std::make_unique<ChromeLauncherController>(&profile_, model_.get());
tablet_mode_client_ = std::make_unique<TabletModeClient>();
tablet_mode_client_->InitForTesting(
fake_tablet_mode_controller_.CreateInterfacePtr());
// Disable safe icon decoding to ensure ArcAppShortcutRequests returns in
// the test environment.
arc::IconDecodeRequest::DisableSafeDecodingForTesting();
}
std::unique_ptr<LauncherContextMenu> CreateLauncherContextMenu(
ash::ShelfItemType shelf_item_type,
int64_t display_id) {
ash::ShelfItem item;
item.id = ash::ShelfID("idmockidmockidmockidmockidmockid");
item.type = shelf_item_type;
return LauncherContextMenu::Create(controller(), &item, display_id);
}
// Creates app window and set optional ARC application id.
views::Widget* CreateArcWindow(std::string& window_app_id) {
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
views::Widget* widget = new views::Widget();
params.context = CurrentContext();
widget->Init(params);
widget->Show();
widget->Activate();
exo::SetShellApplicationId(widget->GetNativeWindow(), window_app_id);
return widget;
}
std::unique_ptr<ui::MenuModel> GetMenuModel(
LauncherContextMenu* launcher_context_menu) {
base::RunLoop run_loop;
std::unique_ptr<ui::MenuModel> menu;
launcher_context_menu->GetMenuModel(base::BindLambdaForTesting(
[&](std::unique_ptr<ui::MenuModel> created_menu) {
menu = std::move(created_menu);
run_loop.Quit();
}));
run_loop.Run();
return menu;
}
std::unique_ptr<ui::MenuModel> GetContextMenu(
ash::ShelfItemDelegate* item_delegate,
int64_t display_id) {
base::RunLoop run_loop;
std::unique_ptr<ui::MenuModel> menu;
item_delegate->GetContextMenu(
display_id, base::BindLambdaForTesting(
[&](std::unique_ptr<ui::MenuModel> created_menu) {
menu = std::move(created_menu);
run_loop.Quit();
}));
run_loop.Run();
return menu;
}
void TearDown() override {
launcher_controller_.reset();
ash::AshTestBase::TearDown();
}
ArcAppTest& arc_test() { return arc_test_; }
Profile* profile() { return &profile_; }
ChromeLauncherController* controller() { return launcher_controller_.get(); }
ash::ShelfModel* model() { return model_.get(); }
private:
TestingProfile profile_;
ArcAppTest arc_test_;
std::unique_ptr<session_manager::SessionManager> session_manager_;
std::unique_ptr<ash::ShelfModel> model_;
std::unique_ptr<ChromeLauncherController> launcher_controller_;
FakeTabletModeController fake_tablet_mode_controller_;
std::unique_ptr<TabletModeClient> tablet_mode_client_;
DISALLOW_COPY_AND_ASSIGN(LauncherContextMenuTest);
};
// Verifies that "New Incognito window" menu item in the launcher context
// menu is disabled when Incognito mode is switched off (by a policy).
TEST_F(LauncherContextMenuTest,
NewIncognitoWindowMenuIsDisabledWhenIncognitoModeOff) {
const int64_t display_id = GetPrimaryDisplay().id();
// Initially, "New Incognito window" should be enabled.
std::unique_ptr<LauncherContextMenu> launcher_context_menu =
CreateLauncherContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
std::unique_ptr<ui::MenuModel> menu =
GetMenuModel(launcher_context_menu.get());
ASSERT_TRUE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_INCOGNITO_WINDOW));
EXPECT_TRUE(launcher_context_menu->IsCommandIdEnabled(
ash::MENU_NEW_INCOGNITO_WINDOW));
// Disable Incognito mode.
IncognitoModePrefs::SetAvailability(profile()->GetPrefs(),
IncognitoModePrefs::DISABLED);
launcher_context_menu =
CreateLauncherContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
menu = GetMenuModel(launcher_context_menu.get());
// The item should be disabled.
ASSERT_TRUE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_INCOGNITO_WINDOW));
EXPECT_FALSE(launcher_context_menu->IsCommandIdEnabled(
ash::MENU_NEW_INCOGNITO_WINDOW));
}
// Verifies that "New window" menu item in the launcher context
// menu is disabled when Incognito mode is forced (by a policy).
TEST_F(LauncherContextMenuTest,
NewWindowMenuIsDisabledWhenIncognitoModeForced) {
const int64_t display_id = GetPrimaryDisplay().id();
// Initially, "New window" should be enabled.
std::unique_ptr<LauncherContextMenu> launcher_context_menu =
CreateLauncherContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
std::unique_ptr<ui::MenuModel> menu =
GetMenuModel(launcher_context_menu.get());
ASSERT_TRUE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_WINDOW));
EXPECT_TRUE(launcher_context_menu->IsCommandIdEnabled(ash::MENU_NEW_WINDOW));
// Disable Incognito mode.
IncognitoModePrefs::SetAvailability(profile()->GetPrefs(),
IncognitoModePrefs::FORCED);
launcher_context_menu =
CreateLauncherContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
menu = GetMenuModel(launcher_context_menu.get());
ASSERT_TRUE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_WINDOW));
EXPECT_FALSE(launcher_context_menu->IsCommandIdEnabled(ash::MENU_NEW_WINDOW));
}
// Verifies that "Close" is not shown in context menu if no browser window is
// opened.
TEST_F(LauncherContextMenuTest,
DesktopShellLauncherContextMenuVerifyCloseItem) {
const int64_t display_id = GetPrimaryDisplay().id();
std::unique_ptr<LauncherContextMenu> launcher_context_menu =
CreateLauncherContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
std::unique_ptr<ui::MenuModel> menu =
GetMenuModel(launcher_context_menu.get());
ASSERT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
}
// Verifies context menu and app menu items for ARC app.
TEST_F(LauncherContextMenuTest, ArcLauncherMenusCheck) {
arc_test().app_instance()->RefreshAppList();
arc_test().app_instance()->SendRefreshAppList(
std::vector<arc::mojom::AppInfo>(arc_test().fake_apps().begin(),
arc_test().fake_apps().begin() + 1));
const std::string app_id = ArcAppTest::GetAppId(arc_test().fake_apps()[0]);
const std::string app_name = arc_test().fake_apps()[0].name;
controller()->PinAppWithID(app_id);
const ash::ShelfID shelf_id(app_id);
const ash::ShelfItem* item = controller()->GetItem(shelf_id);
ASSERT_TRUE(item);
EXPECT_EQ(base::UTF8ToUTF16(app_name), item->title);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
EXPECT_TRUE(item_delegate->GetAppMenuItems(0 /* event_flags */).empty());
const int64_t display_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, display_id);
ASSERT_TRUE(menu);
// ARC app is pinned but not running.
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
// ARC app is running.
std::string window_app_id1("org.chromium.arc.1");
CreateArcWindow(window_app_id1);
arc_test().app_instance()->SendTaskCreated(1, arc_test().fake_apps()[0],
std::string());
item_delegate = model()->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
ash::MenuItemList menu_list =
item_delegate->GetAppMenuItems(0 /* event_flags */);
ASSERT_EQ(1U, menu_list.size());
EXPECT_EQ(base::UTF8ToUTF16(app_name), menu_list[0]->label);
menu = GetContextMenu(item_delegate, display_id);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
// ARC non-launchable app is running.
const std::string app_id2 = ArcAppTest::GetAppId(arc_test().fake_apps()[1]);
const std::string app_name2 = arc_test().fake_apps()[1].name;
std::string window_app_id2("org.chromium.arc.2");
CreateArcWindow(window_app_id2);
arc_test().app_instance()->SendTaskCreated(2, arc_test().fake_apps()[1],
std::string());
const ash::ShelfID shelf_id2(app_id2);
const ash::ShelfItem* item2 = controller()->GetItem(shelf_id2);
ASSERT_TRUE(item2);
EXPECT_EQ(base::UTF8ToUTF16(app_name2), item2->title);
ash::ShelfItemDelegate* item_delegate2 =
model()->GetShelfItemDelegate(shelf_id2);
ASSERT_TRUE(item_delegate2);
menu_list = item_delegate2->GetAppMenuItems(0 /* event_flags */);
ASSERT_EQ(1U, menu_list.size());
EXPECT_EQ(base::UTF8ToUTF16(app_name2), menu_list[0]->label);
menu = GetContextMenu(item_delegate2, display_id);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
// Shelf group context menu.
std::vector<arc::mojom::ShortcutInfo> shortcuts = arc_test().fake_shortcuts();
shortcuts[0].intent_uri +=
";S.org.chromium.arc.shelf_group_id=arc_test_shelf_group;end";
arc_test().app_instance()->SendInstallShortcuts(shortcuts);
const std::string app_id3 =
arc::ArcAppShelfId("arc_test_shelf_group",
ArcAppTest::GetAppId(arc_test().fake_apps()[2]))
.ToString();
constexpr int apps_to_test_in_shelf_group = 2;
const std::string app_name3 = arc_test().fake_apps()[2].name;
for (uint32_t i = 0; i < apps_to_test_in_shelf_group; ++i) {
const uint32_t task_id = 3 + i;
std::string window_app_id3 =
base::StringPrintf("org.chromium.arc.%d", task_id);
CreateArcWindow(window_app_id3);
arc_test().app_instance()->SendTaskCreated(
task_id, arc_test().fake_apps()[2], shortcuts[0].intent_uri);
// Set custom name.
arc_test().app_instance()->SendTaskDescription(
task_id, GetAppNameInShelfGroup(task_id),
std::string() /* icon_png_data_as_string */);
const ash::ShelfID shelf_id3(app_id3);
const ash::ShelfItem* item3 = controller()->GetItem(shelf_id3);
ASSERT_TRUE(item3);
// Validate item label is correct
EXPECT_EQ(base::UTF8ToUTF16(app_name3), item3->title);
ash::ShelfItemDelegate* item_delegate3 =
model()->GetShelfItemDelegate(shelf_id3);
ASSERT_TRUE(item_delegate3);
menu = GetContextMenu(item_delegate3, display_id);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
menu_list = item_delegate3->GetAppMenuItems(0 /* event_flags */);
ASSERT_EQ(i + 1, menu_list.size());
// Ensure custom names are set in the app menu items. Note, they are
// in reverse order, based on activation order.
for (uint32_t j = 0; j <= i; ++j) {
EXPECT_EQ(base::UTF8ToUTF16(GetAppNameInShelfGroup(3 + j)),
menu_list[i - j]->label);
}
}
}
TEST_F(LauncherContextMenuTest, ArcLauncherSuspendAppMenu) {
arc::mojom::AppInfo app = arc_test().fake_apps()[0];
app.suspended = true;
arc_test().app_instance()->RefreshAppList();
arc_test().app_instance()->SendRefreshAppList({app});
const std::string app_id = ArcAppTest::GetAppId(app);
controller()->PinAppWithID(app_id);
const ash::ShelfID shelf_id(app_id);
const ash::ShelfItem* item = controller()->GetItem(shelf_id);
ASSERT_TRUE(item);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
EXPECT_TRUE(item_delegate->GetAppMenuItems(0 /* event_flags */).empty());
const int64_t display_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, display_id);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
}
TEST_F(LauncherContextMenuTest, ArcDeferredLauncherContextMenuItemCheck) {
arc_test().app_instance()->RefreshAppList();
arc_test().app_instance()->SendRefreshAppList(
std::vector<arc::mojom::AppInfo>(arc_test().fake_apps().begin(),
arc_test().fake_apps().begin() + 2));
const std::string app_id1 = ArcAppTest::GetAppId(arc_test().fake_apps()[0]);
const std::string app_id2 = ArcAppTest::GetAppId(arc_test().fake_apps()[1]);
controller()->PinAppWithID(app_id1);
arc_test().StopArcInstance();
const ash::ShelfID shelf_id1(app_id1);
const ash::ShelfID shelf_id2(app_id2);
EXPECT_TRUE(controller()->GetItem(shelf_id1));
EXPECT_FALSE(controller()->GetItem(shelf_id2));
arc::LaunchApp(profile(), app_id1, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
arc::LaunchApp(profile(), app_id2, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
EXPECT_TRUE(controller()->GetItem(shelf_id1));
EXPECT_TRUE(controller()->GetItem(shelf_id2));
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(shelf_id1);
ASSERT_TRUE(item_delegate);
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, 0 /* display_id */);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
item_delegate = model()->GetShelfItemDelegate(shelf_id2);
ASSERT_TRUE(item_delegate);
menu = GetContextMenu(item_delegate, 0 /* display_id */);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
}
TEST_F(LauncherContextMenuTest, CommandIdsMatchEnumsForHistograms) {
// Tests that CommandId enums are not changed as the values are used in
// histograms.
EXPECT_EQ(0, ash::MENU_OPEN_NEW);
EXPECT_EQ(1, ash::MENU_CLOSE);
EXPECT_EQ(2, ash::MENU_PIN);
EXPECT_EQ(3, ash::LAUNCH_TYPE_PINNED_TAB);
EXPECT_EQ(4, ash::LAUNCH_TYPE_REGULAR_TAB);
EXPECT_EQ(5, ash::LAUNCH_TYPE_FULLSCREEN);
EXPECT_EQ(6, ash::LAUNCH_TYPE_WINDOW);
EXPECT_EQ(7, ash::MENU_NEW_WINDOW);
EXPECT_EQ(8, ash::MENU_NEW_INCOGNITO_WINDOW);
EXPECT_EQ(9, ash::NOTIFICATION_CONTAINER);
}
TEST_F(LauncherContextMenuTest, ArcContextMenuOptions) {
// Tests that there are 8 ARC app context menu options. If you're
// adding a context menu option ensure that you have added the enum to
// tools/metrics/enums.xml and that you haven't modified the order of the
// existing enums.
arc_test().app_instance()->RefreshAppList();
arc_test().app_instance()->SendRefreshAppList(
std::vector<arc::mojom::AppInfo>(arc_test().fake_apps().begin(),
arc_test().fake_apps().begin() + 1));
const std::string app_id = ArcAppTest::GetAppId(arc_test().fake_apps()[0]);
const ash::ShelfID shelf_id(app_id);
controller()->PinAppWithID(app_id);
const ash::ShelfItem* item = controller()->GetItem(shelf_id);
ASSERT_TRUE(item);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
int64_t primary_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, primary_id);
// Test that there are 8 items in an ARC app context menu.
EXPECT_EQ(8, menu->GetItemCount());
}
// Tests that the context menu of internal app is correct.
TEST_F(LauncherContextMenuTest, InternalAppShelfContextMenu) {
for (const auto& internal_app : app_list::GetInternalAppList(profile())) {
if (!internal_app.show_in_launcher)
continue;
const std::string app_id = internal_app.app_id;
const ash::ShelfID shelf_id(app_id);
// Pin internal app.
controller()->PinAppWithID(app_id);
const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
ASSERT_TRUE(item);
EXPECT_EQ(l10n_util::GetStringUTF16(internal_app.name_string_resource_id),
item->title);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
const int64_t display_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, display_id);
ASSERT_TRUE(menu);
// Internal app is pinned but not running.
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_OPEN_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
}
}
// Tests that the number of context menu options of internal app is correct.
TEST_F(LauncherContextMenuTest, InternalAppShelfContextMenuOptionsNumber) {
for (const auto& internal_app : app_list::GetInternalAppList(profile())) {
const std::string app_id = internal_app.app_id;
const ash::ShelfID shelf_id(app_id);
// Pin internal app.
controller()->PinAppWithID(app_id);
const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
ASSERT_TRUE(item);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
int64_t primary_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, primary_id);
const int expected_options_num = internal_app.show_in_launcher ? 2 : 1;
EXPECT_EQ(expected_options_num, menu->GetItemCount());
}
}
} // namespace