// Copyright 2013 The Chromium Authors
// 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/shelf/shelf_context_menu.h"
#include <memory>
#include <utility>
#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/test/fake_app_instance.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/shelf_model.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/apps/app_service/app_service_test.h"
#include "chrome/browser/ash/app_list/arc/arc_app_icon.h"
#include "chrome/browser/ash/app_list/arc/arc_app_test.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/app_list/internal_app/internal_app_metadata.h"
#include "chrome/browser/ash/arc/icon_decode_request.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/guest_os/guest_os_shelf_utils.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/arc_app_shelf_id.h"
#include "chrome/browser/ui/ash/shelf/browser_shortcut_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#include "chrome/browser/ui/ash/shelf/extension_shelf_context_menu.h"
#include "chrome/browser/ui/ash/shelf/shelf_controller_helper.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "components/exo/shell_surface_util.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/viz/test/test_gpu_service_holder.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"
#include "url/gurl.h"
using crostini::CrostiniTestHelper;
namespace {
ash::ShelfItemDelegate::AppMenuItems GetAppMenuItems(
ash::ShelfItemDelegate* delegate,
int event_flags) {
return delegate->GetAppMenuItems(event_flags, base::NullCallback());
}
bool IsItemPresentInMenu(ui::MenuModel* menu, int command_id) {
ui::MenuModel* model = menu;
size_t index = 0;
return ui::MenuModel::GetModelAndIndexForCommandId(command_id, &model,
&index);
}
bool IsItemEnabledInMenu(ui::MenuModel* menu, int command_id) {
ui::MenuModel* model = menu;
size_t 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);
}
} // namespace
class ShelfContextMenuTest : public ChromeAshTestBase {
public:
ShelfContextMenuTest(const ShelfContextMenuTest&) = delete;
ShelfContextMenuTest& operator=(const ShelfContextMenuTest&) = delete;
protected:
ShelfContextMenuTest() = default;
~ShelfContextMenuTest() override = default;
void SetUp() override {
ash::ChunneldClient::InitializeFake();
ash::CiceroneClient::InitializeFake();
ash::ConciergeClient::InitializeFake();
ash::SeneschalClient::InitializeFake();
ChromeAshTestBase::SetUp();
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kDisableDefaultApps);
profile_ = std::make_unique<TestingProfile>();
extensions::TestExtensionSystem* extension_system(
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile())));
extension_service_ = extension_system->CreateExtensionService(
base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
extension_service_->Init();
web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
crostini_helper_ = std::make_unique<CrostiniTestHelper>(profile());
crostini_helper_->ReInitializeAppServiceIntegration();
app_service_test_.SetUp(profile());
arc_test_.SetUp(profile());
model_ = std::make_unique<ash::ShelfModel>();
shelf_controller_ =
std::make_unique<ChromeShelfController>(profile(), model_.get());
shelf_controller_->SetProfileForTest(profile());
shelf_controller_->SetShelfControllerHelperForTest(
std::make_unique<ShelfControllerHelper>(profile()));
shelf_controller_->Init();
// Disable safe icon decoding to ensure ArcAppShortcutRequests returns in
// the test environment.
ArcAppIcon::DisableSafeDecodingForTesting();
arc::IconDecodeRequest::DisableSafeDecodingForTesting();
}
std::unique_ptr<ShelfContextMenu> CreateShelfContextMenu(
ash::ShelfItemType shelf_item_type,
int64_t display_id) {
ash::ShelfItem item;
item.id = ash::ShelfID("idmockidmockidmockidmockidmockid");
item.type = shelf_item_type;
return ShelfContextMenu::Create(controller(), &item, display_id);
}
// Creates app window and set optional ARC application id.
views::Widget* CreateArcWindow(const std::string& window_app_id) {
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW);
views::Widget* widget = new views::Widget();
params.context = GetContext();
widget->Init(std::move(params));
widget->Show();
widget->Activate();
exo::SetShellApplicationId(widget->GetNativeWindow(), window_app_id);
return widget;
}
std::unique_ptr<ui::MenuModel> GetMenuModel(
ShelfContextMenu* shelf_context_menu) {
base::RunLoop run_loop;
std::unique_ptr<ui::MenuModel> menu;
shelf_context_menu->GetMenuModel(base::BindLambdaForTesting(
[&](std::unique_ptr<ui::SimpleMenuModel> 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::SimpleMenuModel> created_menu) {
menu = std::move(created_menu);
run_loop.Quit();
}));
run_loop.Run();
return menu;
}
void TearDown() override {
shelf_controller_.reset();
arc_test_.TearDown();
crostini_helper_.reset();
profile_.reset();
ChromeAshTestBase::TearDown();
ash::SeneschalClient::Shutdown();
ash::ConciergeClient::Shutdown();
ash::CiceroneClient::Shutdown();
ash::ChunneldClient::Shutdown();
}
ArcAppTest& arc_test() { return arc_test_; }
apps::AppServiceTest& app_service_test() { return app_service_test_; }
TestingProfile* profile() { return profile_.get(); }
CrostiniTestHelper* crostini_helper() { return crostini_helper_.get(); }
ChromeShelfController* controller() { return shelf_controller_.get(); }
ash::ShelfModel* model() { return model_.get(); }
void SendRefreshAppList(const std::vector<arc::mojom::AppInfoPtr>& apps) {
arc_test_.app_instance()->SendRefreshAppList(apps);
}
void LaunchApp(const std::string& app_id,
const arc::mojom::AppInfo& info,
int32_t task_id) {
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
// AppService checks the task id to decide whether the app is running, so
// create the task id to simulate the running app.
arc_test_.app_instance()->SendTaskCreated(task_id, info, std::string());
}
private:
// CrostiniTestHelper overrides feature list after GPU thread has started.
viz::TestGpuServiceHolder::ScopedAllowRacyFeatureListOverrides
gpu_thread_allow_racy_overrides_;
base::test::ScopedCommandLine scoped_command_line_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<CrostiniTestHelper> crostini_helper_;
ArcAppTest arc_test_;
apps::AppServiceTest app_service_test_;
std::unique_ptr<ash::ShelfModel> model_;
std::unique_ptr<ChromeShelfController> shelf_controller_;
raw_ptr<extensions::ExtensionService, DanglingUntriaged> extension_service_ =
nullptr;
};
// 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(ShelfContextMenuTest,
NewIncognitoWindowMenuIsDisabledWhenIncognitoModeOff) {
const int64_t display_id = GetPrimaryDisplay().id();
// Initially, "New Incognito window" should be enabled.
std::unique_ptr<ShelfContextMenu> shelf_context_menu =
CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
std::unique_ptr<ui::MenuModel> menu = GetMenuModel(shelf_context_menu.get());
ASSERT_TRUE(IsItemPresentInMenu(menu.get(),
ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW));
EXPECT_TRUE(shelf_context_menu->IsCommandIdEnabled(
ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW));
// Disable Incognito mode.
IncognitoModePrefs::SetAvailability(
profile()->GetPrefs(), policy::IncognitoModeAvailability::kDisabled);
shelf_context_menu =
CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
menu = GetMenuModel(shelf_context_menu.get());
// The item should be disabled, and therefore not added to the menu.
EXPECT_FALSE(IsItemPresentInMenu(menu.get(),
ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW));
EXPECT_FALSE(shelf_context_menu->IsCommandIdEnabled(
ash::APP_CONTEXT_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(ShelfContextMenuTest, NewWindowMenuIsDisabledWhenIncognitoModeForced) {
const int64_t display_id = GetPrimaryDisplay().id();
// Initially, "New window" should be enabled.
std::unique_ptr<ShelfContextMenu> shelf_context_menu =
CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
std::unique_ptr<ui::MenuModel> menu = GetMenuModel(shelf_context_menu.get());
ASSERT_TRUE(
IsItemPresentInMenu(menu.get(), ash::APP_CONTEXT_MENU_NEW_WINDOW));
EXPECT_TRUE(
shelf_context_menu->IsCommandIdEnabled(ash::APP_CONTEXT_MENU_NEW_WINDOW));
// Disable Incognito mode.
IncognitoModePrefs::SetAvailability(
profile()->GetPrefs(), policy::IncognitoModeAvailability::kForced);
shelf_context_menu =
CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
menu = GetMenuModel(shelf_context_menu.get());
ASSERT_FALSE(
IsItemPresentInMenu(menu.get(), ash::APP_CONTEXT_MENU_NEW_WINDOW));
EXPECT_FALSE(
shelf_context_menu->IsCommandIdEnabled(ash::APP_CONTEXT_MENU_NEW_WINDOW));
}
// Verifies that "Close" is not shown in context menu if no browser window is
// opened.
TEST_F(ShelfContextMenuTest, DesktopShellShelfContextMenuVerifyCloseItem) {
const int64_t display_id = GetPrimaryDisplay().id();
std::unique_ptr<ShelfContextMenu> shelf_context_menu =
CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
std::unique_ptr<ui::MenuModel> menu = GetMenuModel(shelf_context_menu.get());
ASSERT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
}
// Verifies context menu and app menu items for ARC app.
// The 0th item is sticky but not the following.
TEST_F(ShelfContextMenuTest, ArcLauncherMenusCheck) {
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc_test().fake_apps()[0]->Clone());
arc_test().app_instance()->SendRefreshAppList(apps);
const std::string app_id = ArcAppTest::GetAppId(*arc_test().fake_apps()[0]);
const std::string app_name = arc_test().fake_apps()[0]->name;
PinAppWithIDToShelf(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(GetAppMenuItems(item_delegate, 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::LAUNCH_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
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);
auto menu_list = GetAppMenuItems(item_delegate, 0 /* event_flags */);
ASSERT_EQ(1U, menu_list.size());
EXPECT_EQ(base::UTF8ToUTF16(app_name), menu_list[0].title);
menu = GetContextMenu(item_delegate, display_id);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::LAUNCH_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
// 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 = GetAppMenuItems(item_delegate2, 0 /* event_flags */);
ASSERT_EQ(1U, menu_list.size());
EXPECT_EQ(base::UTF8ToUTF16(app_name2), menu_list[0].title);
menu = GetContextMenu(item_delegate2, display_id);
ASSERT_TRUE(menu);
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::LAUNCH_NEW));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
// 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::LAUNCH_NEW));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
menu_list = GetAppMenuItems(item_delegate3, 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].title);
}
}
}
TEST_F(ShelfContextMenuTest, ArcLauncherSuspendAppMenu) {
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc_test().fake_apps()[0]->Clone())->suspended = true;
SendRefreshAppList(apps);
const std::string app_id = ArcAppTest::GetAppId(*apps[0]);
PinAppWithIDToShelf(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(GetAppMenuItems(item_delegate, 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::LAUNCH_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
}
TEST_F(ShelfContextMenuTest, ArcDeferredShelfContextMenuItemCheck) {
std::vector<arc::mojom::AppInfoPtr> apps;
apps.reserve(2);
for (int i = 0; i < 2; i++)
apps.emplace_back(arc_test().fake_apps()[i]->Clone());
SendRefreshAppList(apps);
const std::string app_id1 = ArcAppTest::GetAppId(*apps[0]);
const std::string app_id2 = ArcAppTest::GetAppId(*apps[1]);
PinAppWithIDToShelf(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));
LaunchApp(app_id1, *apps[0], 1);
LaunchApp(app_id2, *apps[1], 2);
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::LAUNCH_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
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::LAUNCH_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
}
TEST_F(ShelfContextMenuTest, CommandIdsMatchEnumsForHistograms) {
// Tests that CommandId enums are not changed as the values are used in
// histograms.
EXPECT_EQ(1, ash::MENU_CLOSE);
EXPECT_EQ(9, ash::NOTIFICATION_CONTAINER);
EXPECT_EQ(100, ash::LAUNCH_NEW);
EXPECT_EQ(101, ash::TOGGLE_PIN);
EXPECT_EQ(106, ash::APP_CONTEXT_MENU_NEW_WINDOW);
EXPECT_EQ(107, ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW);
EXPECT_EQ(200, ash::DEPRECATED_USE_LAUNCH_TYPE_PINNED);
EXPECT_EQ(201, ash::USE_LAUNCH_TYPE_REGULAR);
EXPECT_EQ(202, ash::DEPRECATED_USE_LAUNCH_TYPE_FULLSCREEN);
EXPECT_EQ(203, ash::USE_LAUNCH_TYPE_WINDOW);
EXPECT_EQ(204, ash::USE_LAUNCH_TYPE_TABBED_WINDOW);
}
TEST_F(ShelfContextMenuTest, ArcContextMenuOptions) {
// Tests that there are the right number of ARC app context menu options. If
// you're adding a context menu option ensure that you have added the enum to
// tools/metrics/histograms/enums.xml and that you haven't modified the order
// of the existing enums.
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc_test().fake_apps()[0]->Clone());
SendRefreshAppList(apps);
const std::string app_id = ArcAppTest::GetAppId(*apps[0]);
const ash::ShelfID shelf_id(app_id);
PinAppWithIDToShelf(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 9 items in an ARC app context menu.
EXPECT_EQ(9u, menu->GetItemCount());
}
// Tests that the context menu of internal app is correct.
TEST_F(ShelfContextMenuTest, InternalAppShelfContextMenu) {
const std::vector<app_list::InternalApp> internal_apps(
app_list::GetInternalAppList(profile()));
for (const auto& internal_app : internal_apps) {
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.
PinAppWithIDToShelf(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::LAUNCH_NEW));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
}
}
// Tests that the number of context menu options of internal app is correct.
TEST_F(ShelfContextMenuTest, InternalAppShelfContextMenuOptionsNumber) {
const std::vector<app_list::InternalApp> internal_apps(
app_list::GetInternalAppList(profile()));
for (const auto& internal_app : internal_apps) {
const std::string app_id = internal_app.app_id;
const ash::ShelfID shelf_id(app_id);
// Pin internal app.
PinAppWithIDToShelf(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 size_t expected_options_num = internal_app.show_in_launcher ? 2 : 1;
EXPECT_EQ(expected_options_num, menu->GetItemCount());
}
}
// Checks the context menu for a "normal" crostini app (i.e. a registered one).
// Particularly, we ensure that the density changing option exists.
// TODO(crbug.com/40168664) Re-enable test
TEST_F(ShelfContextMenuTest, CrostiniNormalApp) {
const std::string app_name = "foo";
crostini_helper()->AddApp(crostini::CrostiniTestHelper::BasicApp(app_name));
const std::string app_id =
crostini::CrostiniTestHelper::GenerateAppId(app_name);
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile())
->AppLaunched(app_id);
PinAppWithIDToShelf(app_id);
const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
ASSERT_TRUE(item);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(ash::ShelfID(app_id));
ASSERT_TRUE(item_delegate);
int64_t primary_id = GetPrimaryDisplay().id();
// We force a scale factor of 2.0, to check that the normal app has a menu
// option to change the dpi settings.
UpdateDisplay("1920x1080*2.0");
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, primary_id);
// Check that every menu item has an icon
for (size_t i = 0; i < menu->GetItemCount(); ++i)
EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
// Precisely which density option is shown is not important to us, we only
// care that one is shown.
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::CROSTINI_USE_LOW_DENSITY) ||
IsItemEnabledInMenu(menu.get(), ash::CROSTINI_USE_HIGH_DENSITY));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
}
// Confirms the menu items for unregistered crostini apps (i.e. apps that do not
// have an associated .desktop file, and therefore can only be closed).
TEST_F(ShelfContextMenuTest, CrostiniUnregisteredApps) {
const std::string fake_window_app_id = "foo";
const std::string fake_window_startup_id = "bar";
const std::string app_id = guest_os::GetGuestOsShelfAppId(
profile(), &fake_window_app_id, &fake_window_startup_id);
PinAppWithIDToShelf(app_id);
const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
ASSERT_TRUE(item);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(ash::ShelfID(app_id));
ASSERT_TRUE(item_delegate);
int64_t primary_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, primary_id);
EXPECT_EQ(menu->GetItemCount(), 1u);
EXPECT_FALSE(
IsItemEnabledInMenu(menu.get(), ash::APP_CONTEXT_MENU_NEW_WINDOW));
}
TEST_F(ShelfContextMenuTest, WebApp) {
constexpr char kWebAppUrl[] = "https://webappone.com/";
constexpr char kWebAppName[] = "WebApp1";
const webapps::AppId app_id = web_app::test::InstallDummyWebApp(
profile(), kWebAppName, GURL(kWebAppUrl));
PinAppWithIDToShelf(app_id);
const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
ASSERT_TRUE(item);
ash::ShelfItemDelegate* item_delegate =
model()->GetShelfItemDelegate(ash::ShelfID(app_id));
ASSERT_TRUE(item_delegate);
int64_t primary_id = GetPrimaryDisplay().id();
std::unique_ptr<ui::MenuModel> menu =
GetContextMenu(item_delegate, primary_id);
// Check that every menu item has an icon
for (size_t i = 0; i < menu->GetItemCount(); ++i)
EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::TOGGLE_PIN));
}