// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/app_controller_mac.h"
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#include <stddef.h>
#include <string>
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_objc_class_swizzler.h"
#include "base/command_line.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/lifetime/application_lifetime_desktop.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/delete_profile_helper.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_init_params.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/shortcuts/chrome_webloc_file.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
#include "chrome/browser/ui/cocoa/history_menu_bridge.h"
#include "chrome/browser/ui/cocoa/test/run_loop_testing.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "chrome/browser/ui/profiles/profile_ui_test_utils.h"
#include "chrome/browser/ui/search/ntp_test_utils.h"
#include "chrome/browser/ui/startup/first_run_service.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.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 "chrome/test/base/ui_test_utils.h"
#include "components/account_id/account_id.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_switches.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "extensions/common/extension.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/base/apple/url_conversions.h"
#include "net/base/filename_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/features.h"
#import "ui/events/test/cocoa_test_event_utils.h"
#include "ui/views/test/dialog_test.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
namespace {
GURL g_open_shortcut_url;
// Instructs the NSApp's delegate to open |url|.
void SendOpenUrlToAppController(const GURL& url) {
[NSApp.delegate application:NSApp openURLs:@[ net::NSURLWithGURL(url) ]];
}
Profile& CreateAndWaitForProfile(const base::FilePath& profile_dir) {
Profile& profile = profiles::testing::CreateProfileSync(
g_browser_process->profile_manager(), profile_dir);
return profile;
}
void CreateAndWaitForSystemProfile() {
CreateAndWaitForProfile(ProfileManager::GetSystemProfilePath());
}
Profile& CreateAndWaitForGuestProfile() {
return CreateAndWaitForProfile(ProfileManager::GetGuestProfilePath());
}
void SetGuestProfileAsLastProfile() {
AppController* app_controller = AppController.sharedController;
// Create the guest profile, and set it as the last used profile.
Profile& guest_profile = CreateAndWaitForGuestProfile();
[app_controller setLastProfile:&guest_profile];
Profile* profile = [app_controller lastProfileIfLoaded];
ASSERT_TRUE(profile);
EXPECT_EQ(guest_profile.GetPath(), profile->GetPath());
EXPECT_TRUE(profile->IsGuestSession());
// Also set the last used profile path preference. If the profile does need to
// be read from disk for some reason this acts as a backstop.
g_browser_process->local_state()->SetString(
prefs::kProfileLastUsed, guest_profile.GetPath().BaseName().value());
}
// Key for ProfileDestroyedData user data.
const char kProfileDestructionWaiterUserDataKey = 0;
// Waits until the Profile instance is destroyed.
class ProfileDestructionWaiter {
public:
explicit ProfileDestructionWaiter(Profile* profile) {
profile->SetUserData(
&kProfileDestructionWaiterUserDataKey,
std::make_unique<ProfileDestroyedData>(run_loop_.QuitClosure()));
}
void Wait() { run_loop_.Run(); }
private:
// Simple user data that calls a callback at destruction.
class ProfileDestroyedData : public base::SupportsUserData::Data {
public:
explicit ProfileDestroyedData(base::OnceClosure callback)
: scoped_closure_runner_(std::move(callback)) {}
private:
base::ScopedClosureRunner scoped_closure_runner_;
};
base::RunLoop run_loop_;
};
} // namespace
@interface TestOpenShortcutOnStartup : NSObject
- (void)applicationWillFinishLaunching:(NSNotification*)notification;
@end
@implementation TestOpenShortcutOnStartup
- (void)applicationWillFinishLaunching:(NSNotification*)notification {
if (!g_open_shortcut_url.is_valid())
return;
SendOpenUrlToAppController(g_open_shortcut_url);
}
@end
namespace {
using AppControllerBrowserTest = InProcessBrowserTest;
// Returns whether a window's pixels are actually on the screen, which is the
// case when it and all of its parents are marked visible.
bool IsReallyVisible(NSWindow* window) {
while (window) {
if (!window.visible)
return false;
window = [window parentWindow];
}
return true;
}
size_t CountVisibleWindows() {
size_t count = 0;
for (NSWindow* w in [NSApp windows])
count = count + (IsReallyVisible(w) ? 1 : 0);
return count;
}
// Returns how many visible NSWindows are expected for a given count of browser
// windows.
size_t ExpectedWindowCountForBrowserCount(size_t browsers) {
return browsers;
}
// Test browser shutdown with a command in the message queue.
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, CommandDuringShutdown) {
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
EXPECT_EQ(ExpectedWindowCountForBrowserCount(1), CountVisibleWindows());
chrome::AttemptExit(); // Set chrome::IsTryingToQuit and close all windows.
// Opening a new window here is fine (unload handlers can also interrupt
// exit). But closing the window posts an autorelease on
// BrowserWindowController, which calls ~Browser() and, if that was the last
// Browser, it invokes applicationWillTerminate: (because IsTryingToQuit is
// set). So, verify assumptions then process that autorelease.
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
EXPECT_EQ(ExpectedWindowCountForBrowserCount(0), CountVisibleWindows());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
EXPECT_EQ(ExpectedWindowCountForBrowserCount(0), CountVisibleWindows());
NSEvent* cmd_n = cocoa_test_event_utils::KeyEventWithKeyCode(
'n', 'n', NSEventTypeKeyDown, NSEventModifierFlagCommand);
[[NSApp mainMenu] performSelector:@selector(performKeyEquivalent:)
withObject:cmd_n
afterDelay:0];
// Let the run loop get flushed, during process cleanup and try not to crash.
}
class AppControllerKeepAliveBrowserTest : public InProcessBrowserTest {
protected:
AppControllerKeepAliveBrowserTest() {
features_.InitAndEnableFeature(features::kDestroyProfileOnBrowserClose);
}
base::test::ScopedFeatureList features_;
};
class AppControllerPlatformAppBrowserTest
: public extensions::PlatformAppBrowserTest {
protected:
AppControllerPlatformAppBrowserTest()
: active_browser_list_(BrowserList::GetInstance()) {}
void SetUpCommandLine(base::CommandLine* command_line) override {
PlatformAppBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kAppId,
"1234");
}
raw_ptr<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,
DISABLED_PlatformAppReopenWithWindows) {
NSUInteger old_window_count = NSApp.windows.count;
EXPECT_EQ(1u, active_browser_list_->size());
[AppController.sharedController 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,
DISABLED_ActivationFocusesBrowserWindow) {
ExtensionTestMessageListener listener("Launched");
const extensions::Extension* app =
InstallAndLaunchPlatformApp("minimal");
ASSERT_TRUE(listener.WaitUntilSatisfied());
NSWindow* app_window = extensions::AppWindowRegistry::Get(profile())
->GetAppWindowsForApp(app->id())
.front()
->GetNativeWindow()
.GetNativeNSWindow();
NSWindow* browser_window =
browser()->window()->GetNativeWindow().GetNativeNSWindow();
chrome::testing::NSRunLoopRunAllPending();
EXPECT_LE([NSApp.orderedWindows indexOfObject:app_window],
[NSApp.orderedWindows indexOfObject:browser_window]);
[AppController.sharedController applicationShouldHandleReopen:NSApp
hasVisibleWindows:YES];
chrome::testing::NSRunLoopRunAllPending();
EXPECT_LE([NSApp.orderedWindows indexOfObject:browser_window],
[NSApp.orderedWindows indexOfObject:app_window]);
}
class AppControllerWebAppBrowserTest : public InProcessBrowserTest {
protected:
AppControllerWebAppBrowserTest()
: active_browser_list_(BrowserList::GetInstance()) {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kApp, GetAppURL());
}
std::string GetAppURL() const {
return "http://example.com/";
}
raw_ptr<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) {
EXPECT_EQ(1u, active_browser_list_->size());
BOOL result =
[AppController.sharedController 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());
}
class AppControllerProfilePickerBrowserTest : public InProcessBrowserTest {
public:
AppControllerProfilePickerBrowserTest()
: active_browser_list_(BrowserList::GetInstance()) {}
~AppControllerProfilePickerBrowserTest() override = default;
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
// Flag the profile picker as already shown in the past, to avoid additional
// feature onboarding logic.
g_browser_process->local_state()->SetBoolean(
prefs::kBrowserProfilePickerShown, true);
}
const BrowserList* active_browser_list() const {
return active_browser_list_;
}
// Brings the ProfilerPicker onscreen and returns its NSWindow.
NSWindow* ActivateProfilePicker() {
NSArray<NSWindow*>* startingWindows = [NSApp windows];
// ProfilePicker::Show() calls ProfilePicker::Display(), which, for tests,
// creates the profile asynchronously. Only after the profile gets created
// is the profile picker initialized and brought onscreen. Therefore, we
// need to wait for the picker to appear before proceeding with the test.
ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
ProfilePicker::EntryPoint::kProfileMenuManageProfiles));
int counter = 5;
while (!ProfilePicker::IsActive() && counter--) {
base::TimeDelta delay = base::Seconds(1);
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), delay);
run_loop.Run();
}
EXPECT_TRUE(ProfilePicker::IsActive());
// The ProfilePicker is the new window in the list.
for (NSWindow* window in [NSApp windows]) {
if (![startingWindows containsObject:window]) {
return window;
}
}
return nil;
}
private:
raw_ptr<const BrowserList> active_browser_list_;
};
// Test that for a guest last profile, commandDispatch should open UserManager
// if guest mode is disabled. Note that this test might be flaky under ASAN
// due to https://crbug.com/674475. Please disable this test under ASAN
// as the tests below if that happened.
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
OpenGuestProfileOnlyIfGuestModeIsEnabled) {
SetGuestProfileAsLastProfile();
PrefService* local_state = g_browser_process->local_state();
local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
AppController* app_controller = AppController.sharedController;
NSMenu* menu = [app_controller applicationDockMenu:NSApp];
ASSERT_TRUE(menu);
NSMenuItem* item = [menu itemWithTag:IDC_NEW_WINDOW];
ASSERT_TRUE(item);
EXPECT_EQ(1u, active_browser_list()->size());
[app_controller commandDispatch:item];
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list()->size());
EXPECT_TRUE(ProfilePicker::IsOpen());
ProfilePicker::Hide();
local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, true);
[app_controller commandDispatch:item];
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2u, active_browser_list()->size());
EXPECT_FALSE(ProfilePicker::IsOpen());
}
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
AboutChromeGuestDisallowed) {
SetGuestProfileAsLastProfile();
// Disallow guest by policy and make sure "About Chrome" is not available
// in the menu.
PrefService* local_state = g_browser_process->local_state();
local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
NSMenuItem* about_menu_item = [[[NSApp.mainMenu itemWithTag:IDC_CHROME_MENU]
submenu] itemWithTag:IDC_ABOUT];
EXPECT_FALSE([AppController.sharedController
validateUserInterfaceItem:about_menu_item]);
}
// Test that for a regular last profile, a reopen event opens a browser.
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
RegularProfileReopenWithNoWindows) {
EXPECT_EQ(1u, active_browser_list()->size());
BOOL result =
[AppController.sharedController applicationShouldHandleReopen:NSApp
hasVisibleWindows:NO];
EXPECT_FALSE(result);
EXPECT_EQ(2u, active_browser_list()->size());
EXPECT_FALSE(ProfilePicker::IsOpen());
}
// Test that for a locked last profile, a reopen event opens the ProfilePicker.
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
LockedProfileReopenWithNoWindows) {
signin_util::ScopedForceSigninSetterForTesting signin_setter(true);
// The User Manager uses the system 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.
CreateAndWaitForSystemProfile();
AppController* app_controller = AppController.sharedController;
// Lock the active profile.
Profile* profile = [app_controller lastProfileIfLoaded];
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
ASSERT_NE(entry, nullptr);
entry->LockForceSigninProfile(true);
EXPECT_TRUE(entry->IsSigninRequired());
EXPECT_EQ(1u, active_browser_list()->size());
BOOL result = [app_controller applicationShouldHandleReopen:NSApp
hasVisibleWindows:NO];
EXPECT_FALSE(result);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list()->size());
EXPECT_TRUE(ProfilePicker::IsOpen());
ProfilePicker::Hide();
}
// "About Chrome" does not unlock the profile (regression test for
// https://crbug.com/1226844).
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
AboutPanelDoesNotUnlockProfile) {
signin_util::ScopedForceSigninSetterForTesting signin_setter(true);
// The User Manager uses the system 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.
CreateAndWaitForSystemProfile();
AppController* app_controller = AppController.sharedController;
// Lock the active profile.
Profile* profile = [app_controller lastProfileIfLoaded];
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
ASSERT_NE(entry, nullptr);
entry->LockForceSigninProfile(true);
EXPECT_TRUE(entry->IsSigninRequired());
EXPECT_EQ(1u, active_browser_list()->size());
Browser* browser = active_browser_list()->get(0);
EXPECT_FALSE(browser->profile()->IsGuestSession());
// "About Chrome" is not available in the menu.
NSMenu* chrome_submenu =
[[NSApp.mainMenu itemWithTag:IDC_CHROME_MENU] submenu];
NSMenuItem* about_menu_item = [chrome_submenu itemWithTag:IDC_ABOUT];
EXPECT_FALSE([app_controller validateUserInterfaceItem:about_menu_item]);
[chrome_submenu update];
EXPECT_FALSE([about_menu_item isEnabled]);
}
// Test that for a guest last profile, a reopen event opens the ProfilePicker.
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
GuestProfileReopenWithNoWindows) {
SetGuestProfileAsLastProfile();
EXPECT_EQ(1u, active_browser_list()->size());
BOOL result =
[AppController.sharedController applicationShouldHandleReopen:NSApp
hasVisibleWindows:NO];
EXPECT_FALSE(result);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list()->size());
EXPECT_TRUE(ProfilePicker::IsOpen());
ProfilePicker::Hide();
}
// Test that the ProfilePicker is shown when there are multiple profiles.
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
MultiProfilePickerShown) {
CreateAndWaitForSystemProfile();
// Add a profile in the cache (simulate another profile on disk).
ProfileManager* profile_manager = g_browser_process->profile_manager();
ProfileAttributesStorage* profile_storage =
&profile_manager->GetProfileAttributesStorage();
const base::FilePath profile_path =
profile_manager->GenerateNextProfileDirectoryPath();
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
profile_storage->AddProfile(std::move(params));
EXPECT_EQ(1u, active_browser_list()->size());
BOOL result =
[AppController.sharedController applicationShouldHandleReopen:NSApp
hasVisibleWindows:NO];
EXPECT_FALSE(result);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list()->size());
EXPECT_TRUE(ProfilePicker::IsOpen());
ProfilePicker::Hide();
}
// Checks that menu items and commands work when the profile picker is open.
IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest, MenuCommands) {
AppController* app_controller = AppController.sharedController;
// Bring the ProfilePicker onscreen. In normal browser operation, it would
// be the mainWindow, but with Ventura, the test harness can't activate
// Chrome, and -mainWindow can return nil. Use a workaround to make it the
// main window.
NSWindow* profileWindow = ActivateProfilePicker();
[app_controller setMainWindowForTesting:profileWindow];
// Menus are updated before they are brought onscreen. This includes a call
// to -menuNeedsUpdate: to update the menu's items.
NSMenu* file_submenu = [[NSApp.mainMenu itemWithTag:IDC_FILE_MENU] submenu];
[app_controller menuNeedsUpdate:file_submenu];
// The Profiler Picker has no tabs, so Close Tab should not be present.
NSMenuItem* close_tab_menu_item = [file_submenu itemWithTag:IDC_CLOSE_TAB];
EXPECT_EQ(nil, close_tab_menu_item);
// Close Window should be available.
NSMenuItem* close_window_menu_item =
[file_submenu itemWithTag:IDC_CLOSE_WINDOW];
EXPECT_FALSE([close_window_menu_item isHidden]);
EXPECT_TRUE([NSApp validateMenuItem:close_window_menu_item]);
// Make sure New Window works.
NSMenuItem* new_window_menu_item = [file_submenu itemWithTag:IDC_NEW_WINDOW];
EXPECT_TRUE([new_window_menu_item isEnabled]);
EXPECT_TRUE([app_controller validateUserInterfaceItem:new_window_menu_item]);
// Activate the item and check that a new browser is opened.
ui_test_utils::BrowserChangeObserver browser_added_observer(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
[file_submenu
performActionForItemAtIndex:[file_submenu
indexOfItem:new_window_menu_item]];
EXPECT_TRUE(browser_added_observer.Wait());
}
class AppControllerFirstRunBrowserTest : public AppControllerBrowserTest {
public:
void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
command_line->RemoveSwitch(switches::kNoFirstRun);
}
};
IN_PROC_BROWSER_TEST_F(AppControllerFirstRunBrowserTest,
OpenNewWindowWhileFreIsRunning) {
EXPECT_TRUE(ProfilePicker::IsFirstRunOpen());
EXPECT_EQ(BrowserList::GetInstance()->size(), 0u);
AppController* app_controller = AppController.sharedController;
NSMenu* menu = [app_controller applicationDockMenu:NSApp];
ASSERT_TRUE(menu);
NSMenuItem* item = [menu itemWithTag:IDC_NEW_WINDOW];
ASSERT_TRUE(item);
[app_controller commandDispatch:item];
profiles::testing::WaitForPickerClosed();
EXPECT_FALSE(ProfilePicker::IsFirstRunOpen());
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
}
IN_PROC_BROWSER_TEST_F(AppControllerFirstRunBrowserTest,
ClickingChromeDockIconDoesNotOpenBrowser) {
EXPECT_TRUE(ProfilePicker::IsFirstRunOpen());
EXPECT_EQ(BrowserList::GetInstance()->size(), 0u);
[AppController.sharedController applicationShouldHandleReopen:NSApp
hasVisibleWindows:NO];
EXPECT_EQ(BrowserList::GetInstance()->size(), 0u);
ProfilePicker::Hide();
}
class AppControllerOpenShortcutBrowserTest : public InProcessBrowserTest {
protected:
AppControllerOpenShortcutBrowserTest() = default;
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);
ASSERT_TRUE(destination);
method_exchangeImplementations(original, destination);
ASSERT_TRUE(embedded_test_server()->Start());
g_open_shortcut_url = embedded_test_server()->GetURL("/simple.html");
}
void SetUpCommandLine(base::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() = default;
void SetUpInProcessBrowserTestFixture() override {
ASSERT_TRUE(embedded_test_server()->Start());
}
void SetUpCommandLine(base::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.
// Flaky. See crbug.com/1234765.
IN_PROC_BROWSER_TEST_F(AppControllerReplaceNTPBrowserTest,
DISABLED_ReplaceNTPAfterStartup) {
// Depending on network connectivity, the NTP URL can either be
// chrome://newtab/ or chrome://new-tab-page-third-party. See
// ntp_test_utils::GetFinalNtpUrl for more details.
std::string expected_url =
ntp_test_utils::GetFinalNtpUrl(browser()->profile()).spec();
// Ensure that there is exactly 1 tab showing, and the tab is the NTP.
GURL ntp(expected_url);
EXPECT_EQ(1, browser()->tab_strip_model()->count());
browser()->tab_strip_model()->GetActiveWebContents()->GetController().LoadURL(
GURL(expected_url), content::Referrer(),
ui::PageTransition::PAGE_TRANSITION_LINK, std::string());
// Wait for one navigation on the active web contents.
content::TestNavigationObserver ntp_navigation_observer(
browser()->tab_strip_model()->GetActiveWebContents());
ntp_navigation_observer.Wait();
EXPECT_EQ(ntp,
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
GURL simple(embedded_test_server()->GetURL("/simple.html"));
SendOpenUrlToAppController(simple);
EXPECT_EQ(1, browser()->tab_strip_model()->count());
content::TestNavigationObserver event_navigation_observer(
browser()->tab_strip_model()->GetActiveWebContents());
event_navigation_observer.Wait();
EXPECT_EQ(simple,
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
// Tests that, even if an incognito browser is the last active browser, a GURL
// is opened in a regular (non-incognito) browser.
// Regression test for https://crbug.com/757253, https://crbug.com/1444747
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, OpenInRegularBrowser) {
ASSERT_TRUE(embedded_test_server()->Start());
AppController* ac =
base::apple::ObjCCastStrict<AppController>([NSApp delegate]);
ASSERT_TRUE(ac);
// Create an incognito browser and make it the last active browser.
Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
EXPECT_EQ(1, browser()->tab_strip_model()->count());
EXPECT_EQ(1, incognito_browser->tab_strip_model()->count());
EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile());
EXPECT_EQ(incognito_browser, chrome::FindLastActive());
// Assure that `windowDidBecomeMain` is called even if this browser process
// lost focus because of other browser processes in other shards taking
// focus. It prevents flakiness.
// See: https://crrev.com/c/4530255/comments/2aadb9cf_9a39d4bf
[[NSNotificationCenter defaultCenter]
postNotificationName:NSWindowDidBecomeMainNotification
object:incognito_browser->window()
->GetNativeWindow()
.GetNativeNSWindow()];
// Open a url.
GURL simple(embedded_test_server()->GetURL("/simple.html"));
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(simple);
event_navigation_observer.Wait();
// It should be opened in the regular browser.
EXPECT_EQ(2, browser()->tab_strip_model()->count());
EXPECT_EQ(1, incognito_browser->tab_strip_model()->count());
EXPECT_EQ(simple, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
// Tests that, even if only an incognito browser is currently opened, a GURL
// is opened in a regular (non-incognito) browser.
// Regression test for https://crbug.com/757253, https://crbug.com/1444747
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest,
OpenInRegularBrowserWhenOnlyIncognitoBrowserIsOpened) {
ASSERT_TRUE(embedded_test_server()->Start());
AppController* ac =
base::apple::ObjCCastStrict<AppController>([NSApp delegate]);
ASSERT_TRUE(ac);
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
// Close the current browser.
Profile* profile = browser()->profile();
chrome::CloseAllBrowsers();
ui_test_utils::WaitForBrowserToClose();
EXPECT_TRUE(BrowserList::GetInstance()->empty());
// Create an incognito browser and check that it is the last active browser.
Browser* incognito_browser = CreateIncognitoBrowser(profile);
EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile());
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
EXPECT_EQ(incognito_browser, chrome::FindLastActive());
// Assure that `windowDidBecomeMain` is called even if this browser process
// lost focus because of other browser processes in other shards taking
// focus. It prevents flakiness.
// See: https://crrev.com/c/4530255/comments/2aadb9cf_9a39d4bf
[[NSNotificationCenter defaultCenter]
postNotificationName:NSWindowDidBecomeMainNotification
object:incognito_browser->window()
->GetNativeWindow()
.GetNativeNSWindow()];
// Open a url.
GURL simple(embedded_test_server()->GetURL("/simple.html"));
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(simple);
event_navigation_observer.Wait();
// Check that a new regular browser is opened
// and the url is opened in the regular browser.
Browser* new_browser = chrome::FindLastActive();
EXPECT_EQ(BrowserList::GetInstance()->size(), 2u);
EXPECT_TRUE(new_browser->profile()->IsRegularProfile());
EXPECT_EQ(profile, new_browser->profile());
EXPECT_EQ(simple, new_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
// Tests that, if a guest browser is the last active browser, a GURL is opened
// in the guest browser.
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, OpenUrlInGuestBrowser) {
ASSERT_TRUE(embedded_test_server()->Start());
AppController* ac =
base::apple::ObjCCastStrict<AppController>([NSApp delegate]);
ASSERT_TRUE(ac);
// Create a guest browser and make it the last active browser.
Browser* guest_browser = CreateGuestBrowser();
EXPECT_EQ(1, browser()->tab_strip_model()->count());
EXPECT_EQ(1, guest_browser->tab_strip_model()->count());
EXPECT_TRUE(guest_browser->profile()->IsGuestSession());
guest_browser->window()->Show();
EXPECT_EQ(guest_browser, chrome::FindLastActive());
// Assure that `windowDidBecomeMain` is called even if this browser process
// lost focus because of other browser processes in other shards taking
// focus. It prevents flakiness.
// See: https://crrev.com/c/4530255/comments/2aadb9cf_9a39d4bf
[[NSNotificationCenter defaultCenter]
postNotificationName:NSWindowDidBecomeMainNotification
object:guest_browser->window()
->GetNativeWindow()
.GetNativeNSWindow()];
// Open a url.
GURL simple(embedded_test_server()->GetURL("/simple.html"));
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(simple);
event_navigation_observer.Wait();
// It should be opened in the guest browser.
EXPECT_EQ(1, browser()->tab_strip_model()->count());
EXPECT_EQ(2, guest_browser->tab_strip_model()->count());
EXPECT_EQ(simple, guest_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
// Tests that when a GURL is opened while incognito forced and there is no
// browser opened, it is opened in a new incognito browser.
// Test for https://crbug.com/1444747#c8
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, OpenUrlWhenForcedIncognito) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
// Close the current non-incognito browser.
Profile* profile = browser()->profile();
chrome::CloseAllBrowsers();
ui_test_utils::WaitForBrowserToClose();
EXPECT_TRUE(BrowserList::GetInstance()->empty());
// Force incognito mode.
IncognitoModePrefs::SetAvailability(
profile->GetPrefs(), policy::IncognitoModeAvailability::kForced);
// Open a url.
GURL simple(embedded_test_server()->GetURL("/simple.html"));
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(simple);
event_navigation_observer.Wait();
// Check that a new incognito browser is opened
// and the url is opened in the incognito browser.
Browser* new_browser = chrome::FindLastActive();
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
EXPECT_TRUE(new_browser->profile()->IsIncognitoProfile());
EXPECT_TRUE(new_browser->profile()->IsPrimaryOTRProfile());
EXPECT_EQ(profile, new_browser->profile()->GetOriginalProfile());
EXPECT_EQ(simple, new_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
// Tests that when a GURL is opened while incognito forced and an incognito
// browser is opened, it is opened in the already opened incognito browser.
// Test for https://crbug.com/1444747#c8
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest,
OpenUrlWhenForcedIncognitoAndIncognitoBrowserIsOpened) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
// Close the current non-incognito browser.
Profile* profile = browser()->profile();
chrome::CloseAllBrowsers();
ui_test_utils::WaitForBrowserToClose();
EXPECT_TRUE(BrowserList::GetInstance()->empty());
// Force incognito mode.
IncognitoModePrefs::SetAvailability(
profile->GetPrefs(), policy::IncognitoModeAvailability::kForced);
// Create an incognito browser.
Browser* incognito_browser = CreateIncognitoBrowser(profile);
EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile());
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
EXPECT_EQ(1, incognito_browser->tab_strip_model()->count());
EXPECT_EQ(incognito_browser, chrome::FindLastActive());
// Assure that `windowDidBecomeMain` is called even if this browser process
// lost focus because of other browser processes in other shards taking
// focus. It prevents flakiness.
// See: https://crrev.com/c/4530255/comments/2aadb9cf_9a39d4bf
[[NSNotificationCenter defaultCenter]
postNotificationName:NSWindowDidBecomeMainNotification
object:incognito_browser->window()
->GetNativeWindow()
.GetNativeNSWindow()];
// Open a url.
GURL simple(embedded_test_server()->GetURL("/simple.html"));
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(simple);
event_navigation_observer.Wait();
// Check the url is opened in the already opened incognito browser.
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
EXPECT_EQ(2, incognito_browser->tab_strip_model()->count());
EXPECT_EQ(simple, incognito_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
class AppControllerShortcutsNotAppsBrowserTest : public InProcessBrowserTest {
protected:
AppControllerShortcutsNotAppsBrowserTest() {
features_.InitAndEnableFeature(features::kShortcutsNotApps);
}
base::test::ScopedFeatureList features_;
};
IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
OpenChromeWeblocFile) {
ASSERT_TRUE(embedded_test_server()->Start());
AppController* ac =
base::apple::ObjCCastStrict<AppController>([NSApp delegate]);
ASSERT_TRUE(ac);
// Create and open a .crwebloc file
GURL simple(embedded_test_server()->GetURL("/simple.html"));
base::ScopedTempDir temp_dir;
base::FilePath crwebloc_file;
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
crwebloc_file = temp_dir.GetPath().AppendASCII("test shortcut.crwebloc");
ASSERT_TRUE(shortcuts::ChromeWeblocFile(
simple, *base::SafeBaseName::Create(
browser()->profile()->GetPath()))
.SaveToFile(crwebloc_file));
}
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(net::FilePathToFileURL(crwebloc_file));
event_navigation_observer.Wait();
// It should be opened in the regular browser.
EXPECT_EQ(2, browser()->tab_strip_model()->count());
EXPECT_EQ(simple, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(temp_dir.Delete());
}
}
IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
OpenChromeWeblocFileInSecondProfile) {
ASSERT_TRUE(embedded_test_server()->Start());
AppController* ac =
base::apple::ObjCCastStrict<AppController>([NSApp delegate]);
ASSERT_TRUE(ac);
// Create profile 2.
Profile* profile2_ptr = nullptr;
{
base::ScopedAllowBlockingForTesting allow_blocking;
ProfileManager* profile_manager = g_browser_process->profile_manager();
profile2_ptr = profile_manager->GetProfile(
profile_manager->GenerateNextProfileDirectoryPath());
}
// Create and open a .crwebloc file
GURL simple(embedded_test_server()->GetURL("/simple.html"));
base::ScopedTempDir temp_dir;
base::FilePath crwebloc_file;
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
crwebloc_file = temp_dir.GetPath().AppendASCII("test shortcut.crwebloc");
ASSERT_TRUE(
shortcuts::ChromeWeblocFile(
simple, *base::SafeBaseName::Create(profile2_ptr->GetPath()))
.SaveToFile(crwebloc_file));
}
content::TestNavigationObserver event_navigation_observer(simple);
event_navigation_observer.StartWatchingNewWebContents();
SendOpenUrlToAppController(net::FilePathToFileURL(crwebloc_file));
event_navigation_observer.Wait();
// It should be opened in a new browser in the second profile.
EXPECT_EQ(1, browser()->tab_strip_model()->count());
Browser* new_browser = chrome::FindLastActive();
EXPECT_EQ(profile2_ptr, new_browser->profile());
EXPECT_EQ(1, new_browser->tab_strip_model()->count());
EXPECT_EQ(simple, new_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(temp_dir.Delete());
}
}
IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
LockedProfileOpensProfilePicker) {
// Flag the profile picker as already shown in the past, to avoid additional
// feature onboarding logic.
g_browser_process->local_state()->SetBoolean(
prefs::kBrowserProfilePickerShown, true);
signin_util::ScopedForceSigninSetterForTesting signin_setter(true);
// The User Manager uses the system 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.
CreateAndWaitForSystemProfile();
AppController* app_controller = AppController.sharedController;
// Lock the active profile.
Profile* profile = [app_controller lastProfileIfLoaded];
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
ASSERT_NE(entry, nullptr);
entry->LockForceSigninProfile(true);
EXPECT_TRUE(entry->IsSigninRequired());
// Create and open a .crwebloc file
GURL simple("https://simple.invalid/");
base::ScopedTempDir temp_dir;
base::FilePath crwebloc_file;
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
crwebloc_file = temp_dir.GetPath().AppendASCII("test shortcut.crwebloc");
ASSERT_TRUE(shortcuts::ChromeWeblocFile(
simple, *base::SafeBaseName::Create(profile->GetPath()))
.SaveToFile(crwebloc_file));
}
SendOpenUrlToAppController(net::FilePathToFileURL(crwebloc_file));
auto* active_browser_list = BrowserList::GetInstance();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, active_browser_list->size());
EXPECT_TRUE(ProfilePicker::IsOpen());
ProfilePicker::Hide();
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(temp_dir.Delete());
}
}
class AppControllerMainMenuBrowserTest : public InProcessBrowserTest {
protected:
AppControllerMainMenuBrowserTest() = default;
};
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
HistoryMenuResetAfterProfileDeletion) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
AppController* app_controller = AppController.sharedController;
// Use the existing profile as profile 1.
Profile* profile1 = browser()->profile();
// Create profile 2.
base::FilePath profile2_path =
profile_manager->GenerateNextProfileDirectoryPath();
Profile& profile2 =
profiles::testing::CreateProfileSync(profile_manager, profile2_path);
// Load profile1's History Service backend so it will be assigned to the
// HistoryMenuBridge when setLastProfile is called, or else this test will
// fail flaky.
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
profile1, ServiceAccessType::EXPLICIT_ACCESS));
// Switch the controller to profile1.
[app_controller setLastProfile:profile1];
base::RunLoop().RunUntilIdle();
// Verify the controller's History Menu corresponds to profile1.
EXPECT_TRUE([app_controller historyMenuBridge]->service());
EXPECT_EQ([app_controller historyMenuBridge]->service(),
HistoryServiceFactory::GetForProfile(
profile1, ServiceAccessType::EXPLICIT_ACCESS));
// Load profile2's History Service backend so it will be assigned to the
// HistoryMenuBridge when setLastProfile is called, or else this test will
// fail flaky.
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
&profile2, ServiceAccessType::EXPLICIT_ACCESS));
// Switch the controller to profile2.
[app_controller setLastProfile:&profile2];
base::RunLoop().RunUntilIdle();
// Verify the controller's History Menu has changed.
EXPECT_TRUE([app_controller historyMenuBridge]->service());
EXPECT_EQ([app_controller historyMenuBridge]->service(),
HistoryServiceFactory::GetForProfile(
&profile2, ServiceAccessType::EXPLICIT_ACCESS));
EXPECT_NE(HistoryServiceFactory::GetForProfile(
profile1, ServiceAccessType::EXPLICIT_ACCESS),
HistoryServiceFactory::GetForProfile(
&profile2, ServiceAccessType::EXPLICIT_ACCESS));
// Delete profile2.
profile_manager->GetDeleteProfileHelper().MaybeScheduleProfileForDeletion(
profile2.GetPath(), base::DoNothing(),
ProfileMetrics::DELETE_PROFILE_USER_MANAGER);
content::RunAllTasksUntilIdle();
// Verify the controller's history is back to profile1.
EXPECT_EQ([app_controller historyMenuBridge]->service(),
HistoryServiceFactory::GetForProfile(
profile1, ServiceAccessType::EXPLICIT_ACCESS));
}
// Disabled because of flakiness. See crbug.com/1278031.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
DISABLED_ReloadingDestroyedProfileDoesNotCrash) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
AppController* app_controller = AppController.sharedController;
Profile* profile = browser()->profile();
base::FilePath profile_path = profile->GetPath();
// Switch the controller to |profile|.
[app_controller setLastProfile:profile];
base::RunLoop().RunUntilIdle();
EXPECT_EQ(profile, [app_controller lastProfileIfLoaded]);
// Trigger Profile* destruction. Note that this event (destruction from
// memory) is a separate event from profile deletion (from disk).
chrome::CloseAllBrowsers();
ProfileDestructionWaiter(profile).Wait();
EXPECT_EQ(nullptr, [app_controller lastProfileIfLoaded]);
// Re-open the profile. Since the Profile* is destroyed, this involves loading
// it from disk.
base::ScopedAllowBlockingForTesting allow_blocking;
profile = profile_manager->GetProfile(profile_path);
[app_controller setLastProfile:profile];
base::RunLoop().RunUntilIdle();
// We mostly want to make sure re-loading the same profile didn't cause a
// crash. This means we didn't have e.g. a dangling ProfilePrefRegistrar, or
// observers pointing to the old (now dead) Profile.
EXPECT_EQ(profile, [app_controller lastProfileIfLoaded]);
}
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
BookmarksMenuIsRestoredAfterProfileSwitch) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
AppController* app_controller = AppController.sharedController;
[app_controller mainMenuCreated];
// Constants for bookmarks that we will create later.
const std::u16string title1(u"Dinosaur Comics");
const GURL url1("http://qwantz.com//");
const std::u16string title2(u"XKCD");
const GURL url2("https://www.xkcd.com/");
// Use the existing profile as profile 1.
Profile* profile1 = browser()->profile();
bookmarks::test::WaitForBookmarkModelToLoad(
BookmarkModelFactory::GetForBrowserContext(profile1));
// Create profile 2.
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath path2 = profile_manager->GenerateNextProfileDirectoryPath();
std::unique_ptr<Profile> profile2 =
Profile::CreateProfile(path2, nullptr, Profile::CreateMode::kSynchronous);
Profile* profile2_ptr = profile2.get();
profile_manager->RegisterTestingProfile(std::move(profile2), false);
bookmarks::test::WaitForBookmarkModelToLoad(
BookmarkModelFactory::GetForBrowserContext(profile2_ptr));
// Switch to profile 1, create bookmark 1 and force the menu to build.
[app_controller setLastProfile:profile1];
[app_controller bookmarkMenuBridge]->GetBookmarkModel()
-> AddURL([app_controller bookmarkMenuBridge]->GetBookmarkModel()
-> bookmark_bar_node(),
0, title1, url1);
NSMenu* profile1_submenu =
[app_controller bookmarkMenuBridge]->BookmarkMenu();
[[profile1_submenu delegate] menuNeedsUpdate:profile1_submenu];
// Switch to profile 2, create bookmark 2 and force the menu to build.
[app_controller setLastProfile:profile2_ptr];
[app_controller bookmarkMenuBridge]->GetBookmarkModel()
-> AddURL([app_controller bookmarkMenuBridge]->GetBookmarkModel()
-> bookmark_bar_node(),
0, title2, url2);
NSMenu* profile2_submenu =
[app_controller bookmarkMenuBridge]->BookmarkMenu();
[[profile2_submenu delegate] menuNeedsUpdate:profile2_submenu];
EXPECT_NE(profile1_submenu, profile2_submenu);
// Test that only bookmark 2 is shown.
EXPECT_FALSE([[app_controller bookmarkMenuBridge]->BookmarkMenu()
itemWithTitle:base::SysUTF16ToNSString(title1)]);
EXPECT_TRUE([[app_controller bookmarkMenuBridge]->BookmarkMenu()
itemWithTitle:base::SysUTF16ToNSString(title2)]);
// Switch *back* to profile 1 and *don't* force the menu to build.
[app_controller setLastProfile:profile1];
// Test that only bookmark 1 is shown in the restored menu.
EXPECT_TRUE([[app_controller bookmarkMenuBridge]->BookmarkMenu()
itemWithTitle:base::SysUTF16ToNSString(title1)]);
EXPECT_FALSE([[app_controller bookmarkMenuBridge]->BookmarkMenu()
itemWithTitle:base::SysUTF16ToNSString(title2)]);
// Ensure a cached menu was used.
EXPECT_EQ(profile1_submenu,
[app_controller bookmarkMenuBridge]->BookmarkMenu());
}
// Tests opening a new window from a browser command while incognito is forced.
// Regression test for https://crbug.com/1206726
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
ForcedIncognito_NewWindow) {
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
// Close the current non-incognito browser.
Profile* profile = browser()->profile();
chrome::CloseAllBrowsers();
ui_test_utils::WaitForBrowserToClose();
EXPECT_TRUE(BrowserList::GetInstance()->empty());
// Force incognito mode.
IncognitoModePrefs::SetAvailability(
profile->GetPrefs(), policy::IncognitoModeAvailability::kForced);
// Simulate click on "New window".
ui_test_utils::BrowserChangeObserver browser_added_observer(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
AppController* app_controller = AppController.sharedController;
NSMenu* menu = [app_controller applicationDockMenu:NSApp];
ASSERT_TRUE(menu);
NSMenuItem* item = [menu itemWithTag:IDC_NEW_WINDOW];
ASSERT_TRUE(item);
[app_controller commandDispatch:item];
// Check that a new incognito browser is opened.
Browser* new_browser = browser_added_observer.Wait();
EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
EXPECT_TRUE(new_browser->profile()->IsPrimaryOTRProfile());
EXPECT_EQ(profile, new_browser->profile()->GetOriginalProfile());
}
} // namespace
//--------------------------AppControllerHandoffBrowserTest---------------------
static GURL g_handoff_url;
static std::u16string g_handoff_title;
@interface AppController (BrowserTest)
- (void)new_updateHandoffManagerWithURL:(const GURL&)handoffURL
title:(const std::u16string&)handoffTitle;
@end
@implementation AppController (BrowserTest)
- (void)new_updateHandoffManagerWithURL:(const GURL&)handoffURL
title:(const std::u16string&)handoffTitle {
g_handoff_url = handoffURL;
g_handoff_title = handoffTitle;
}
@end
namespace {
class AppControllerHandoffBrowserTest : public InProcessBrowserTest {
protected:
// Swizzle Handoff related implementations.
void SetUpInProcessBrowserTestFixture() override {
// This swizzle intercepts the URL that would be sent to the Handoff
// Manager, and instead puts it into a variable accessible to this test.
swizzler_ = std::make_unique<base::apple::ScopedObjCClassSwizzler>(
[AppController class], @selector(updateHandoffManagerWithURL:title:),
@selector(new_updateHandoffManagerWithURL:title:));
}
void TearDownInProcessBrowserTestFixture() override { swizzler_.reset(); }
// Closes the tab, and waits for the close to finish.
void CloseTab(Browser* browser, int index) {
content::WebContentsDestroyedWatcher destroyed_watcher(
browser->tab_strip_model()->GetWebContentsAt(index));
browser->tab_strip_model()->CloseWebContentsAt(
index, TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB);
destroyed_watcher.Wait();
}
private:
std::unique_ptr<base::apple::ScopedObjCClassSwizzler> swizzler_;
};
// Tests that as a user switches between tabs, navigates within a tab, and
// switches between browser windows, the correct URL is being passed to the
// Handoff.
IN_PROC_BROWSER_TEST_F(AppControllerHandoffBrowserTest, TestHandoffURLs) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(g_handoff_url, GURL(url::kAboutBlankURL));
EXPECT_EQ(g_handoff_title, u"about:blank");
// Test that navigating to a URL updates the handoff manager.
GURL test_url1 = embedded_test_server()->GetURL("/title1.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url1));
EXPECT_EQ(g_handoff_url, test_url1);
EXPECT_TRUE(base::EndsWith(g_handoff_title, u"title1.html"));
// Test that opening a new tab updates the handoff URL.
GURL test_url2 = embedded_test_server()->GetURL("/title2.html");
NavigateParams params(browser(), test_url2, ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
ui_test_utils::NavigateToURL(¶ms);
EXPECT_EQ(g_handoff_url, test_url2);
// Test that switching tabs updates the handoff URL.
browser()->tab_strip_model()->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
EXPECT_EQ(g_handoff_url, test_url1);
EXPECT_TRUE(base::EndsWith(g_handoff_title, u"title1.html"));
// Test that closing the current tab updates the handoff URL.
CloseTab(browser(), 0);
EXPECT_EQ(g_handoff_url, test_url2);
EXPECT_EQ(g_handoff_title, u"Title Of Awesomeness");
// Test that opening a new browser window updates the handoff URL.
GURL test_url3 = embedded_test_server()->GetURL("/title3.html");
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(test_url3), WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
EXPECT_EQ(g_handoff_url, test_url3);
EXPECT_EQ(g_handoff_title, u"Title Of More Awesomeness");
// Check that there are exactly 2 browsers.
BrowserList* active_browser_list = BrowserList::GetInstance();
EXPECT_EQ(2u, active_browser_list->size());
// Close the second browser window (which only has 1 tab left).
Browser* browser2 = active_browser_list->get(1);
CloseBrowserSynchronously(browser2);
EXPECT_EQ(g_handoff_url, test_url2);
EXPECT_EQ(g_handoff_title, u"Title Of Awesomeness");
// The URLs of incognito windows should not be passed to Handoff.
GURL test_url4 = embedded_test_server()->GetURL("/simple.html");
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(test_url4), WindowOpenDisposition::OFF_THE_RECORD,
ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
EXPECT_EQ(g_handoff_url, GURL());
EXPECT_EQ(g_handoff_title, u"");
// Open a new tab in the incognito window.
EXPECT_EQ(2u, active_browser_list->size());
Browser* browser3 = active_browser_list->get(1);
ui_test_utils::NavigateToURLWithDisposition(
browser3, test_url4, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
EXPECT_EQ(g_handoff_url, GURL());
EXPECT_EQ(g_handoff_title, u"");
// Navigate the current tab in the incognito window.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser3, test_url1));
EXPECT_EQ(g_handoff_url, GURL());
EXPECT_EQ(g_handoff_title, u"");
// Activate the original browser window.
Browser* browser1 = active_browser_list->get(0);
browser1->window()->Show();
EXPECT_EQ(g_handoff_url, test_url2);
EXPECT_EQ(g_handoff_title, u"Title Of Awesomeness");
}
class AppControllerHandoffPrerenderBrowserTest
: public AppControllerHandoffBrowserTest {
public:
void SetUpOnMainThread() override {
prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->ServeFilesFromDirectory(
base::PathService::CheckedGet(chrome::DIR_TEST_DATA));
ASSERT_TRUE(embedded_test_server()->Start());
}
content::WebContents* GetActiveWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::test::PrerenderTestHelper& prerender_helper() {
return prerender_helper_;
}
protected:
AppControllerHandoffPrerenderBrowserTest()
: prerender_helper_(base::BindRepeating(
&AppControllerHandoffPrerenderBrowserTest::GetActiveWebContents,
// Unretained is safe here, as this class owns PrerenderTestHelper
// object, which holds the callback being constructed here, so the
// callback will be destructed before this class.
base::Unretained(this))) {}
private:
content::test::PrerenderTestHelper prerender_helper_;
};
// Tests that as a user switches from main page to prerendered page, the correct
// URL is being passed to the Handoff.
IN_PROC_BROWSER_TEST_F(AppControllerHandoffPrerenderBrowserTest,
TestHandoffURLs) {
// Navigate to an initial page.
GURL url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url));
// Start a prerender.
GURL prerender_url = embedded_test_server()->GetURL("/simple.html");
prerender_helper().AddPrerender(prerender_url);
EXPECT_EQ(g_handoff_url, url);
// Activate.
content::TestActivationManager navigation_manager(GetActiveWebContents(),
prerender_url);
ASSERT_TRUE(
content::ExecJs(GetActiveWebContents()->GetPrimaryMainFrame(),
content::JsReplace("location = $1", prerender_url)));
navigation_manager.WaitForNavigationFinished();
EXPECT_TRUE(navigation_manager.was_activated());
EXPECT_TRUE(navigation_manager.was_successful());
EXPECT_EQ(g_handoff_url, prerender_url);
}
} // namespace