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 40namespace { 41 42GURL 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 82namespace { 83 84class 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. 103IN_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 117IN_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 141class 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. 160IN_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. 176void 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 188void 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 201class 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. 217IN_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. 229IN_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. 256IN_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 281class 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 325IN_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