1 // Copyright (c) 2012 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 <Carbon/Carbon.h>
6 #import <Cocoa/Cocoa.h>
7 #import <Foundation/Foundation.h>
8 #import <Foundation/NSAppleEventDescriptor.h>
9 #import <objc/message.h>
10 #import <objc/runtime.h>
12 #include "base/command_line.h"
13 #include "base/mac/foundation_util.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/run_loop.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "components/bookmarks/browser/bookmark_model.h"
21 #import "chrome/browser/app_controller_mac.h"
22 #include "chrome/browser/apps/app_browsertest_util.h"
23 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/profiles/profile_manager.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_list.h"
28 #include "chrome/browser/ui/browser_window.h"
29 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
30 #include "chrome/browser/ui/host_desktop.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/browser/ui/user_manager.h"
33 #include "chrome/common/chrome_constants.h"
34 #include "chrome/common/chrome_switches.h"
35 #include "chrome/common/pref_names.h"
36 #include "chrome/common/url_constants.h"
37 #include "chrome/test/base/in_process_browser_test.h"
38 #include "components/bookmarks/test/bookmark_test_helpers.h"
39 #include "components/signin/core/common/profile_management_switches.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/test/test_navigation_observer.h"
42 #include "extensions/browser/app_window/app_window_registry.h"
43 #include "extensions/common/extension.h"
44 #include "extensions/test/extension_test_message_listener.h"
45 #include "net/test/embedded_test_server/embedded_test_server.h"
49 GURL g_open_shortcut_url = GURL::EmptyGURL();
51 // Returns an Apple Event that instructs the application to open |url|.
52 NSAppleEventDescriptor* AppleEventToOpenUrl(const GURL& url) {
53 NSAppleEventDescriptor* shortcut_event = [[[NSAppleEventDescriptor alloc]
54 initWithEventClass:kASAppleScriptSuite
55 eventID:kASSubroutineEvent
57 returnID:kAutoGenerateReturnID
58 transactionID:kAnyTransactionID] autorelease];
59 NSString* url_string = [NSString stringWithUTF8String:url.spec().c_str()];
60 [shortcut_event setParamDescriptor:[NSAppleEventDescriptor
61 descriptorWithString:url_string]
62 forKeyword:keyDirectObject];
63 return shortcut_event;
66 // Instructs the NSApp's delegate to open |url|.
67 void SendAppleEventToOpenUrlToAppController(const GURL& url) {
68 AppController* controller =
69 base::mac::ObjCCast<AppController>([NSApp delegate]);
71 class_getInstanceMethod([controller class], @selector(getUrl:withReply:));
75 NSAppleEventDescriptor* shortcut_event = AppleEventToOpenUrl(url);
77 method_invoke(controller, get_url, shortcut_event, NULL);
82 @interface TestOpenShortcutOnStartup : NSObject
83 - (void)applicationWillFinishLaunching:(NSNotification*)notification;
86 @implementation TestOpenShortcutOnStartup
88 - (void)applicationWillFinishLaunching:(NSNotification*)notification {
89 if (!g_open_shortcut_url.is_valid())
92 SendAppleEventToOpenUrlToAppController(g_open_shortcut_url);
99 class AppControllerPlatformAppBrowserTest
100 : public extensions::PlatformAppBrowserTest {
102 AppControllerPlatformAppBrowserTest()
103 : active_browser_list_(BrowserList::GetInstance(
104 chrome::GetActiveDesktop())) {
107 void SetUpCommandLine(CommandLine* command_line) override {
108 PlatformAppBrowserTest::SetUpCommandLine(command_line);
109 command_line->AppendSwitchASCII(switches::kAppId,
113 const BrowserList* active_browser_list_;
116 // Test that if only a platform app window is open and no browser windows are
117 // open then a reopen event does nothing.
118 IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest,
119 PlatformAppReopenWithWindows) {
120 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
121 NSUInteger old_window_count = [[NSApp windows] count];
122 EXPECT_EQ(1u, active_browser_list_->size());
123 [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:YES];
124 // We do not EXPECT_TRUE the result here because the method
125 // deminiaturizes windows manually rather than return YES and have
128 EXPECT_EQ(old_window_count, [[NSApp windows] count]);
129 EXPECT_EQ(1u, active_browser_list_->size());
132 IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest,
133 ActivationFocusesBrowserWindow) {
134 base::scoped_nsobject<AppController> app_controller(
135 [[AppController alloc] init]);
137 ExtensionTestMessageListener listener("Launched", false);
138 const extensions::Extension* app =
139 InstallAndLaunchPlatformApp("minimal");
140 ASSERT_TRUE(listener.WaitUntilSatisfied());
142 NSWindow* app_window = extensions::AppWindowRegistry::Get(profile())
143 ->GetAppWindowsForApp(app->id())
146 NSWindow* browser_window = browser()->window()->GetNativeWindow();
148 EXPECT_LE([[NSApp orderedWindows] indexOfObject:app_window],
149 [[NSApp orderedWindows] indexOfObject:browser_window]);
150 [app_controller applicationShouldHandleReopen:NSApp
151 hasVisibleWindows:YES];
152 EXPECT_LE([[NSApp orderedWindows] indexOfObject:browser_window],
153 [[NSApp orderedWindows] indexOfObject:app_window]);
156 class AppControllerWebAppBrowserTest : public InProcessBrowserTest {
158 AppControllerWebAppBrowserTest()
159 : active_browser_list_(BrowserList::GetInstance(
160 chrome::GetActiveDesktop())) {
163 void SetUpCommandLine(CommandLine* command_line) override {
164 command_line->AppendSwitchASCII(switches::kApp, GetAppURL());
167 std::string GetAppURL() const {
168 return "http://example.com/";
171 const BrowserList* active_browser_list_;
174 // Test that in web app mode a reopen event opens the app URL.
175 IN_PROC_BROWSER_TEST_F(AppControllerWebAppBrowserTest,
176 WebAppReopenWithNoWindows) {
177 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
178 EXPECT_EQ(1u, active_browser_list_->size());
179 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
181 EXPECT_FALSE(result);
182 EXPECT_EQ(2u, active_browser_list_->size());
184 Browser* browser = active_browser_list_->get(0);
186 browser->tab_strip_model()->GetActiveWebContents()->GetURL();
187 EXPECT_EQ(GetAppURL(), current_url.spec());
190 // Called when the ProfileManager has created a profile.
191 void CreateProfileCallback(const base::Closure& quit_closure,
193 Profile::CreateStatus status) {
194 EXPECT_TRUE(profile);
195 EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status);
196 EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status);
197 // This will be called multiple times. Wait until the profile is initialized
198 // fully to quit the loop.
199 if (status == Profile::CREATE_STATUS_INITIALIZED)
203 void CreateAndWaitForGuestProfile() {
204 ProfileManager::CreateCallback create_callback =
205 base::Bind(&CreateProfileCallback,
206 base::MessageLoop::current()->QuitClosure());
207 g_browser_process->profile_manager()->CreateProfileAsync(
208 ProfileManager::GetGuestProfilePath(),
213 base::RunLoop().Run();
216 class AppControllerNewProfileManagementBrowserTest
217 : public InProcessBrowserTest {
219 AppControllerNewProfileManagementBrowserTest()
220 : active_browser_list_(BrowserList::GetInstance(
221 chrome::GetActiveDesktop())) {
224 void SetUpCommandLine(CommandLine* command_line) override {
225 switches::EnableNewProfileManagementForTesting(command_line);
228 const BrowserList* active_browser_list_;
231 // Test that for a regular last profile, a reopen event opens a browser.
232 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
233 RegularProfileReopenWithNoWindows) {
234 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
235 EXPECT_EQ(1u, active_browser_list_->size());
236 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
238 EXPECT_FALSE(result);
239 EXPECT_EQ(2u, active_browser_list_->size());
240 EXPECT_FALSE(UserManager::IsShowing());
243 // Test that for a locked last profile, a reopen event opens the User Manager.
244 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
245 LockedProfileReopenWithNoWindows) {
246 // The User Manager uses the guest profile as its underlying profile. To
247 // minimize flakiness due to the scheduling/descheduling of tasks on the
248 // different threads, pre-initialize the guest profile before it is needed.
249 CreateAndWaitForGuestProfile();
250 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
252 // Lock the active profile.
253 Profile* profile = [ac lastProfile];
254 ProfileInfoCache& cache =
255 g_browser_process->profile_manager()->GetProfileInfoCache();
256 size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath());
257 cache.SetProfileSigninRequiredAtIndex(profile_index, true);
258 EXPECT_TRUE(cache.ProfileIsSigninRequiredAtIndex(profile_index));
260 EXPECT_EQ(1u, active_browser_list_->size());
261 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
262 EXPECT_FALSE(result);
264 base::RunLoop().RunUntilIdle();
265 EXPECT_EQ(1u, active_browser_list_->size());
266 EXPECT_TRUE(UserManager::IsShowing());
270 // Test that for a guest last profile, a reopen event opens the User Manager.
271 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
272 GuestProfileReopenWithNoWindows) {
273 // Create the guest profile, and set it as the last used profile so the
274 // app controller can use it on init.
275 CreateAndWaitForGuestProfile();
276 PrefService* local_state = g_browser_process->local_state();
277 local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
279 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
281 Profile* profile = [ac lastProfile];
282 EXPECT_EQ(ProfileManager::GetGuestProfilePath(), profile->GetPath());
283 EXPECT_TRUE(profile->IsGuestSession());
285 EXPECT_EQ(1u, active_browser_list_->size());
286 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
287 EXPECT_FALSE(result);
289 base::RunLoop().RunUntilIdle();
291 EXPECT_EQ(1u, active_browser_list_->size());
292 EXPECT_TRUE(UserManager::IsShowing());
296 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
297 AboutChromeForcesUserManager) {
298 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
300 // Create the guest profile, and set it as the last used profile so the
301 // app controller can use it on init.
302 CreateAndWaitForGuestProfile();
303 PrefService* local_state = g_browser_process->local_state();
304 local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
306 // Prohibiting guest mode forces the user manager flow for About Chrome.
307 local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
309 Profile* guest_profile = [ac lastProfile];
310 EXPECT_EQ(ProfileManager::GetGuestProfilePath(), guest_profile->GetPath());
311 EXPECT_TRUE(guest_profile->IsGuestSession());
313 // Tell the browser to open About Chrome.
314 EXPECT_EQ(1u, active_browser_list_->size());
315 [ac orderFrontStandardAboutPanel:NSApp];
317 base::RunLoop().RunUntilIdle();
319 // No new browser is opened; the User Manager opens instead.
320 EXPECT_EQ(1u, active_browser_list_->size());
321 EXPECT_TRUE(UserManager::IsShowing());
326 class AppControllerOpenShortcutBrowserTest : public InProcessBrowserTest {
328 AppControllerOpenShortcutBrowserTest() {
331 void SetUpInProcessBrowserTestFixture() override {
332 // In order to mimic opening shortcut during browser startup, we need to
333 // send the event before -applicationDidFinishLaunching is called, but
334 // after AppController is loaded.
336 // Since -applicationWillFinishLaunching does nothing now, we swizzle it to
337 // our function to send the event. We need to do this early before running
338 // the main message loop.
340 // NSApp does not exist yet. We need to get the AppController using
342 Class appControllerClass = NSClassFromString(@"AppController");
343 Class openShortcutClass = NSClassFromString(@"TestOpenShortcutOnStartup");
345 ASSERT_TRUE(appControllerClass != nil);
346 ASSERT_TRUE(openShortcutClass != nil);
348 SEL targetMethod = @selector(applicationWillFinishLaunching:);
349 Method original = class_getInstanceMethod(appControllerClass,
351 Method destination = class_getInstanceMethod(openShortcutClass,
354 ASSERT_TRUE(original != NULL);
355 ASSERT_TRUE(destination != NULL);
357 method_exchangeImplementations(original, destination);
359 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
360 g_open_shortcut_url = embedded_test_server()->GetURL("/simple.html");
363 void SetUpCommandLine(CommandLine* command_line) override {
364 // If the arg is empty, PrepareTestCommandLine() after this function will
365 // append about:blank as default url.
366 command_line->AppendArg(chrome::kChromeUINewTabURL);
370 IN_PROC_BROWSER_TEST_F(AppControllerOpenShortcutBrowserTest,
371 OpenShortcutOnStartup) {
372 EXPECT_EQ(1, browser()->tab_strip_model()->count());
373 EXPECT_EQ(g_open_shortcut_url,
374 browser()->tab_strip_model()->GetActiveWebContents()
375 ->GetLastCommittedURL());
378 class AppControllerReplaceNTPBrowserTest : public InProcessBrowserTest {
380 AppControllerReplaceNTPBrowserTest() {}
382 void SetUpInProcessBrowserTestFixture() override {
383 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
386 void SetUpCommandLine(CommandLine* command_line) override {
387 // If the arg is empty, PrepareTestCommandLine() after this function will
388 // append about:blank as default url.
389 command_line->AppendArg(chrome::kChromeUINewTabURL);
393 // Tests that when a GURL is opened after startup, it replaces the NTP.
394 IN_PROC_BROWSER_TEST_F(AppControllerReplaceNTPBrowserTest,
395 ReplaceNTPAfterStartup) {
396 // Ensure that there is exactly 1 tab showing, and the tab is the NTP.
397 GURL ntp(chrome::kChromeUINewTabURL);
398 EXPECT_EQ(1, browser()->tab_strip_model()->count());
402 ->GetActiveWebContents()
403 ->GetLastCommittedURL());
405 GURL simple(embedded_test_server()->GetURL("/simple.html"));
406 SendAppleEventToOpenUrlToAppController(simple);
408 // Wait for one navigation on the active web contents.
409 EXPECT_EQ(1, browser()->tab_strip_model()->count());
410 content::TestNavigationObserver obs(
411 browser()->tab_strip_model()->GetActiveWebContents(), 1);
417 ->GetActiveWebContents()
418 ->GetLastCommittedURL());
421 class AppControllerMainMenuBrowserTest : public InProcessBrowserTest {
423 AppControllerMainMenuBrowserTest() {
427 IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
428 BookmarksMenuIsRestoredAfterProfileSwitch) {
429 ProfileManager* profile_manager = g_browser_process->profile_manager();
430 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
433 // Constants for bookmarks that we will create later.
434 const base::string16 title1(base::ASCIIToUTF16("Dinosaur Comics"));
435 const GURL url1("http://qwantz.com//");
437 const base::string16 title2(base::ASCIIToUTF16("XKCD"));
438 const GURL url2("https://www.xkcd.com/");
440 // Use the existing profile as profile 1.
441 Profile* profile1 = browser()->profile();
442 bookmarks::test::WaitForBookmarkModelToLoad(
443 BookmarkModelFactory::GetForProfile(profile1));
446 base::FilePath path2 = profile_manager->GenerateNextProfileDirectoryPath();
448 Profile::CreateProfile(path2, NULL, Profile::CREATE_MODE_SYNCHRONOUS);
449 profile_manager->RegisterTestingProfile(profile2, false, true);
450 bookmarks::test::WaitForBookmarkModelToLoad(
451 BookmarkModelFactory::GetForProfile(profile2));
453 // Switch to profile 1, create bookmark 1 and force the menu to build.
454 [ac windowChangedToProfile:profile1];
455 [ac bookmarkMenuBridge]->GetBookmarkModel()->AddURL(
456 [ac bookmarkMenuBridge]->GetBookmarkModel()->bookmark_bar_node(),
458 [ac bookmarkMenuBridge]->BuildMenu();
460 // Switch to profile 2, create bookmark 2 and force the menu to build.
461 [ac windowChangedToProfile:profile2];
462 [ac bookmarkMenuBridge]->GetBookmarkModel()->AddURL(
463 [ac bookmarkMenuBridge]->GetBookmarkModel()->bookmark_bar_node(),
465 [ac bookmarkMenuBridge]->BuildMenu();
467 // Test that only bookmark 2 is shown.
468 EXPECT_FALSE([[ac bookmarkMenuBridge]->BookmarkMenu() itemWithTitle:
469 SysUTF16ToNSString(title1)]);
470 EXPECT_TRUE([[ac bookmarkMenuBridge]->BookmarkMenu() itemWithTitle:
471 SysUTF16ToNSString(title2)]);
473 // Switch *back* to profile 1 and *don't* force the menu to build.
474 [ac windowChangedToProfile:profile1];
476 // Test that only bookmark 1 is shown in the restored menu.
477 EXPECT_TRUE([[ac bookmarkMenuBridge]->BookmarkMenu() itemWithTitle:
478 SysUTF16ToNSString(title1)]);
479 EXPECT_FALSE([[ac bookmarkMenuBridge]->BookmarkMenu() itemWithTitle:
480 SysUTF16ToNSString(title2)]);