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 "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 "chrome/app/chrome_command_ids.h"
17#import "chrome/browser/app_controller_mac.h"
18#include "chrome/browser/apps/app_browsertest_util.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/browser/profiles/profile_manager.h"
21#include "chrome/browser/ui/browser.h"
22#include "chrome/browser/ui/browser_list.h"
23#include "chrome/browser/ui/browser_window.h"
24#include "chrome/browser/ui/host_desktop.h"
25#include "chrome/browser/ui/tabs/tab_strip_model.h"
26#include "chrome/browser/ui/user_manager.h"
27#include "chrome/common/chrome_constants.h"
28#include "chrome/common/chrome_switches.h"
29#include "chrome/common/pref_names.h"
30#include "chrome/common/url_constants.h"
31#include "chrome/test/base/in_process_browser_test.h"
32#include "chrome/test/base/ui_test_utils.h"
33#include "components/signin/core/common/profile_management_switches.h"
34#include "content/public/browser/web_contents.h"
35#include "extensions/browser/app_window/app_window_registry.h"
36#include "extensions/common/extension.h"
37#include "extensions/test/extension_test_message_listener.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 = extensions::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(UserManager::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(UserManager::IsShowing());
252  UserManager::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(UserManager::IsShowing());
278  UserManager::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