blob: d79912eed0114cf0d8d426ebd506e8cdf7c82d53 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <Foundation/NSAppleEventDescriptor.h>
#import <objc/message.h>
#import <objc/runtime.h>
#include "base/command_line.h"
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_nsobject.h"
#include "base/prefs/pref_service.h"
#include "base/run_loop.h"
#include "chrome/app/chrome_command_ids.h"
#import "chrome/browser/app_controller_mac.h"
#include "chrome/browser/apps/app_browsertest_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/user_manager.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/common/extension.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
namespace {
GURL g_open_shortcut_url = GURL::EmptyGURL();
// Returns an Apple Event that instructs the application to open |url|.
NSAppleEventDescriptor* AppleEventToOpenUrl(const GURL& url) {
NSAppleEventDescriptor* shortcut_event = [[[NSAppleEventDescriptor alloc]
initWithEventClass:kASAppleScriptSuite
eventID:kASSubroutineEvent
targetDescriptor:nil
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID] autorelease];
NSString* url_string = [NSString stringWithUTF8String:url.spec().c_str()];
[shortcut_event setParamDescriptor:[NSAppleEventDescriptor
descriptorWithString:url_string]
forKeyword:keyDirectObject];
return shortcut_event;
}
// Instructs the NSApp's delegate to open |url|.
void SendAppleEventToOpenUrlToAppController(const GURL& url) {
AppController* controller =
base::mac::ObjCCast<AppController>([NSApp delegate]);
Method get_url =
class_getInstanceMethod([controller class], @selector(getUrl:withReply:));
ASSERT_TRUE(get_url);
NSAppleEventDescriptor* shortcut_event = AppleEventToOpenUrl(url);
method_invoke(controller, get_url, shortcut_event, NULL);
}
} // namespace
@interface TestOpenShortcutOnStartup : NSObject
- (void)applicationWillFinishLaunching:(NSNotification*)notification;
@end
@implementation TestOpenShortcutOnStartup
- (void)applicationWillFinishLaunching:(NSNotification*)notification {
if (!g_open_shortcut_url.is_valid())
return;
SendAppleEventToOpenUrlToAppController(g_open_shortcut_url);
}
@end
namespace {
class AppControllerPlatformAppBrowserTest
: public extensions::PlatformAppBrowserTest {
protected:
AppControllerPlatformAppBrowserTest()
: active_browser_list_(BrowserList::GetInstance(
chrome::GetActiveDesktop())) {
}
void SetUpCommandLine(CommandLine* command_line) override {
PlatformAppBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kAppId,
"1234");
}
const BrowserList* active_browser_list_;
};
// Test that if only a platform app window is open and no browser windows are
// open then a reopen event does nothing.
IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest,
PlatformAppReopenWithWindows) {
base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
NSUInteger old_window_count = [[NSApp windows] count];
EXPECT_EQ(1u, active_browser_list_->size());
[ac applicationShouldHandleReopen:NSApp hasVisibleWindows:YES];
// We do not EXPECT_TRUE the result here because the method
// deminiaturizes windows manually rather than return YES and have
// AppKit do it.
EXPECT_EQ(old_window_count, [[NSApp windows] count]);
EXPECT_EQ(1u, active_browser_list_->size());
}
IN_PROC_BROWSER_TEST_F(AppControllerPlatformAppBrowserTest,
ActivationFocusesBrowserWindow) {
base::scoped_nsobject<AppController> app_controller(
[[AppController alloc] init]);
ExtensionTestMessageListener listener("Launched", false);
const extensions::Extension* app =
InstallAndLaunchPlatformApp("minimal");
ASSERT_TRUE(listener.WaitUntilSatisfied());
NSWindow* app_window = extensions::AppWindowRegistry::Get(profile())
->GetAppWindowsForApp(app->id())
.front()
->GetNativeWindow();
NSWindow* browser_window = browser()->window()->GetNativeWindow();
EXPECT_LE([[NSApp orderedWindows] indexOfObject:app_window],
[[NSApp orderedWindows] indexOfObject:browser_window]);
[app_controller applicationShouldHandleReopen:NSApp
hasVisibleWindows:YES];
EXPECT_LE([[NSApp orderedWindows] indexOfObject:browser_window],
[[NSApp orderedWindows] indexOfObject:app_window]);
}
class AppControllerWebAppBrowserTest : public InProcessBrowserTest {
protected:
AppControllerWebAppBrowserTest()
: active_browser_list_(BrowserList::GetInstance(
chrome::GetActiveDesktop())) {
}
void SetUpCommandLine(CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kApp, GetAppURL());
}
std::string GetAppURL() const {
return "http://example.com/";
}
const BrowserList* active_browser_list_;
};
// Test that in web app mode a reopen event opens the app URL.
IN_PROC_BROWSER_TEST_F(AppControllerWebAppBrowserTest,
WebAppReopenWithNoWindows) {
base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
EXPECT_EQ(1u, active_browser_list_->size());
BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
EXPECT_FALSE(result);
EXPECT_EQ(2u, active_browser_list_->size());
Browser* browser = active_browser_list_->get(0);
GURL current_url =
browser->tab_strip_model()->GetActiveWebContents()->GetURL();
EXPECT_EQ(GetAppURL(), current_url.spec());
}
// Called when the ProfileManager has created a profile.
void CreateProfileCallback(const base::Closure& quit_closure,
Profile* profile,
Profile::CreateStatus status) {
EXPECT_TRUE(profile);
EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status);
EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status);
// This will be called multiple times. Wait until the profile is initialized
// fully to quit the loop.
if (status == Profile::CREATE_STATUS_INITIALIZED)
quit_closure.Run();
}
void CreateAndWaitForGuestProfile() {
ProfileManager::CreateCallback create_callback =
base::Bind(&CreateProfileCallback,
base::MessageLoop::current()->QuitClosure());
g_browser_process->profile_manager()->CreateProfileAsync(
ProfileManager::GetGuestProfilePath(),
create_callback,
base::string16(),
base::string16(),
std::string());
base::RunLoop().Run();
}
class AppControllerNewProfileManagementBrowserTest
: public InProcessBrowserTest {
protected:
AppControllerNewProfileManagementBrowserTest()
: active_browser_list_(BrowserList::GetInstance(
chrome::GetActiveDesktop())) {
}
void SetUpCommandLine(CommandLine* command_line) override {
switches::EnableNewProfileManagementForTesting(command_line);
}
const BrowserList* active_browser_list_;
};
// Test that for a regular last profile, a reopen event opens a browser.
IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
RegularProfileReopenWithNoWindows) {
base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
EXPECT_EQ(1u, active_browser_list_->size());
BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
EXPECT_FALSE(result);
EXPECT_EQ(2u, active_browser_list_->size());
EXPECT_FALSE(UserManager::IsShowing());
}
// Test that for a locked last profile, a reopen event opens the User Manager.
IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
LockedProfileReopenWithNoWindows) {
// The User Manager uses the guest profile as its underlying profile. To
// minimize flakiness due to the scheduling/descheduling of tasks on the
// different threads, pre-initialize the guest profile before it is needed.
CreateAndWaitForGuestProfile();
base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
// Lock the active profile.
Profile* profile = [ac lastProfile];
ProfileInfoCache& cache =
g_browser_process->profile_manager()->GetProfileInfoCache();
size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath());
cache.SetProfileSigninRequiredAtIndex(profile_index, true);
EXPECT_TRUE(cache.ProfileIsSigninRequiredAtIndex(profile_index));
EXPECT_EQ(1u, active_browser_list_->size());
BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
EXPECT_FALSE(result);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list_->size());
EXPECT_TRUE(UserManager::IsShowing());
UserManager::Hide();
}
// Test that for a guest last profile, a reopen event opens the User Manager.
IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
GuestProfileReopenWithNoWindows) {
// Create the guest profile, and set it as the last used profile so the
// app controller can use it on init.
CreateAndWaitForGuestProfile();
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
Profile* profile = [ac lastProfile];
EXPECT_EQ(ProfileManager::GetGuestProfilePath(), profile->GetPath());
EXPECT_TRUE(profile->IsGuestSession());
EXPECT_EQ(1u, active_browser_list_->size());
BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
EXPECT_FALSE(result);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list_->size());
EXPECT_TRUE(UserManager::IsShowing());
UserManager::Hide();
}
IN_PROC_BROWSER_TEST_F(AppControllerNewProfileManagementBrowserTest,
AboutChromeForcesUserManager) {
base::scoped_nsobject<AppController> ac([[AppController alloc] init]);
// Create the guest profile, and set it as the last used profile so the
// app controller can use it on init.
CreateAndWaitForGuestProfile();
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
// Prohibiting guest mode forces the user manager flow for About Chrome.
local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
Profile* guest_profile = [ac lastProfile];
EXPECT_EQ(ProfileManager::GetGuestProfilePath(), guest_profile->GetPath());
EXPECT_TRUE(guest_profile->IsGuestSession());
// Tell the browser to open About Chrome.
EXPECT_EQ(1u, active_browser_list_->size());
[ac orderFrontStandardAboutPanel:NSApp];
base::RunLoop().RunUntilIdle();
// No new browser is opened; the User Manager opens instead.
EXPECT_EQ(1u, active_browser_list_->size());
EXPECT_TRUE(UserManager::IsShowing());
UserManager::Hide();
}
class AppControllerOpenShortcutBrowserTest : public InProcessBrowserTest {
protected:
AppControllerOpenShortcutBrowserTest() {
}
void SetUpInProcessBrowserTestFixture() override {
// In order to mimic opening shortcut during browser startup, we need to
// send the event before -applicationDidFinishLaunching is called, but
// after AppController is loaded.
//
// Since -applicationWillFinishLaunching does nothing now, we swizzle it to
// our function to send the event. We need to do this early before running
// the main message loop.
//
// NSApp does not exist yet. We need to get the AppController using
// reflection.
Class appControllerClass = NSClassFromString(@"AppController");
Class openShortcutClass = NSClassFromString(@"TestOpenShortcutOnStartup");
ASSERT_TRUE(appControllerClass != nil);
ASSERT_TRUE(openShortcutClass != nil);
SEL targetMethod = @selector(applicationWillFinishLaunching:);
Method original = class_getInstanceMethod(appControllerClass,
targetMethod);
Method destination = class_getInstanceMethod(openShortcutClass,
targetMethod);
ASSERT_TRUE(original != NULL);
ASSERT_TRUE(destination != NULL);
method_exchangeImplementations(original, destination);
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
g_open_shortcut_url = embedded_test_server()->GetURL("/simple.html");
}
void SetUpCommandLine(CommandLine* command_line) override {
// If the arg is empty, PrepareTestCommandLine() after this function will
// append about:blank as default url.
command_line->AppendArg(chrome::kChromeUINewTabURL);
}
};
IN_PROC_BROWSER_TEST_F(AppControllerOpenShortcutBrowserTest,
OpenShortcutOnStartup) {
EXPECT_EQ(1, browser()->tab_strip_model()->count());
EXPECT_EQ(g_open_shortcut_url,
browser()->tab_strip_model()->GetActiveWebContents()
->GetLastCommittedURL());
}
class AppControllerReplaceNTPBrowserTest : public InProcessBrowserTest {
protected:
AppControllerReplaceNTPBrowserTest() {}
void SetUpInProcessBrowserTestFixture() override {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
}
void SetUpCommandLine(CommandLine* command_line) override {
// If the arg is empty, PrepareTestCommandLine() after this function will
// append about:blank as default url.
command_line->AppendArg(chrome::kChromeUINewTabURL);
}
};
// Tests that when a GURL is opened after startup, it replaces the NTP.
IN_PROC_BROWSER_TEST_F(AppControllerReplaceNTPBrowserTest,
ReplaceNTPAfterStartup) {
// Ensure that there is exactly 1 tab showing, and the tab is the NTP.
GURL ntp(chrome::kChromeUINewTabURL);
EXPECT_EQ(1, browser()->tab_strip_model()->count());
EXPECT_EQ(ntp,
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
GURL simple(embedded_test_server()->GetURL("/simple.html"));
SendAppleEventToOpenUrlToAppController(simple);
// Wait for one navigation on the active web contents.
EXPECT_EQ(1, browser()->tab_strip_model()->count());
content::TestNavigationObserver obs(
browser()->tab_strip_model()->GetActiveWebContents(), 1);
obs.Wait();
EXPECT_EQ(simple,
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
} // namespace