1 // Copyright 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.
5 #import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/command_line.h"
10 #import "base/mac/foundation_util.h"
11 #import "base/mac/scoped_nsobject.h"
12 #import "base/mac/scoped_objc_class_swizzler.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/apps/app_browsertest_util.h"
16 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/extensions/launch_util.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/browser_iterator.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "extensions/browser/app_window/app_window_registry.h"
24 #include "extensions/browser/app_window/native_app_window.h"
25 #include "extensions/browser/uninstall_reason.h"
26 #include "extensions/common/extension.h"
27 #include "extensions/test/extension_test_message_listener.h"
28 #import "ui/base/test/scoped_fake_nswindow_focus.h"
32 class AppShimMenuControllerBrowserTest
33 : public extensions::PlatformAppBrowserTest {
35 // The apps that can be installed and launched by SetUpApps().
36 enum AvailableApps { PACKAGED_1 = 0x1, PACKAGED_2 = 0x2, HOSTED = 0x4 };
38 AppShimMenuControllerBrowserTest()
42 initial_menu_item_count_(0) {}
44 void SetUpCommandLine(base::CommandLine* command_line) override {
45 PlatformAppBrowserTest::SetUpCommandLine(command_line);
46 command_line->AppendSwitch(switches::kEnableNewBookmarkApps);
49 // Start testing apps and wait for them to launch. |flags| is a bitmask of
51 void SetUpApps(int flags) {
53 if (flags & PACKAGED_1) {
54 ExtensionTestMessageListener listener_1("Launched", false);
55 app_1_ = InstallAndLaunchPlatformApp("minimal_id");
56 ASSERT_TRUE(listener_1.WaitUntilSatisfied());
59 if (flags & PACKAGED_2) {
60 ExtensionTestMessageListener listener_2("Launched", false);
61 app_2_ = InstallAndLaunchPlatformApp("minimal");
62 ASSERT_TRUE(listener_2.WaitUntilSatisfied());
66 hosted_app_ = InstallHostedApp();
68 // Explicitly set the launch type to open in a new window.
69 extensions::SetLaunchType(profile(), hosted_app_->id(),
70 extensions::LAUNCH_TYPE_WINDOW);
71 LaunchHostedApp(hosted_app_);
74 initial_menu_item_count_ = [[[NSApp mainMenu] itemArray] count];
77 void CheckHasAppMenus(const extensions::Extension* app) const {
78 const int kExtraTopLevelItems = 4;
79 NSArray* item_array = [[NSApp mainMenu] itemArray];
80 ASSERT_EQ(initial_menu_item_count_ + kExtraTopLevelItems,
82 for (NSUInteger i = 0; i < initial_menu_item_count_; ++i)
83 EXPECT_TRUE([[item_array objectAtIndex:i] isHidden]);
84 NSMenuItem* app_menu = [item_array objectAtIndex:initial_menu_item_count_];
85 EXPECT_EQ(app->id(), base::SysNSStringToUTF8([app_menu title]));
86 EXPECT_EQ(app->name(),
87 base::SysNSStringToUTF8([[app_menu submenu] title]));
88 for (NSUInteger i = initial_menu_item_count_;
89 i < initial_menu_item_count_ + kExtraTopLevelItems;
91 NSMenuItem* menu = [item_array objectAtIndex:i];
92 EXPECT_GT([[menu submenu] numberOfItems], 0);
93 EXPECT_FALSE([menu isHidden]);
97 void CheckNoAppMenus() const {
98 NSArray* item_array = [[NSApp mainMenu] itemArray];
99 EXPECT_EQ(initial_menu_item_count_, [item_array count]);
100 for (NSUInteger i = 0; i < initial_menu_item_count_; ++i)
101 EXPECT_FALSE([[item_array objectAtIndex:i] isHidden]);
104 void CheckEditMenu(const extensions::Extension* app) const {
105 const int edit_menu_index = initial_menu_item_count_ + 2;
107 NSMenuItem* edit_menu =
108 [[[NSApp mainMenu] itemArray] objectAtIndex:edit_menu_index];
109 NSMenu* edit_submenu = [edit_menu submenu];
110 NSMenuItem* paste_match_style_menu_item =
111 [edit_submenu itemWithTag:IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE];
112 NSMenuItem* find_menu_item = [edit_submenu itemWithTag:IDC_FIND_MENU];
113 if (app->is_hosted_app()) {
114 EXPECT_FALSE([paste_match_style_menu_item isHidden]);
115 EXPECT_FALSE([find_menu_item isHidden]);
117 EXPECT_TRUE([paste_match_style_menu_item isHidden]);
118 EXPECT_TRUE([find_menu_item isHidden]);
122 extensions::AppWindow* FirstWindowForApp(const extensions::Extension* app) {
123 extensions::AppWindowRegistry::AppWindowList window_list =
124 extensions::AppWindowRegistry::Get(profile())
125 ->GetAppWindowsForApp(app->id());
126 EXPECT_FALSE(window_list.empty());
127 return window_list.front();
130 const extensions::Extension* app_1_;
131 const extensions::Extension* app_2_;
132 const extensions::Extension* hosted_app_;
133 NSUInteger initial_menu_item_count_;
136 DISALLOW_COPY_AND_ASSIGN(AppShimMenuControllerBrowserTest);
139 // Test that focusing an app window changes the menu bar.
140 IN_PROC_BROWSER_TEST_F(AppShimMenuControllerBrowserTest,
141 PlatformAppFocusUpdatesMenuBar) {
142 SetUpApps(PACKAGED_1 | PACKAGED_2);
143 // When an app is focused, all Chrome menu items should be hidden, and a menu
144 // item for the app should be added.
145 extensions::AppWindow* app_1_app_window = FirstWindowForApp(app_1_);
146 [[NSNotificationCenter defaultCenter]
147 postNotificationName:NSWindowDidBecomeMainNotification
148 object:app_1_app_window->GetNativeWindow()];
149 CheckHasAppMenus(app_1_);
151 // When another app is focused, the menu item for the app should change.
152 extensions::AppWindow* app_2_app_window = FirstWindowForApp(app_2_);
153 [[NSNotificationCenter defaultCenter]
154 postNotificationName:NSWindowDidBecomeMainNotification
155 object:app_2_app_window->GetNativeWindow()];
156 CheckHasAppMenus(app_2_);
158 // When a browser window is focused, the menu items for the app should be
160 BrowserWindow* chrome_window = chrome::BrowserIterator()->window();
161 [[NSNotificationCenter defaultCenter]
162 postNotificationName:NSWindowDidBecomeMainNotification
163 object:chrome_window->GetNativeWindow()];
166 // When an app window is closed and there are no other app windows, the menu
167 // items for the app should be removed.
168 app_1_app_window->GetBaseWindow()->Close();
169 chrome_window->Close();
170 [[NSNotificationCenter defaultCenter]
171 postNotificationName:NSWindowDidBecomeMainNotification
172 object:app_2_app_window->GetNativeWindow()];
173 CheckHasAppMenus(app_2_);
174 [[NSNotificationCenter defaultCenter]
175 postNotificationName:NSWindowDidResignMainNotification
176 object:app_2_app_window->GetNativeWindow()];
177 app_2_app_window->GetBaseWindow()->Close();
181 // Test that closing windows without main status do not update the menu.
182 IN_PROC_BROWSER_TEST_F(AppShimMenuControllerBrowserTest,
183 ClosingBackgroundWindowLeavesMenuBar) {
184 // Start with app1 active.
185 SetUpApps(PACKAGED_1);
186 extensions::AppWindow* app_1_app_window = FirstWindowForApp(app_1_);
189 ui::test::ScopedFakeNSWindowFocus fake_focus;
190 [app_1_app_window->GetNativeWindow() makeMainWindow];
191 CheckHasAppMenus(app_1_);
193 // Closing a background window without focusing it should not change menus.
194 BrowserWindow* chrome_window = chrome::BrowserIterator()->window();
195 chrome_window->Close();
196 [[NSNotificationCenter defaultCenter]
197 postNotificationName:NSWindowWillCloseNotification
198 object:chrome_window->GetNativeWindow()];
199 CheckHasAppMenus(app_1_);
201 // |fake_focus| going out of scope sends NSWindowWillResignMainNotification.
203 app_1_app_window->GetBaseWindow()->Close();
207 // Test to check that hosted apps have "Find" and "Paste and Match Style" menu
208 // items under the "Edit" menu.
209 // Disabled until tab versus window apps are properly tested
210 // http://crbug.com/517744
211 IN_PROC_BROWSER_TEST_F(AppShimMenuControllerBrowserTest,
212 DISABLED_HostedAppHasAdditionalEditMenuItems) {
213 SetUpApps(HOSTED | PACKAGED_1);
215 // Find the first hosted app window.
216 Browser* hosted_app_browser = nullptr;
217 BrowserList* browsers =
218 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
219 for (Browser* browser : *browsers) {
220 const extensions::Extension* extension =
221 apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
222 if (extension && extension->is_hosted_app()) {
223 hosted_app_browser = browser;
227 EXPECT_TRUE(hosted_app_browser);
229 // Focus the hosted app.
230 [[NSNotificationCenter defaultCenter]
231 postNotificationName:NSWindowDidBecomeMainNotification
232 object:hosted_app_browser->window()->GetNativeWindow()];
233 CheckEditMenu(hosted_app_);
235 // Now focus a platform app, the Edit menu should not have the additional
237 [[NSNotificationCenter defaultCenter]
238 postNotificationName:NSWindowDidBecomeMainNotification
239 object:FirstWindowForApp(app_1_)->GetNativeWindow()];
240 CheckEditMenu(app_1_);
243 // Test that uninstalling an app restores the main menu.
244 IN_PROC_BROWSER_TEST_F(AppShimMenuControllerBrowserTest,
245 ExtensionUninstallUpdatesMenuBar) {
246 SetUpApps(PACKAGED_1 | PACKAGED_2);
248 FirstWindowForApp(app_2_)->GetBaseWindow()->Close();
249 chrome::BrowserIterator()->window()->Close();
250 NSWindow* app_1_window = FirstWindowForApp(app_1_)->GetNativeWindow();
252 ui::test::ScopedFakeNSWindowFocus fake_focus;
253 [app_1_window makeMainWindow];
255 CheckHasAppMenus(app_1_);
256 ExtensionService::UninstallExtensionHelper(
259 extensions::UNINSTALL_REASON_FOR_TESTING);
261 // OSX will send NSWindowWillResignMainNotification when a main window is
263 [[NSNotificationCenter defaultCenter]
264 postNotificationName:NSWindowDidResignMainNotification
265 object:app_1_window];