1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/ash/launcher/shelf_context_menu.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "ash/public/cpp/app_menu_constants.h"
11 #include "ash/public/cpp/shelf_item.h"
12 #include "ash/public/cpp/shelf_model.h"
13 #include "base/files/file_path.h"
14 #include "base/macros.h"
15 #include "base/run_loop.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/test/bind.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "chrome/browser/apps/app_service/app_service_test.h"
21 #include "chrome/browser/chromeos/arc/icon_decode_request.h"
22 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
23 #include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
24 #include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
25 #include "chrome/browser/chromeos/crostini/crostini_util.h"
26 #include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
27 #include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
28 #include "chrome/browser/extensions/extension_service.h"
29 #include "chrome/browser/extensions/test_extension_system.h"
30 #include "chrome/browser/installable/installable_metrics.h"
31 #include "chrome/browser/prefs/incognito_mode_prefs.h"
32 #include "chrome/browser/profiles/profile.h"
33 #include "chrome/browser/ui/app_list/arc/arc_app_icon.h"
34 #include "chrome/browser/ui/app_list/arc/arc_app_test.h"
35 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
36 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
37 #include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
38 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
39 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
40 #include "chrome/browser/ui/ash/launcher/extension_shelf_context_menu.h"
41 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
42 #include "chrome/browser/web_applications/test/test_system_web_app_manager.h"
43 #include "chrome/browser/web_applications/test/test_web_app_provider.h"
44 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
45 #include "chrome/test/base/chrome_ash_test_base.h"
46 #include "chrome/test/base/testing_profile.h"
47 #include "components/arc/metrics/arc_metrics_constants.h"
48 #include "components/arc/mojom/app.mojom.h"
49 #include "components/arc/test/fake_app_instance.h"
50 #include "components/exo/shell_surface_util.h"
51 #include "components/prefs/pref_service.h"
52 #include "components/session_manager/core/session_manager.h"
53 #include "ui/aura/window_event_dispatcher.h"
54 #include "ui/base/l10n/l10n_util.h"
55 #include "ui/display/display.h"
56 #include "ui/views/widget/widget.h"
57 #include "url/gurl.h"
58
59 using crostini::CrostiniTestHelper;
60
61 namespace {
62
GetAppMenuItems(ash::ShelfItemDelegate * delegate,int event_flags)63 ash::ShelfItemDelegate::AppMenuItems GetAppMenuItems(
64 ash::ShelfItemDelegate* delegate,
65 int event_flags) {
66 return delegate->GetAppMenuItems(event_flags, base::NullCallback());
67 }
68
IsItemPresentInMenu(ui::MenuModel * menu,int command_id)69 bool IsItemPresentInMenu(ui::MenuModel* menu, int command_id) {
70 ui::MenuModel* model = menu;
71 int index = 0;
72 return ui::MenuModel::GetModelAndIndexForCommandId(command_id, &model,
73 &index);
74 }
75
IsItemEnabledInMenu(ui::MenuModel * menu,int command_id)76 bool IsItemEnabledInMenu(ui::MenuModel* menu, int command_id) {
77 ui::MenuModel* model = menu;
78 int index = 0;
79 return ui::MenuModel::GetModelAndIndexForCommandId(command_id, &model,
80 &index) &&
81 menu->IsEnabledAt(index);
82 }
83
GetAppNameInShelfGroup(uint32_t task_id)84 std::string GetAppNameInShelfGroup(uint32_t task_id) {
85 return base::StringPrintf("AppInShelfGroup%d", task_id);
86 }
87
88 class ShelfContextMenuTest : public ChromeAshTestBase {
89 protected:
90 ShelfContextMenuTest() = default;
91 ~ShelfContextMenuTest() override = default;
92
SetUp()93 void SetUp() override {
94 ChromeAshTestBase::SetUp();
95
96 extensions::TestExtensionSystem* extension_system(
97 static_cast<extensions::TestExtensionSystem*>(
98 extensions::ExtensionSystem::Get(&profile_)));
99 extension_service_ = extension_system->CreateExtensionService(
100 base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
101 extension_service_->Init();
102
103 ConfigureWebAppProvider();
104 web_app::TestWebAppProvider::Get(&profile_)->Start();
105
106 crostini_helper_ = std::make_unique<CrostiniTestHelper>(profile());
107 crostini_helper_->ReInitializeAppServiceIntegration();
108
109 app_service_test_.SetUp(&profile_);
110 arc_test_.SetUp(&profile_);
111
112 session_manager_ = std::make_unique<session_manager::SessionManager>();
113 model_ = std::make_unique<ash::ShelfModel>();
114 launcher_controller_ =
115 std::make_unique<ChromeLauncherController>(&profile_, model_.get());
116 launcher_controller_->Init();
117
118 // Disable safe icon decoding to ensure ArcAppShortcutRequests returns in
119 // the test environment.
120 ArcAppIcon::DisableSafeDecodingForTesting();
121 arc::IconDecodeRequest::DisableSafeDecodingForTesting();
122 }
123
CreateShelfContextMenu(ash::ShelfItemType shelf_item_type,int64_t display_id)124 std::unique_ptr<ShelfContextMenu> CreateShelfContextMenu(
125 ash::ShelfItemType shelf_item_type,
126 int64_t display_id) {
127 ash::ShelfItem item;
128 item.id = ash::ShelfID("idmockidmockidmockidmockidmockid");
129 item.type = shelf_item_type;
130 return ShelfContextMenu::Create(controller(), &item, display_id);
131 }
132
133 // Creates app window and set optional ARC application id.
CreateArcWindow(const std::string & window_app_id)134 views::Widget* CreateArcWindow(const std::string& window_app_id) {
135 views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
136 views::Widget* widget = new views::Widget();
137 params.context = GetContext();
138 widget->Init(std::move(params));
139 widget->Show();
140 widget->Activate();
141 exo::SetShellApplicationId(widget->GetNativeWindow(), window_app_id);
142 return widget;
143 }
144
GetMenuModel(ShelfContextMenu * shelf_context_menu)145 std::unique_ptr<ui::MenuModel> GetMenuModel(
146 ShelfContextMenu* shelf_context_menu) {
147 base::RunLoop run_loop;
148 std::unique_ptr<ui::MenuModel> menu;
149 shelf_context_menu->GetMenuModel(base::BindLambdaForTesting(
150 [&](std::unique_ptr<ui::SimpleMenuModel> created_menu) {
151 menu = std::move(created_menu);
152 run_loop.Quit();
153 }));
154 run_loop.Run();
155 return menu;
156 }
157
GetContextMenu(ash::ShelfItemDelegate * item_delegate,int64_t display_id)158 std::unique_ptr<ui::MenuModel> GetContextMenu(
159 ash::ShelfItemDelegate* item_delegate,
160 int64_t display_id) {
161 base::RunLoop run_loop;
162 std::unique_ptr<ui::MenuModel> menu;
163 item_delegate->GetContextMenu(
164 display_id, base::BindLambdaForTesting(
165 [&](std::unique_ptr<ui::SimpleMenuModel> created_menu) {
166 menu = std::move(created_menu);
167 run_loop.Quit();
168 }));
169 run_loop.Run();
170 return menu;
171 }
172
TearDown()173 void TearDown() override {
174 launcher_controller_.reset();
175
176 arc_test_.TearDown();
177
178 crostini_helper_.reset();
179
180 ChromeAshTestBase::TearDown();
181 }
182
arc_test()183 ArcAppTest& arc_test() { return arc_test_; }
184
app_service_test()185 apps::AppServiceTest& app_service_test() { return app_service_test_; }
186
profile()187 TestingProfile* profile() { return &profile_; }
188
crostini_helper()189 CrostiniTestHelper* crostini_helper() { return crostini_helper_.get(); }
190
controller()191 ChromeLauncherController* controller() { return launcher_controller_.get(); }
192
model()193 ash::ShelfModel* model() { return model_.get(); }
194
SendRefreshAppList(const std::vector<arc::mojom::AppInfo> & apps)195 void SendRefreshAppList(const std::vector<arc::mojom::AppInfo>& apps) {
196 arc_test_.app_instance()->SendRefreshAppList(apps);
197 app_service_test_.FlushMojoCalls();
198 }
199
LaunchApp(const std::string & app_id,const arc::mojom::AppInfo & info,int32_t task_id)200 void LaunchApp(const std::string& app_id,
201 const arc::mojom::AppInfo& info,
202 int32_t task_id) {
203 arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
204 arc::UserInteractionType::NOT_USER_INITIATED);
205
206 // AppService checks the task id to decide whether the app is running, so
207 // create the task id to simulate the running app.
208 arc_test_.app_instance()->SendTaskCreated(task_id, info, std::string());
209 }
210
211 private:
ConfigureWebAppProvider()212 void ConfigureWebAppProvider() {
213 auto system_web_app_manager =
214 std::make_unique<web_app::TestSystemWebAppManager>(&profile_);
215
216 auto* provider = web_app::TestWebAppProvider::Get(&profile_);
217 provider->SetSystemWebAppManager(std::move(system_web_app_manager));
218 provider->SetRunSubsystemStartupTasks(true);
219 }
220
221 TestingProfile profile_;
222 std::unique_ptr<CrostiniTestHelper> crostini_helper_;
223 ArcAppTest arc_test_;
224 apps::AppServiceTest app_service_test_;
225 std::unique_ptr<session_manager::SessionManager> session_manager_;
226 std::unique_ptr<ash::ShelfModel> model_;
227 std::unique_ptr<ChromeLauncherController> launcher_controller_;
228 extensions::ExtensionService* extension_service_ = nullptr;
229
230 DISALLOW_COPY_AND_ASSIGN(ShelfContextMenuTest);
231 };
232
233 // Verifies that "New Incognito window" menu item in the launcher context
234 // menu is disabled when Incognito mode is switched off (by a policy).
TEST_F(ShelfContextMenuTest,NewIncognitoWindowMenuIsDisabledWhenIncognitoModeOff)235 TEST_F(ShelfContextMenuTest,
236 NewIncognitoWindowMenuIsDisabledWhenIncognitoModeOff) {
237 const int64_t display_id = GetPrimaryDisplay().id();
238 // Initially, "New Incognito window" should be enabled.
239 std::unique_ptr<ShelfContextMenu> shelf_context_menu =
240 CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
241 std::unique_ptr<ui::MenuModel> menu = GetMenuModel(shelf_context_menu.get());
242 ASSERT_TRUE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_INCOGNITO_WINDOW));
243 EXPECT_TRUE(
244 shelf_context_menu->IsCommandIdEnabled(ash::MENU_NEW_INCOGNITO_WINDOW));
245
246 // Disable Incognito mode.
247 IncognitoModePrefs::SetAvailability(profile()->GetPrefs(),
248 IncognitoModePrefs::DISABLED);
249 shelf_context_menu =
250 CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
251 menu = GetMenuModel(shelf_context_menu.get());
252 // The item should be disabled, and therefore not added to the menu.
253 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_INCOGNITO_WINDOW));
254 EXPECT_FALSE(
255 shelf_context_menu->IsCommandIdEnabled(ash::MENU_NEW_INCOGNITO_WINDOW));
256 }
257
258 // Verifies that "New window" menu item in the launcher context
259 // menu is disabled when Incognito mode is forced (by a policy).
TEST_F(ShelfContextMenuTest,NewWindowMenuIsDisabledWhenIncognitoModeForced)260 TEST_F(ShelfContextMenuTest, NewWindowMenuIsDisabledWhenIncognitoModeForced) {
261 const int64_t display_id = GetPrimaryDisplay().id();
262 // Initially, "New window" should be enabled.
263 std::unique_ptr<ShelfContextMenu> shelf_context_menu =
264 CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
265 std::unique_ptr<ui::MenuModel> menu = GetMenuModel(shelf_context_menu.get());
266 ASSERT_TRUE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_WINDOW));
267 EXPECT_TRUE(shelf_context_menu->IsCommandIdEnabled(ash::MENU_NEW_WINDOW));
268
269 // Disable Incognito mode.
270 IncognitoModePrefs::SetAvailability(profile()->GetPrefs(),
271 IncognitoModePrefs::FORCED);
272 shelf_context_menu =
273 CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
274 menu = GetMenuModel(shelf_context_menu.get());
275 ASSERT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_NEW_WINDOW));
276 EXPECT_FALSE(shelf_context_menu->IsCommandIdEnabled(ash::MENU_NEW_WINDOW));
277 }
278
279 // Verifies that "Close" is not shown in context menu if no browser window is
280 // opened.
TEST_F(ShelfContextMenuTest,DesktopShellShelfContextMenuVerifyCloseItem)281 TEST_F(ShelfContextMenuTest, DesktopShellShelfContextMenuVerifyCloseItem) {
282 const int64_t display_id = GetPrimaryDisplay().id();
283 std::unique_ptr<ShelfContextMenu> shelf_context_menu =
284 CreateShelfContextMenu(ash::TYPE_BROWSER_SHORTCUT, display_id);
285 std::unique_ptr<ui::MenuModel> menu = GetMenuModel(shelf_context_menu.get());
286 ASSERT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
287 }
288
289 // Verifies context menu and app menu items for ARC app.
290 // The 0th item is sticky but not the following.
TEST_F(ShelfContextMenuTest,ArcLauncherMenusCheck)291 TEST_F(ShelfContextMenuTest, ArcLauncherMenusCheck) {
292 arc_test().app_instance()->SendRefreshAppList(
293 std::vector<arc::mojom::AppInfo>(arc_test().fake_apps().begin(),
294 arc_test().fake_apps().begin() + 1));
295 app_service_test().WaitForAppService();
296 const std::string app_id = ArcAppTest::GetAppId(arc_test().fake_apps()[0]);
297 const std::string app_name = arc_test().fake_apps()[0].name;
298
299 controller()->PinAppWithID(app_id);
300
301 const ash::ShelfID shelf_id(app_id);
302 const ash::ShelfItem* item = controller()->GetItem(shelf_id);
303 ASSERT_TRUE(item);
304 EXPECT_EQ(base::UTF8ToUTF16(app_name), item->title);
305 ash::ShelfItemDelegate* item_delegate =
306 model()->GetShelfItemDelegate(shelf_id);
307 ASSERT_TRUE(item_delegate);
308 EXPECT_TRUE(GetAppMenuItems(item_delegate, 0 /* event_flags */).empty());
309
310 const int64_t display_id = GetPrimaryDisplay().id();
311 std::unique_ptr<ui::MenuModel> menu =
312 GetContextMenu(item_delegate, display_id);
313 ASSERT_TRUE(menu);
314
315 // ARC app is pinned but not running.
316 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_OPEN_NEW));
317 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
318 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
319 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
320 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
321
322 // ARC app is running.
323 std::string window_app_id1("org.chromium.arc.1");
324 CreateArcWindow(window_app_id1);
325 arc_test().app_instance()->SendTaskCreated(1, arc_test().fake_apps()[0],
326 std::string());
327 app_service_test().WaitForAppService();
328
329 item_delegate = model()->GetShelfItemDelegate(shelf_id);
330 ASSERT_TRUE(item_delegate);
331 auto menu_list = GetAppMenuItems(item_delegate, 0 /* event_flags */);
332 ASSERT_EQ(1U, menu_list.size());
333 EXPECT_EQ(base::UTF8ToUTF16(app_name), menu_list[0].title);
334
335 menu = GetContextMenu(item_delegate, display_id);
336 ASSERT_TRUE(menu);
337
338 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
339 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
340 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
341 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
342 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
343
344 // ARC non-launchable app is running.
345 const std::string app_id2 = ArcAppTest::GetAppId(arc_test().fake_apps()[1]);
346 const std::string app_name2 = arc_test().fake_apps()[1].name;
347 std::string window_app_id2("org.chromium.arc.2");
348 CreateArcWindow(window_app_id2);
349 arc_test().app_instance()->SendTaskCreated(2, arc_test().fake_apps()[1],
350 std::string());
351 app_service_test().WaitForAppService();
352 const ash::ShelfID shelf_id2(app_id2);
353 const ash::ShelfItem* item2 = controller()->GetItem(shelf_id2);
354 ASSERT_TRUE(item2);
355 EXPECT_EQ(base::UTF8ToUTF16(app_name2), item2->title);
356 ash::ShelfItemDelegate* item_delegate2 =
357 model()->GetShelfItemDelegate(shelf_id2);
358 ASSERT_TRUE(item_delegate2);
359
360 menu_list = GetAppMenuItems(item_delegate2, 0 /* event_flags */);
361 ASSERT_EQ(1U, menu_list.size());
362 EXPECT_EQ(base::UTF8ToUTF16(app_name2), menu_list[0].title);
363
364 menu = GetContextMenu(item_delegate2, display_id);
365 ASSERT_TRUE(menu);
366
367 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
368 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_PIN));
369 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
370 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
371 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
372
373 // Shelf group context menu.
374 std::vector<arc::mojom::ShortcutInfo> shortcuts = arc_test().fake_shortcuts();
375 shortcuts[0].intent_uri +=
376 ";S.org.chromium.arc.shelf_group_id=arc_test_shelf_group;end";
377 arc_test().app_instance()->SendInstallShortcuts(shortcuts);
378 app_service_test().WaitForAppService();
379 const std::string app_id3 =
380 arc::ArcAppShelfId("arc_test_shelf_group",
381 ArcAppTest::GetAppId(arc_test().fake_apps()[2]))
382 .ToString();
383
384 constexpr int apps_to_test_in_shelf_group = 2;
385 const std::string app_name3 = arc_test().fake_apps()[2].name;
386 for (uint32_t i = 0; i < apps_to_test_in_shelf_group; ++i) {
387 const uint32_t task_id = 3 + i;
388 std::string window_app_id3 =
389 base::StringPrintf("org.chromium.arc.%d", task_id);
390 CreateArcWindow(window_app_id3);
391 arc_test().app_instance()->SendTaskCreated(
392 task_id, arc_test().fake_apps()[2], shortcuts[0].intent_uri);
393 // Set custom name.
394 arc_test().app_instance()->SendTaskDescription(
395 task_id, GetAppNameInShelfGroup(task_id),
396 std::string() /* icon_png_data_as_string */);
397 app_service_test().WaitForAppService();
398 const ash::ShelfID shelf_id3(app_id3);
399 const ash::ShelfItem* item3 = controller()->GetItem(shelf_id3);
400 ASSERT_TRUE(item3);
401
402 // Validate item label is correct
403 EXPECT_EQ(base::UTF8ToUTF16(app_name3), item3->title);
404
405 ash::ShelfItemDelegate* item_delegate3 =
406 model()->GetShelfItemDelegate(shelf_id3);
407 ASSERT_TRUE(item_delegate3);
408
409 menu = GetContextMenu(item_delegate3, display_id);
410 ASSERT_TRUE(menu);
411
412 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
413 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_PIN));
414 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
415 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
416 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
417
418 menu_list = GetAppMenuItems(item_delegate3, 0 /* event_flags */);
419 ASSERT_EQ(i + 1, menu_list.size());
420
421 // Ensure custom names are set in the app menu items. Note, they are
422 // in reverse order, based on activation order.
423 for (uint32_t j = 0; j <= i; ++j) {
424 EXPECT_EQ(base::UTF8ToUTF16(GetAppNameInShelfGroup(3 + j)),
425 menu_list[i - j].title);
426 }
427 }
428 }
429
TEST_F(ShelfContextMenuTest,ArcLauncherSuspendAppMenu)430 TEST_F(ShelfContextMenuTest, ArcLauncherSuspendAppMenu) {
431 arc::mojom::AppInfo app = arc_test().fake_apps()[0];
432 app.suspended = true;
433 SendRefreshAppList({app});
434 const std::string app_id = ArcAppTest::GetAppId(app);
435
436 controller()->PinAppWithID(app_id);
437
438 const ash::ShelfID shelf_id(app_id);
439 const ash::ShelfItem* item = controller()->GetItem(shelf_id);
440 ASSERT_TRUE(item);
441 ash::ShelfItemDelegate* item_delegate =
442 model()->GetShelfItemDelegate(shelf_id);
443 ASSERT_TRUE(item_delegate);
444 EXPECT_TRUE(GetAppMenuItems(item_delegate, 0 /* event_flags */).empty());
445
446 const int64_t display_id = GetPrimaryDisplay().id();
447 std::unique_ptr<ui::MenuModel> menu =
448 GetContextMenu(item_delegate, display_id);
449 ASSERT_TRUE(menu);
450
451 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
452 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
453 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
454 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
455 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
456 }
457
TEST_F(ShelfContextMenuTest,ArcDeferredShelfContextMenuItemCheck)458 TEST_F(ShelfContextMenuTest, ArcDeferredShelfContextMenuItemCheck) {
459 SendRefreshAppList(std::vector<arc::mojom::AppInfo>(
460 arc_test().fake_apps().begin(), arc_test().fake_apps().begin() + 2));
461 const std::string app_id1 = ArcAppTest::GetAppId(arc_test().fake_apps()[0]);
462 const std::string app_id2 = ArcAppTest::GetAppId(arc_test().fake_apps()[1]);
463
464 controller()->PinAppWithID(app_id1);
465
466 arc_test().StopArcInstance();
467
468 const ash::ShelfID shelf_id1(app_id1);
469 const ash::ShelfID shelf_id2(app_id2);
470
471 EXPECT_TRUE(controller()->GetItem(shelf_id1));
472 EXPECT_FALSE(controller()->GetItem(shelf_id2));
473
474 LaunchApp(app_id1, arc_test().fake_apps()[0], 1);
475 LaunchApp(app_id2, arc_test().fake_apps()[1], 2);
476
477 EXPECT_TRUE(controller()->GetItem(shelf_id1));
478 EXPECT_TRUE(controller()->GetItem(shelf_id2));
479
480 ash::ShelfItemDelegate* item_delegate =
481 model()->GetShelfItemDelegate(shelf_id1);
482 ASSERT_TRUE(item_delegate);
483 std::unique_ptr<ui::MenuModel> menu =
484 GetContextMenu(item_delegate, 0 /* display_id */);
485 ASSERT_TRUE(menu);
486
487 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
488 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
489 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
490 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
491 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
492
493 item_delegate = model()->GetShelfItemDelegate(shelf_id2);
494 ASSERT_TRUE(item_delegate);
495 menu = GetContextMenu(item_delegate, 0 /* display_id */);
496 ASSERT_TRUE(menu);
497
498 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_OPEN_NEW));
499 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
500 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_CLOSE));
501 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
502 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
503 }
504
TEST_F(ShelfContextMenuTest,CommandIdsMatchEnumsForHistograms)505 TEST_F(ShelfContextMenuTest, CommandIdsMatchEnumsForHistograms) {
506 // Tests that CommandId enums are not changed as the values are used in
507 // histograms.
508 EXPECT_EQ(0, ash::MENU_OPEN_NEW);
509 EXPECT_EQ(1, ash::MENU_CLOSE);
510 EXPECT_EQ(2, ash::MENU_PIN);
511 EXPECT_EQ(3, ash::LAUNCH_TYPE_PINNED_TAB);
512 EXPECT_EQ(4, ash::LAUNCH_TYPE_REGULAR_TAB);
513 EXPECT_EQ(5, ash::LAUNCH_TYPE_FULLSCREEN);
514 EXPECT_EQ(6, ash::LAUNCH_TYPE_WINDOW);
515 EXPECT_EQ(7, ash::MENU_NEW_WINDOW);
516 EXPECT_EQ(8, ash::MENU_NEW_INCOGNITO_WINDOW);
517 EXPECT_EQ(9, ash::NOTIFICATION_CONTAINER);
518 }
519
TEST_F(ShelfContextMenuTest,ArcContextMenuOptions)520 TEST_F(ShelfContextMenuTest, ArcContextMenuOptions) {
521 // Tests that there are the right number of ARC app context menu options. If
522 // you're adding a context menu option ensure that you have added the enum to
523 // tools/metrics/histograms/enums.xml and that you haven't modified the order
524 // of the existing enums.
525 SendRefreshAppList(std::vector<arc::mojom::AppInfo>(
526 arc_test().fake_apps().begin(), arc_test().fake_apps().begin() + 1));
527 const std::string app_id = ArcAppTest::GetAppId(arc_test().fake_apps()[0]);
528 const ash::ShelfID shelf_id(app_id);
529
530 controller()->PinAppWithID(app_id);
531 const ash::ShelfItem* item = controller()->GetItem(shelf_id);
532 ASSERT_TRUE(item);
533 ash::ShelfItemDelegate* item_delegate =
534 model()->GetShelfItemDelegate(shelf_id);
535 ASSERT_TRUE(item_delegate);
536 int64_t primary_id = GetPrimaryDisplay().id();
537 std::unique_ptr<ui::MenuModel> menu =
538 GetContextMenu(item_delegate, primary_id);
539
540 // Test that there are 9 items in an ARC app context menu.
541 EXPECT_EQ(9, menu->GetItemCount());
542 }
543
544 // Tests that the context menu of internal app is correct.
TEST_F(ShelfContextMenuTest,InternalAppShelfContextMenu)545 TEST_F(ShelfContextMenuTest, InternalAppShelfContextMenu) {
546 const std::vector<app_list::InternalApp> internal_apps(
547 app_list::GetInternalAppList(profile()));
548 for (const auto& internal_app : internal_apps) {
549 if (!internal_app.show_in_launcher)
550 continue;
551
552 const std::string app_id = internal_app.app_id;
553 const ash::ShelfID shelf_id(app_id);
554 // Pin internal app.
555 controller()->PinAppWithID(app_id);
556 const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
557 ASSERT_TRUE(item);
558 EXPECT_EQ(l10n_util::GetStringUTF16(internal_app.name_string_resource_id),
559 item->title);
560 ash::ShelfItemDelegate* item_delegate =
561 model()->GetShelfItemDelegate(shelf_id);
562 ASSERT_TRUE(item_delegate);
563
564 const int64_t display_id = GetPrimaryDisplay().id();
565 std::unique_ptr<ui::MenuModel> menu =
566 GetContextMenu(item_delegate, display_id);
567 ASSERT_TRUE(menu);
568
569 // Internal app is pinned but not running.
570 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_OPEN_NEW));
571 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::MENU_PIN));
572 EXPECT_FALSE(IsItemPresentInMenu(menu.get(), ash::MENU_CLOSE));
573 }
574 }
575
576 // Tests that the number of context menu options of internal app is correct.
TEST_F(ShelfContextMenuTest,InternalAppShelfContextMenuOptionsNumber)577 TEST_F(ShelfContextMenuTest, InternalAppShelfContextMenuOptionsNumber) {
578 const std::vector<app_list::InternalApp> internal_apps(
579 app_list::GetInternalAppList(profile()));
580 for (const auto& internal_app : internal_apps) {
581 const std::string app_id = internal_app.app_id;
582 const ash::ShelfID shelf_id(app_id);
583 // Pin internal app.
584 controller()->PinAppWithID(app_id);
585 const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
586 ASSERT_TRUE(item);
587
588 ash::ShelfItemDelegate* item_delegate =
589 model()->GetShelfItemDelegate(shelf_id);
590 ASSERT_TRUE(item_delegate);
591 int64_t primary_id = GetPrimaryDisplay().id();
592 std::unique_ptr<ui::MenuModel> menu =
593 GetContextMenu(item_delegate, primary_id);
594
595 const int expected_options_num = internal_app.show_in_launcher ? 2 : 1;
596 EXPECT_EQ(expected_options_num, menu->GetItemCount());
597 }
598 }
599
600 // Checks some properties for crostini's terminal app's context menu,
601 // specifically that every menu item has an icon.
TEST_F(ShelfContextMenuTest,CrostiniTerminalApp)602 TEST_F(ShelfContextMenuTest, CrostiniTerminalApp) {
603 const std::string app_id = crostini::kCrostiniTerminalSystemAppId;
604 crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
605 crostini::kCrostiniDefaultVmName);
606
607 controller()->PinAppWithID(app_id);
608 const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
609 ASSERT_TRUE(item);
610
611 ash::ShelfItemDelegate* item_delegate =
612 model()->GetShelfItemDelegate(ash::ShelfID(app_id));
613 ASSERT_TRUE(item_delegate);
614 int64_t primary_id = GetPrimaryDisplay().id();
615 std::unique_ptr<ui::MenuModel> menu =
616 GetContextMenu(item_delegate, primary_id);
617
618 // Check that every menu item has an icon
619 for (int i = 0; i < menu->GetItemCount(); ++i)
620 EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
621
622 // When crostini is running, the terminal should have an option to kill the
623 // vm.
624 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::SHUTDOWN_GUEST_OS));
625 }
626
627 // Checks the context menu for a "normal" crostini app (i.e. a registered one).
628 // Particularly, we ensure that the density changing option exists.
TEST_F(ShelfContextMenuTest,CrostiniNormalApp)629 TEST_F(ShelfContextMenuTest, CrostiniNormalApp) {
630 const std::string app_name = "foo";
631 crostini_helper()->AddApp(crostini::CrostiniTestHelper::BasicApp(app_name));
632 app_service_test().FlushMojoCalls();
633 const std::string app_id =
634 crostini::CrostiniTestHelper::GenerateAppId(app_name);
635 guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile())
636 ->AppLaunched(app_id);
637
638 controller()->PinAppWithID(app_id);
639 const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
640 ASSERT_TRUE(item);
641
642 ash::ShelfItemDelegate* item_delegate =
643 model()->GetShelfItemDelegate(ash::ShelfID(app_id));
644 ASSERT_TRUE(item_delegate);
645 int64_t primary_id = GetPrimaryDisplay().id();
646
647 // We force a scale factor of 2.0, to check that the normal app has a menu
648 // option to change the dpi settings.
649 UpdateDisplay("1920x1080*2.0");
650
651 std::unique_ptr<ui::MenuModel> menu =
652 GetContextMenu(item_delegate, primary_id);
653
654 // Check that every menu item has an icon
655 for (int i = 0; i < menu->GetItemCount(); ++i)
656 EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
657
658 // Precisely which density option is shown is not important to us, we only
659 // care that one is shown.
660 EXPECT_TRUE(IsItemEnabledInMenu(menu.get(), ash::CROSTINI_USE_LOW_DENSITY) ||
661 IsItemEnabledInMenu(menu.get(), ash::CROSTINI_USE_HIGH_DENSITY));
662 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
663 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
664 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
665 }
666
667 // Confirms the menu items for unregistered crostini apps (i.e. apps that do not
668 // have an associated .desktop file, and therefore can only be closed).
TEST_F(ShelfContextMenuTest,CrostiniUnregisteredApps)669 TEST_F(ShelfContextMenuTest, CrostiniUnregisteredApps) {
670 const std::string fake_window_app_id = "foo";
671 const std::string fake_window_startup_id = "bar";
672 const std::string app_id = crostini::GetCrostiniShelfAppId(
673 profile(), &fake_window_app_id, &fake_window_startup_id);
674 controller()->PinAppWithID(app_id);
675 const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
676 ASSERT_TRUE(item);
677
678 ash::ShelfItemDelegate* item_delegate =
679 model()->GetShelfItemDelegate(ash::ShelfID(app_id));
680 ASSERT_TRUE(item_delegate);
681 int64_t primary_id = GetPrimaryDisplay().id();
682 std::unique_ptr<ui::MenuModel> menu =
683 GetContextMenu(item_delegate, primary_id);
684
685 EXPECT_EQ(menu->GetItemCount(), 1);
686 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::MENU_NEW_WINDOW));
687 }
688
TEST_F(ShelfContextMenuTest,WebApp)689 TEST_F(ShelfContextMenuTest, WebApp) {
690 constexpr char kWebAppUrl[] = "https://webappone.com/";
691 constexpr char kWebAppName[] = "WebApp1";
692
693 app_service_test().FlushMojoCalls();
694 const web_app::AppId app_id =
695 web_app::InstallDummyWebApp(profile(), kWebAppName, GURL(kWebAppUrl));
696
697 controller()->PinAppWithID(app_id);
698 const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
699 ASSERT_TRUE(item);
700
701 ash::ShelfItemDelegate* item_delegate =
702 model()->GetShelfItemDelegate(ash::ShelfID(app_id));
703 ASSERT_TRUE(item_delegate);
704 int64_t primary_id = GetPrimaryDisplay().id();
705 std::unique_ptr<ui::MenuModel> menu =
706 GetContextMenu(item_delegate, primary_id);
707
708 // Check that every menu item has an icon
709 for (int i = 0; i < menu->GetItemCount(); ++i)
710 EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
711
712 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
713 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::SHOW_APP_INFO));
714 EXPECT_FALSE(IsItemEnabledInMenu(menu.get(), ash::UNINSTALL));
715 }
716
717 } // namespace
718