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 "chrome/app/chrome_command_ids.h"
18 #import "chrome/browser/app_controller_mac.h"
19 #include "chrome/browser/apps/app_browsertest_util.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/browser/ui/host_desktop.h"
26 #include "chrome/browser/ui/tabs/tab_strip_model.h"
27 #include "chrome/browser/ui/user_manager.h"
28 #include "chrome/common/chrome_constants.h"
29 #include "chrome/common/chrome_switches.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/common/url_constants.h"
32 #include "chrome/test/base/in_process_browser_test.h"
33 #include "components/signin/core/common/profile_management_switches.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/test/test_navigation_observer.h"
36 #include "extensions/browser/app_window/app_window_registry.h"
37 #include "extensions/common/extension.h"
38 #include "extensions/test/extension_test_message_listener.h"
39 #include "net/test/embedded_test_server/embedded_test_server.h"
43 GURL g_open_shortcut_url = GURL::EmptyGURL();
45 // Returns an Apple Event that instructs the application to open |url|.
46 NSAppleEventDescriptor* AppleEventToOpenUrl(const GURL& url) {
47 NSAppleEventDescriptor* shortcut_event = [[[NSAppleEventDescriptor alloc]
48 initWithEventClass:kASAppleScriptSuite
49 eventID:kASSubroutineEvent
51 returnID:kAutoGenerateReturnID
52 transactionID:kAnyTransactionID] autorelease];
53 NSString* url_string = [NSString stringWithUTF8String:url.spec().c_str()];
54 [shortcut_event setParamDescriptor:[NSAppleEventDescriptor
55 descriptorWithString:url_string]
56 forKeyword:keyDirectObject];
57 return shortcut_event;
60 // Instructs the NSApp's delegate to open |url|.
61 void SendAppleEventToOpenUrlToAppController(const GURL& url) {
62 AppController* controller =
63 base::mac::ObjCCast<AppController>([NSApp delegate]);
65 class_getInstanceMethod([controller class], @selector(getUrl:withReply:));
69 NSAppleEventDescriptor* shortcut_event = AppleEventToOpenUrl(url);
71 method_invoke(controller, get_url, shortcut_event, NULL);
76 @interface TestOpenShortcutOnStartup : NSObject
77 - (void)applicationWillFinishLaunching:(NSNotification*)notification;
80 @implementation TestOpenShortcutOnStartup
82 - (void)applicationWillFinishLaunching:(NSNotification*)notification {
83 if (!g_open_shortcut_url.is_valid())
86 SendAppleEventToOpenUrlToAppController(g_open_shortcut_url);
93 class AppControllerPlatformAppBrowserTest
94 : public extensions::PlatformAppBrowserTest {
96 AppControllerPlatformAppBrowserTest()
97 : active_browser_list_(BrowserList::GetInstance(
98 chrome::GetActiveDesktop())) {
101 void SetUpCommandLine(CommandLine* command_line) override {
102 PlatformAppBrowserTest::SetUpCommandLine(command_line);
103 command_line->AppendSwitchASCII(switches::kAppId,
107 const BrowserList* active_browser_list_;
110 // Test that if only a platform app window is open and no browser windows are
111 // open then a reopen event does nothing.
112 IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest,
113 PlatformAppReopenWithWindows) {
114 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
115 NSUInteger old_window_count = [[NSApp windows] count];
116 EXPECT_EQ(1u, active_browser_list_->size());
117 [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:YES];
118 // We do not EXPECT_TRUE the result here because the method
119 // deminiaturizes windows manually rather than return YES and have
122 EXPECT_EQ(old_window_count, [[NSApp windows] count]);
123 EXPECT_EQ(1u, active_browser_list_->size());
126 IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest,
127 ActivationFocusesBrowserWindow) {
128 base::scoped_nsobject<AppController> app_controller(
129 [[AppController alloc] init]);
131 ExtensionTestMessageListener listener("Launched", false);
132 const extensions::Extension* app =
133 InstallAndLaunchPlatformApp("minimal");
134 ASSERT_TRUE(listener.WaitUntilSatisfied());
136 NSWindow* app_window = extensions::AppWindowRegistry::Get(profile())
137 ->GetAppWindowsForApp(app->id())
140 NSWindow* browser_window = browser()->window()->GetNativeWindow();
142 EXPECT_LE([[NSApp orderedWindows] indexOfObject:app_window],
143 [[NSApp orderedWindows] indexOfObject:browser_window]);
144 [app_controller applicationShouldHandleReopen:NSApp
145 hasVisibleWindows:YES];
146 EXPECT_LE([[NSApp orderedWindows] indexOfObject:browser_window],
147 [[NSApp orderedWindows] indexOfObject:app_window]);
150 class AppControllerWebAppBrowserTest : public InProcessBrowserTest {
152 AppControllerWebAppBrowserTest()
153 : active_browser_list_(BrowserList::GetInstance(
154 chrome::GetActiveDesktop())) {
157 void SetUpCommandLine(CommandLine* command_line) override {
158 command_line->AppendSwitchASCII(switches::kApp, GetAppURL());
161 std::string GetAppURL() const {
162 return "http://example.com/";
165 const BrowserList* active_browser_list_;
168 // Test that in web app mode a reopen event opens the app URL.
169 IN_PROC_BROWSER_TEST_F(AppControllerWebAppBrowserTest,
170 WebAppReopenWithNoWindows) {
171 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
172 EXPECT_EQ(1u, active_browser_list_->size());
173 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
175 EXPECT_FALSE(result);
176 EXPECT_EQ(2u, active_browser_list_->size());
178 Browser* browser = active_browser_list_->get(0);
180 browser->tab_strip_model()->GetActiveWebContents()->GetURL();
181 EXPECT_EQ(GetAppURL(), current_url.spec());
184 // Called when the ProfileManager has created a profile.
185 void CreateProfileCallback(const base::Closure& quit_closure,
187 Profile::CreateStatus status) {
188 EXPECT_TRUE(profile);
189 EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status);
190 EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status);
191 // This will be called multiple times. Wait until the profile is initialized
192 // fully to quit the loop.
193 if (status == Profile::CREATE_STATUS_INITIALIZED)
197 void CreateAndWaitForGuestProfile() {
198 ProfileManager::CreateCallback create_callback =
199 base::Bind(&CreateProfileCallback,
200 base::MessageLoop::current()->QuitClosure());
201 g_browser_process->profile_manager()->CreateProfileAsync(
202 ProfileManager::GetGuestProfilePath(),
207 base::RunLoop().Run();
210 class AppControllerNewProfileManagementBrowserTest
211 : public InProcessBrowserTest {
213 AppControllerNewProfileManagementBrowserTest()
214 : active_browser_list_(BrowserList::GetInstance(
215 chrome::GetActiveDesktop())) {
218 void SetUpCommandLine(CommandLine* command_line) override {
219 switches::EnableNewProfileManagementForTesting(command_line);
222 const BrowserList* active_browser_list_;
225 // Test that for a regular last profile, a reopen event opens a browser.
226 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
227 RegularProfileReopenWithNoWindows) {
228 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
229 EXPECT_EQ(1u, active_browser_list_->size());
230 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
232 EXPECT_FALSE(result);
233 EXPECT_EQ(2u, active_browser_list_->size());
234 EXPECT_FALSE(UserManager::IsShowing());
237 // Test that for a locked last profile, a reopen event opens the User Manager.
238 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
239 LockedProfileReopenWithNoWindows) {
240 // The User Manager uses the guest profile as its underlying profile. To
241 // minimize flakiness due to the scheduling/descheduling of tasks on the
242 // different threads, pre-initialize the guest profile before it is needed.
243 CreateAndWaitForGuestProfile();
244 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
246 // Lock the active profile.
247 Profile* profile = [ac lastProfile];
248 ProfileInfoCache& cache =
249 g_browser_process->profile_manager()->GetProfileInfoCache();
250 size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath());
251 cache.SetProfileSigninRequiredAtIndex(profile_index, true);
252 EXPECT_TRUE(cache.ProfileIsSigninRequiredAtIndex(profile_index));
254 EXPECT_EQ(1u, active_browser_list_->size());
255 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
256 EXPECT_FALSE(result);
258 base::RunLoop().RunUntilIdle();
259 EXPECT_EQ(1u, active_browser_list_->size());
260 EXPECT_TRUE(UserManager::IsShowing());
264 // Test that for a guest last profile, a reopen event opens the User Manager.
265 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
266 GuestProfileReopenWithNoWindows) {
267 // Create the guest profile, and set it as the last used profile so the
268 // app controller can use it on init.
269 CreateAndWaitForGuestProfile();
270 PrefService* local_state = g_browser_process->local_state();
271 local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
273 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
275 Profile* profile = [ac lastProfile];
276 EXPECT_EQ(ProfileManager::GetGuestProfilePath(), profile->GetPath());
277 EXPECT_TRUE(profile->IsGuestSession());
279 EXPECT_EQ(1u, active_browser_list_->size());
280 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
281 EXPECT_FALSE(result);
283 base::RunLoop().RunUntilIdle();
285 EXPECT_EQ(1u, active_browser_list_->size());
286 EXPECT_TRUE(UserManager::IsShowing());
290 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
291 AboutChromeForcesUserManager) {
292 base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
294 // Create the guest profile, and set it as the last used profile so the
295 // app controller can use it on init.
296 CreateAndWaitForGuestProfile();
297 PrefService* local_state = g_browser_process->local_state();
298 local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
300 // Prohibiting guest mode forces the user manager flow for About Chrome.
301 local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
303 Profile* guest_profile = [ac lastProfile];
304 EXPECT_EQ(ProfileManager::GetGuestProfilePath(), guest_profile->GetPath());
305 EXPECT_TRUE(guest_profile->IsGuestSession());
307 // Tell the browser to open About Chrome.
308 EXPECT_EQ(1u, active_browser_list_->size());
309 [ac orderFrontStandardAboutPanel:NSApp];
311 base::RunLoop().RunUntilIdle();
313 // No new browser is opened; the User Manager opens instead.
314 EXPECT_EQ(1u, active_browser_list_->size());
315 EXPECT_TRUE(UserManager::IsShowing());
320 class AppControllerOpenShortcutBrowserTest : public InProcessBrowserTest {
322 AppControllerOpenShortcutBrowserTest() {
325 void SetUpInProcessBrowserTestFixture() override {
326 // In order to mimic opening shortcut during browser startup, we need to
327 // send the event before -applicationDidFinishLaunching is called, but
328 // after AppController is loaded.
330 // Since -applicationWillFinishLaunching does nothing now, we swizzle it to
331 // our function to send the event. We need to do this early before running
332 // the main message loop.
334 // NSApp does not exist yet. We need to get the AppController using
336 Class appControllerClass = NSClassFromString(@"AppController");
337 Class openShortcutClass = NSClassFromString(@"TestOpenShortcutOnStartup");
339 ASSERT_TRUE(appControllerClass != nil);
340 ASSERT_TRUE(openShortcutClass != nil);
342 SEL targetMethod = @selector(applicationWillFinishLaunching:);
343 Method original = class_getInstanceMethod(appControllerClass,
345 Method destination = class_getInstanceMethod(openShortcutClass,
348 ASSERT_TRUE(original != NULL);
349 ASSERT_TRUE(destination != NULL);
351 method_exchangeImplementations(original, destination);
353 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
354 g_open_shortcut_url = embedded_test_server()->GetURL("/simple.html");
357 void SetUpCommandLine(CommandLine* command_line) override {
358 // If the arg is empty, PrepareTestCommandLine() after this function will
359 // append about:blank as default url.
360 command_line->AppendArg(chrome::kChromeUINewTabURL);
364 IN_PROC_BROWSER_TEST_F(AppControllerOpenShortcutBrowserTest,
365 OpenShortcutOnStartup) {
366 EXPECT_EQ(1, browser()->tab_strip_model()->count());
367 EXPECT_EQ(g_open_shortcut_url,
368 browser()->tab_strip_model()->GetActiveWebContents()
369 ->GetLastCommittedURL());
372 class AppControllerReplaceNTPBrowserTest : public InProcessBrowserTest {
374 AppControllerReplaceNTPBrowserTest() {}
376 void SetUpInProcessBrowserTestFixture() override {
377 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
380 void SetUpCommandLine(CommandLine* command_line) override {
381 // If the arg is empty, PrepareTestCommandLine() after this function will
382 // append about:blank as default url.
383 command_line->AppendArg(chrome::kChromeUINewTabURL);
387 // Tests that when a GURL is opened after startup, it replaces the NTP.
388 IN_PROC_BROWSER_TEST_F(AppControllerReplaceNTPBrowserTest,
389 ReplaceNTPAfterStartup) {
390 // Ensure that there is exactly 1 tab showing, and the tab is the NTP.
391 GURL ntp(chrome::kChromeUINewTabURL);
392 EXPECT_EQ(1, browser()->tab_strip_model()->count());
396 ->GetActiveWebContents()
397 ->GetLastCommittedURL());
399 GURL simple(embedded_test_server()->GetURL("/simple.html"));
400 SendAppleEventToOpenUrlToAppController(simple);
402 // Wait for one navigation on the active web contents.
403 EXPECT_EQ(1, browser()->tab_strip_model()->count());
404 content::TestNavigationObserver obs(
405 browser()->tab_strip_model()->GetActiveWebContents(), 1);
411 ->GetActiveWebContents()
412 ->GetLastCommittedURL());