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. 4 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> 11 12 #include "apps/app_window_registry.h" 13 #include "base/command_line.h" 14 #include "base/mac/foundation_util.h" 15 #include "base/mac/scoped_nsobject.h" 16 #include "base/prefs/pref_service.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/extensions/extension_test_message_listener.h" 22 #include "chrome/browser/profiles/profile_manager.h" 23 #include "chrome/browser/ui/browser.h" 24 #include "chrome/browser/ui/browser_list.h" 25 #include "chrome/browser/ui/browser_window.h" 26 #import "chrome/browser/ui/cocoa/profiles/user_manager_mac.h" 27 #include "chrome/browser/ui/host_desktop.h" 28 #include "chrome/browser/ui/tabs/tab_strip_model.h" 29 #include "chrome/common/chrome_constants.h" 30 #include "chrome/common/chrome_switches.h" 31 #include "chrome/common/pref_names.h" 32 #include "chrome/common/url_constants.h" 33 #include "chrome/test/base/in_process_browser_test.h" 34 #include "chrome/test/base/ui_test_utils.h" 35 #include "components/signin/core/common/profile_management_switches.h" 36 #include "content/public/browser/web_contents.h" 37 #include "extensions/common/extension.h" 38 #include "net/test/embedded_test_server/embedded_test_server.h" 39 40 namespace { 41 42 GURL g_open_shortcut_url = GURL::EmptyGURL(); 43 44 } // namespace 45 46 @interface TestOpenShortcutOnStartup : NSObject 47 - (void)applicationWillFinishLaunching:(NSNotification*)notification; 48 @end 49 50 @implementation TestOpenShortcutOnStartup 51 52 - (void)applicationWillFinishLaunching:(NSNotification*)notification { 53 if (!g_open_shortcut_url.is_valid()) 54 return; 55 56 AppController* controller = 57 base::mac::ObjCCast<AppController>([NSApp delegate]); 58 Method getUrl = class_getInstanceMethod([controller class], 59 @selector(getUrl:withReply:)); 60 61 if (getUrl == nil) 62 return; 63 64 base::scoped_nsobject<NSAppleEventDescriptor> shortcutEvent( 65 [[NSAppleEventDescriptor alloc] 66 initWithEventClass:kASAppleScriptSuite 67 eventID:kASSubroutineEvent 68 targetDescriptor:nil 69 returnID:kAutoGenerateReturnID 70 transactionID:kAnyTransactionID]); 71 NSString* url = 72 [NSString stringWithUTF8String:g_open_shortcut_url.spec().c_str()]; 73 [shortcutEvent setParamDescriptor: 74 [NSAppleEventDescriptor descriptorWithString:url] 75 forKeyword:keyDirectObject]; 76 77 method_invoke(controller, getUrl, shortcutEvent.get(), NULL); 78 } 79 80 @end 81 82 namespace { 83 84 class AppControllerPlatformAppBrowserTest 85 : public extensions::PlatformAppBrowserTest { 86 protected: 87 AppControllerPlatformAppBrowserTest() 88 : active_browser_list_(BrowserList::GetInstance( 89 chrome::GetActiveDesktop())) { 90 } 91 92 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 93 PlatformAppBrowserTest::SetUpCommandLine(command_line); 94 command_line->AppendSwitchASCII(switches::kAppId, 95 "1234"); 96 } 97 98 const BrowserList* active_browser_list_; 99 }; 100 101 // Test that if only a platform app window is open and no browser windows are 102 // open then a reopen event does nothing. 103 IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest, 104 PlatformAppReopenWithWindows) { 105 base::scoped_nsobject<AppController> ac([[AppController alloc] init]); 106 NSUInteger old_window_count = [[NSApp windows] count]; 107 EXPECT_EQ(1u, active_browser_list_->size()); 108 [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:YES]; 109 // We do not EXPECT_TRUE the result here because the method 110 // deminiaturizes windows manually rather than return YES and have 111 // AppKit do it. 112 113 EXPECT_EQ(old_window_count, [[NSApp windows] count]); 114 EXPECT_EQ(1u, active_browser_list_->size()); 115 } 116 117 IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest, 118 ActivationFocusesBrowserWindow) { 119 base::scoped_nsobject<AppController> app_controller( 120 [[AppController alloc] init]); 121 122 ExtensionTestMessageListener listener("Launched", false); 123 const extensions::Extension* app = 124 InstallAndLaunchPlatformApp("minimal"); 125 ASSERT_TRUE(listener.WaitUntilSatisfied()); 126 127 NSWindow* app_window = apps::AppWindowRegistry::Get(profile()) 128 ->GetAppWindowsForApp(app->id()) 129 .front() 130 ->GetNativeWindow(); 131 NSWindow* browser_window = browser()->window()->GetNativeWindow(); 132 133 EXPECT_LE([[NSApp orderedWindows] indexOfObject:app_window], 134 [[NSApp orderedWindows] indexOfObject:browser_window]); 135 [app_controller applicationShouldHandleReopen:NSApp 136 hasVisibleWindows:YES]; 137 EXPECT_LE([[NSApp orderedWindows] indexOfObject:browser_window], 138 [[NSApp orderedWindows] indexOfObject:app_window]); 139 } 140 141 class AppControllerWebAppBrowserTest : public InProcessBrowserTest { 142 protected: 143 AppControllerWebAppBrowserTest() 144 : active_browser_list_(BrowserList::GetInstance( 145 chrome::GetActiveDesktop())) { 146 } 147 148 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 149 command_line->AppendSwitchASCII(switches::kApp, GetAppURL()); 150 } 151 152 std::string GetAppURL() const { 153 return "http://example.com/"; 154 } 155 156 const BrowserList* active_browser_list_; 157 }; 158 159 // Test that in web app mode a reopen event opens the app URL. 160 IN_PROC_BROWSER_TEST_F(AppControllerWebAppBrowserTest, 161 WebAppReopenWithNoWindows) { 162 base::scoped_nsobject<AppController> ac([[AppController alloc] init]); 163 EXPECT_EQ(1u, active_browser_list_->size()); 164 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO]; 165 166 EXPECT_FALSE(result); 167 EXPECT_EQ(2u, active_browser_list_->size()); 168 169 Browser* browser = active_browser_list_->get(0); 170 GURL current_url = 171 browser->tab_strip_model()->GetActiveWebContents()->GetURL(); 172 EXPECT_EQ(GetAppURL(), current_url.spec()); 173 } 174 175 // Called when the ProfileManager has created a profile. 176 void CreateProfileCallback(const base::Closure& quit_closure, 177 Profile* profile, 178 Profile::CreateStatus status) { 179 EXPECT_TRUE(profile); 180 EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status); 181 EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status); 182 // This will be called multiple times. Wait until the profile is initialized 183 // fully to quit the loop. 184 if (status == Profile::CREATE_STATUS_INITIALIZED) 185 quit_closure.Run(); 186 } 187 188 void CreateAndWaitForGuestProfile() { 189 ProfileManager::CreateCallback create_callback = 190 base::Bind(&CreateProfileCallback, 191 base::MessageLoop::current()->QuitClosure()); 192 g_browser_process->profile_manager()->CreateProfileAsync( 193 ProfileManager::GetGuestProfilePath(), 194 create_callback, 195 base::string16(), 196 base::string16(), 197 std::string()); 198 base::RunLoop().Run(); 199 } 200 201 class AppControllerNewProfileManagementBrowserTest 202 : public InProcessBrowserTest { 203 protected: 204 AppControllerNewProfileManagementBrowserTest() 205 : active_browser_list_(BrowserList::GetInstance( 206 chrome::GetActiveDesktop())) { 207 } 208 209 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 210 switches::EnableNewProfileManagementForTesting(command_line); 211 } 212 213 const BrowserList* active_browser_list_; 214 }; 215 216 // Test that for a regular last profile, a reopen event opens a browser. 217 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest, 218 RegularProfileReopenWithNoWindows) { 219 base::scoped_nsobject<AppController> ac([[AppController alloc] init]); 220 EXPECT_EQ(1u, active_browser_list_->size()); 221 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO]; 222 223 EXPECT_FALSE(result); 224 EXPECT_EQ(2u, active_browser_list_->size()); 225 EXPECT_FALSE(UserManagerMac::IsShowing()); 226 } 227 228 // Test that for a locked last profile, a reopen event opens the User Manager. 229 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest, 230 LockedProfileReopenWithNoWindows) { 231 // The User Manager uses the guest profile as its underlying profile. To 232 // minimize flakiness due to the scheduling/descheduling of tasks on the 233 // different threads, pre-initialize the guest profile before it is needed. 234 CreateAndWaitForGuestProfile(); 235 base::scoped_nsobject<AppController> ac([[AppController alloc] init]); 236 237 // Lock the active profile. 238 Profile* profile = [ac lastProfile]; 239 ProfileInfoCache& cache = 240 g_browser_process->profile_manager()->GetProfileInfoCache(); 241 size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath()); 242 cache.SetProfileSigninRequiredAtIndex(profile_index, true); 243 EXPECT_TRUE(cache.ProfileIsSigninRequiredAtIndex(profile_index)); 244 245 EXPECT_EQ(1u, active_browser_list_->size()); 246 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO]; 247 EXPECT_FALSE(result); 248 249 base::RunLoop().RunUntilIdle(); 250 EXPECT_EQ(1u, active_browser_list_->size()); 251 EXPECT_TRUE(UserManagerMac::IsShowing()); 252 UserManagerMac::Hide(); 253 } 254 255 // Test that for a guest last profile, a reopen event opens the User Manager. 256 IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest, 257 GuestProfileReopenWithNoWindows) { 258 // Create the guest profile, and set it as the last used profile so the 259 // app controller can use it on init. 260 CreateAndWaitForGuestProfile(); 261 PrefService* local_state = g_browser_process->local_state(); 262 local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir); 263 264 base::scoped_nsobject<AppController> ac([[AppController alloc] init]); 265 266 Profile* profile = [ac lastProfile]; 267 EXPECT_EQ(ProfileManager::GetGuestProfilePath(), profile->GetPath()); 268 EXPECT_TRUE(profile->IsGuestSession()); 269 270 EXPECT_EQ(1u, active_browser_list_->size()); 271 BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO]; 272 EXPECT_FALSE(result); 273 274 base::RunLoop().RunUntilIdle(); 275 276 EXPECT_EQ(1u, active_browser_list_->size()); 277 EXPECT_TRUE(UserManagerMac::IsShowing()); 278 UserManagerMac::Hide(); 279 } 280 281 class AppControllerOpenShortcutBrowserTest : public InProcessBrowserTest { 282 protected: 283 AppControllerOpenShortcutBrowserTest() { 284 } 285 286 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 287 // In order to mimic opening shortcut during browser startup, we need to 288 // send the event before -applicationDidFinishLaunching is called, but 289 // after AppController is loaded. 290 // 291 // Since -applicationWillFinishLaunching does nothing now, we swizzle it to 292 // our function to send the event. We need to do this early before running 293 // the main message loop. 294 // 295 // NSApp does not exist yet. We need to get the AppController using 296 // reflection. 297 Class appControllerClass = NSClassFromString(@"AppController"); 298 Class openShortcutClass = NSClassFromString(@"TestOpenShortcutOnStartup"); 299 300 ASSERT_TRUE(appControllerClass != nil); 301 ASSERT_TRUE(openShortcutClass != nil); 302 303 SEL targetMethod = @selector(applicationWillFinishLaunching:); 304 Method original = class_getInstanceMethod(appControllerClass, 305 targetMethod); 306 Method destination = class_getInstanceMethod(openShortcutClass, 307 targetMethod); 308 309 ASSERT_TRUE(original != NULL); 310 ASSERT_TRUE(destination != NULL); 311 312 method_exchangeImplementations(original, destination); 313 314 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 315 g_open_shortcut_url = embedded_test_server()->GetURL("/simple.html"); 316 } 317 318 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 319 // If the arg is empty, PrepareTestCommandLine() after this function will 320 // append about:blank as default url. 321 command_line->AppendArg(chrome::kChromeUINewTabURL); 322 } 323 }; 324 325 IN_PROC_BROWSER_TEST_F(AppControllerOpenShortcutBrowserTest, 326 OpenShortcutOnStartup) { 327 EXPECT_EQ(1, browser()->tab_strip_model()->count()); 328 EXPECT_EQ(g_open_shortcut_url, 329 browser()->tab_strip_model()->GetActiveWebContents() 330 ->GetLastCommittedURL()); 331 } 332 333 } // namespace 334