// 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 <vector>

#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/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.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_merged_surface_service.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/bookmark_test_helpers.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_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
#include "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
#include "chrome/browser/ui/cocoa/history_menu_bridge.h"
#include "chrome/browser/ui/cocoa/test/run_loop_testing.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.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/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/no_renderer_crashes_assertion.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"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.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

@interface AppController (ConfirmQuitPanelTesting)
- (ConfirmQuitResult)test_confirmQuitIfNeeded;
@end

@implementation AppController (ConfirmQuitPanelTesting)

- (ConfirmQuitResult)test_confirmQuitIfNeeded {
  return ConfirmQuitResultAborted;
}

@end

static BOOL g_use_mock_current_event = NO;
static NSEvent* g_mock_current_event = nil;

@interface NSApplication (ConfirmQuitTesting)
- (NSEvent*)test_currentEvent;
@end

@implementation NSApplication (ConfirmQuitTesting)
- (NSEvent*)test_currentEvent {
  if (g_use_mock_current_event) {
    return g_mock_current_event;
  }
  return [self test_currentEvent];
}
@end

@interface ConfirmQuitPanelController (Testing)
- (BOOL)test_runConfirmQuitLoopWithEvent:(NSEvent*)event
                       dismissedCallback:(void (^)())dismissedCallback;
@end

@implementation ConfirmQuitPanelController (Testing)
- (BOOL)test_runConfirmQuitLoopWithEvent:(NSEvent*)event
                       dismissedCallback:(void (^)())dismissedCallback {
  return YES;
}
@end

namespace {

class ScopedMockCurrentEvent {
 public:
  explicit ScopedMockCurrentEvent(NSEvent* event) {
    g_mock_current_event = event;
    g_use_mock_current_event = YES;
    swizzler_ = std::make_unique<base::apple::ScopedObjCClassSwizzler>(
        [NSApplication class], @selector(currentEvent),
        @selector(test_currentEvent));
  }

  ~ScopedMockCurrentEvent() {
    g_mock_current_event = nil;
    g_use_mock_current_event = NO;
  }

 private:
  std::unique_ptr<base::apple::ScopedObjCClassSwizzler> swizzler_;
};

class ScopedMockConfirmQuitPanel {
 public:
  ScopedMockConfirmQuitPanel() {
    swizzler_ = std::make_unique<base::apple::ScopedObjCClassSwizzler>(
        [ConfirmQuitPanelController class],
        @selector(runConfirmQuitLoopWithEvent:dismissedCallback:),
        @selector(test_runConfirmQuitLoopWithEvent:dismissedCallback:));
  }
  ~ScopedMockConfirmQuitPanel() = default;

 private:
  std::unique_ptr<base::apple::ScopedObjCClassSwizzler> swizzler_;
};

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, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_EQ(ExpectedWindowCountForBrowserCount(1), CountVisibleWindows());

  // Set chrome::IsTryingToQuit and close all windows. Closing the window here
  // closes the Browser and marks it for deletion and, if that was the last
  // Browser, it invokes applicationWillTerminate: (because IsTryingToQuit is
  // set). So, verify assumptions then process that autorelease.
  ui_test_utils::BrowserDestroyedObserver browser_destroyed_observer;
  chrome::AttemptExit();
  browser_destroyed_observer.Wait();

  EXPECT_EQ(0u, GlobalBrowserCollection::GetInstance()->GetSize());
  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.
}

IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest,
                       CancelConfirmQuitResetsClosingAllBrowsers) {
  base::apple::ScopedObjCClassSwizzler swizzler(
      [AppController class], @selector(confirmQuitIfNeeded),
      @selector(test_confirmQuitIfNeeded));

  std::vector<bool> closing_all_browsers_notifications;
  base::CallbackListSubscription subscription =
      chrome::AddClosingAllBrowsersCallback(base::BindRepeating(
          [](std::vector<bool>* notifications, bool closing) {
            notifications->push_back(closing);
          },
          base::Unretained(&closing_all_browsers_notifications)));

  chrome::AttemptUserExit();

  ASSERT_EQ(2u, closing_all_browsers_notifications.size());
  EXPECT_TRUE(closing_all_browsers_notifications[0]);
  EXPECT_FALSE(closing_all_browsers_notifications[1]);
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
}

// Regression test for a shutdown race in the AppKit "About" menu path.
//
// Browser::Create() (chrome/browser/ui/browser.cc) CHECKs that a Browser can
// be created for the given profile (GetCreationStatusForProfile == kOk).
// Callers are expected to honor that contract; once the process is shutting
// down the status flips to kErrorShuttingDown and any unguarded
// Browser::Create() call trips the CHECK_EQ.
//
// The "About" menu item runs async: RunInLastProfileSafely() loads the
// profile, then the callback calls OpenAboutWindow() -> Browser::Create().
// If shutdown begins between the click and the callback firing, the callback
// lands in a shutting-down process and the unguarded create crashes.
//
// With the OnProfileLoaded() shutdown guard, the callback short-circuits
// (no Browser, no profile picker). Without it, this test crashes on the
// CHECK_EQ.
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, AboutPanelDuringShutdown) {
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());

  ui_test_utils::BrowserDestroyedObserver browser_destroyed_observer;
  chrome::AttemptExit();
  browser_destroyed_observer.Wait();
  ASSERT_TRUE(g_browser_process->IsShuttingDown());

  [AppController.sharedController orderFrontStandardAboutPanel:nil];

  // The non-kOk branch in OnProfileLoaded short-circuits silently rather than
  // falling through to kShowProfilePickerOnFailure.
  EXPECT_EQ(0u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_FALSE(ProfilePicker::IsOpen());
}

class AppControllerKeepAliveBrowserTest : public InProcessBrowserTest {
 protected:
  AppControllerKeepAliveBrowserTest() {
    features_.InitAndEnableFeature(features::kDestroyProfileOnBrowserClose);
  }

  base::test::ScopedFeatureList features_;
};

class AppControllerPlatformAppBrowserTest
    : public extensions::PlatformAppBrowserTest {
 protected:
  // extensions::PlatformAppBrowserTest:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    PlatformAppBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(switches::kAppId,
                                    "1234");
  }
};

// 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, GlobalBrowserCollection::GetInstance()->GetSize());
  [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, GlobalBrowserCollection::GetInstance()->GetSize());
}

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()->GetWindow()->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:
  // InProcessBrowserTest:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(switches::kApp, GetAppURL());
  }

  std::string GetAppURL() const {
    return "https://example.com/";
  }
};

// Test that in web app mode a reopen event opens the app URL.
IN_PROC_BROWSER_TEST_F(AppControllerWebAppBrowserTest,
                       WebAppReopenWithNoWindows) {
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
  BOOL result =
      [AppController.sharedController applicationShouldHandleReopen:NSApp
                                                  hasVisibleWindows:NO];

  EXPECT_FALSE(result);
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());

  GURL current_url =
      browser()->GetTabStripModel()->GetActiveWebContents()->GetURL();
  EXPECT_EQ(GetAppURL(), current_url.spec());
}

class AppControllerProfilePickerBrowserTest : public InProcessBrowserTest {
 public:
  // InProcessBrowserTest:
  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);
  }

  // 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;
  }
};

// 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/40498250. 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, GlobalBrowserCollection::GetInstance()->GetSize());

  [app_controller commandDispatch:item];

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_TRUE(ProfilePicker::IsOpen());
  ProfilePicker::Hide();

  local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, true);
  [app_controller commandDispatch:item];
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  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, GlobalBrowserCollection::GetInstance()->GetSize());
  BOOL result =
      [AppController.sharedController applicationShouldHandleReopen:NSApp
                                                  hasVisibleWindows:NO];

  EXPECT_FALSE(result);
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  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, GlobalBrowserCollection::GetInstance()->GetSize());
  BOOL result = [app_controller applicationShouldHandleReopen:NSApp
                                            hasVisibleWindows:NO];
  EXPECT_FALSE(result);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_TRUE(ProfilePicker::IsOpen());
  ProfilePicker::Hide();
}

// "About Chrome" does not unlock the profile (regression test for
// https://crbug.com/40188984).
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, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_FALSE(browser()->GetProfile()->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, GlobalBrowserCollection::GetInstance()->GetSize());
  BOOL result =
      [AppController.sharedController applicationShouldHandleReopen:NSApp
                                                  hasVisibleWindows:NO];
  EXPECT_FALSE(result);

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
  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, GlobalBrowserCollection::GetInstance()->GetSize());
  BOOL result =
      [AppController.sharedController applicationShouldHandleReopen:NSApp
                                                  hasVisibleWindows:NO];
  EXPECT_FALSE(result);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
  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::BrowserCreatedObserver browser_created_observer;
  [file_submenu
      performActionForItemAtIndex:[file_submenu
                                      indexOfItem:new_window_menu_item]];
  EXPECT_TRUE(browser_created_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(GlobalBrowserCollection::GetInstance()->GetSize(), 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(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
}

IN_PROC_BROWSER_TEST_F(AppControllerFirstRunBrowserTest,
                       ClickingChromeDockIconDoesNotOpenBrowser) {
  EXPECT_TRUE(ProfilePicker::IsFirstRunOpen());
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 0u);
  [AppController.sharedController applicationShouldHandleReopen:NSApp
                                              hasVisibleWindows:NO];

  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 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/40781670.
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()->GetProfile()).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/40536115, https://crbug.com/40912038
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, OpenInRegularBrowser) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Ensure the AppController is the NSApp delegate.
  std::ignore = AppController.sharedController;

  // Create an incognito browser and make it the last active browser.
  Browser* incognito_browser = CreateIncognitoBrowser(browser()->GetProfile());
  EXPECT_EQ(1, browser()->tab_strip_model()->count());
  EXPECT_EQ(1, incognito_browser->tab_strip_model()->count());
  EXPECT_TRUE(incognito_browser->GetProfile()->IsIncognitoProfile());
  EXPECT_EQ(incognito_browser,
            GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser());
  // 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->GetWindow()
                               ->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/40536115, https://crbug.com/40912038
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest,
                       OpenInRegularBrowserWhenOnlyIncognitoBrowserIsOpened) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Ensure the AppController is the NSApp delegate.
  std::ignore = AppController.sharedController;

  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  // Create an incognito browser.
  Profile* profile = browser()->GetProfile();
  Browser* incognito_browser = CreateIncognitoBrowser(profile);
  EXPECT_TRUE(incognito_browser->GetProfile()->IsIncognitoProfile());
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);

  // Close the original browser.
  CloseBrowserSynchronously(browser());
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);

  // Check that the incognito browser is the last active browser.
  EXPECT_EQ(incognito_browser,
            GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser());
  // 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->GetWindow()
                               ->GetNativeWindow()
                               .GetNativeNSWindow()];

  // Open a url, waiting for a new browser window to open and navigation to
  // complete.
  GURL simple(embedded_test_server()->GetURL("/simple.html"));
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  content::TestNavigationObserver event_navigation_observer(simple);
  event_navigation_observer.StartWatchingNewWebContents();
  SendOpenUrlToAppController(simple);
  BrowserWindowInterface* new_browser = browser_created_observer.Wait();
  event_navigation_observer.Wait();

  // Check that a new regular browser is opened and the url is opened in it.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_TRUE(new_browser->GetProfile()->IsRegularProfile());
  EXPECT_EQ(profile, new_browser->GetProfile());
  EXPECT_EQ(simple, new_browser->GetTabStripModel()
                        ->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());
  // Ensure the AppController is the NSApp delegate.
  std::ignore = AppController.sharedController;

  // 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->GetProfile()->IsGuestSession());
  guest_browser->GetWindow()->Show();
  EXPECT_EQ(guest_browser,
            GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser());
  // 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->GetWindow()
                               ->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/40912038#comment9
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest, OpenUrlWhenForcedIncognito) {
  ASSERT_TRUE(embedded_test_server()->Start());
  Profile* profile = browser()->GetProfile();
  base::FilePath original_profile_base_name = profile->GetBaseName();
  // Force incognito mode.
  IncognitoModePrefs::SetAvailability(
      profile->GetPrefs(), policy::IncognitoModeAvailability::kForced);
  // Close the current non-incognito browser.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  ui_test_utils::BrowserDestroyedObserver observer(browser());
  chrome::CloseAllBrowsers();
  observer.Wait();
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  // 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.
  BrowserWindowInterface* new_browser =
      GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser();
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_TRUE(new_browser->GetProfile()->IsIncognitoProfile());
  EXPECT_TRUE(new_browser->GetProfile()->IsPrimaryOTRProfile());
  EXPECT_EQ(original_profile_base_name,
            new_browser->GetProfile()->GetBaseName());
  EXPECT_EQ(simple, new_browser->GetTabStripModel()
                        ->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/40912038#comment9
IN_PROC_BROWSER_TEST_F(AppControllerBrowserTest,
                       OpenUrlWhenForcedIncognitoAndIncognitoBrowserIsOpened) {
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  // Force incognito mode.
  Profile* profile = browser()->GetProfile();
  IncognitoModePrefs::SetAvailability(
      profile->GetPrefs(), policy::IncognitoModeAvailability::kForced);
  // Create an incognito browser.
  Browser* incognito_browser = CreateIncognitoBrowser(profile);
  EXPECT_TRUE(incognito_browser->GetProfile()->IsIncognitoProfile());
  // Close the current non-incognito browser.
  CloseBrowserSynchronously(browser());
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(1, incognito_browser->tab_strip_model()->count());
  EXPECT_EQ(incognito_browser,
            GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser());
  // 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->GetWindow()
                               ->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(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(2, incognito_browser->tab_strip_model()->count());
  EXPECT_EQ(simple, incognito_browser->tab_strip_model()
                        ->GetActiveWebContents()
                        ->GetLastCommittedURL());
}

using AppControllerShortcutsNotAppsBrowserTest = InProcessBrowserTest;

IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
                       OpenChromeWeblocFile) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Ensure the AppController is the NSApp delegate.
  std::ignore = AppController.sharedController;

  // 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()->GetProfile()->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,
                       DisallowChromeUrlWeblocFile) {
  // Ensure the AppController is the NSApp delegate.
  std::ignore = AppController.sharedController;

  // Create and open a .crwebloc file with a chrome:// URL.
  GURL chrome_url("chrome://settings/");
  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(
                    chrome_url, *base::SafeBaseName::Create(
                                    browser()->GetProfile()->GetPath()))
                    .SaveToFile(crwebloc_file));
  }

  int initial_tab_count = browser()->tab_strip_model()->count();
  content::WebContents* current_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  SendOpenUrlToAppController(net::FilePathToFileURL(crwebloc_file));
  // Wait for any background tasks to complete and send its replies to the UI
  // thread. This helps ensure that SendOpenUrlToAppController() completes.
  base::ThreadPoolInstance::Get()->FlushForTesting();
  base::RunLoop().RunUntilIdle();

  // It should not be opened in the browser.
  EXPECT_EQ(initial_tab_count, browser()->tab_strip_model()->count());
  EXPECT_NE(chrome_url, current_contents->GetLastCommittedURL());

  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.Delete());
  }
}

IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
                       OpenChromeWeblocFileInSecondProfile) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Ensure the AppController is the NSApp delegate.
  std::ignore = AppController.sharedController;

  // 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());
  BrowserWindowInterface* new_browser =
      GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser();
  EXPECT_EQ(profile2_ptr, new_browser->GetProfile());
  EXPECT_EQ(1, new_browser->GetTabStripModel()->count());
  EXPECT_EQ(simple, new_browser->GetTabStripModel()
                        ->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));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_TRUE(ProfilePicker::IsOpen());
  ProfilePicker::Hide();
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.Delete());
  }
}

class AppControllerMainMenuBrowserTest : public InProcessBrowserTest {
 protected:
  AppControllerMainMenuBrowserTest() = default;
};

// Verifies that the Confirm Quit panel is shown when a browser window is
// active, the kConfirmToQuitEnabled preference is enabled, and the quit is
// keyboard-initiated.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       ConfirmQuitPanelShownWithBrowserWindowAndKeyQuit) {
  g_browser_process->local_state()->SetBoolean(prefs::kConfirmToQuitEnabled,
                                               true);

  ASSERT_FALSE(GlobalBrowserCollection::GetInstance()->IsEmpty());

  NSEvent* cmd_q_event = cocoa_test_event_utils::KeyEventWithKeyCode(
      'q', 'q', NSEventTypeKeyDown, NSEventModifierFlagCommand);
  ScopedMockCurrentEvent mock_event(cmd_q_event);
  ScopedMockConfirmQuitPanel mock_quit_panel;

  EXPECT_EQ([AppController.sharedController confirmQuitIfNeeded],
            ConfirmQuitResultConfirmed);
}

// Verifies that the Confirm Quit panel is not shown when the
// kConfirmToQuitEnabled preference is disabled and the quit is
// keyboard-initiated.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       ConfirmQuitPanelNotShownWhenPrefDisabled) {
  g_browser_process->local_state()->SetBoolean(prefs::kConfirmToQuitEnabled,
                                               false);

  ASSERT_FALSE(GlobalBrowserCollection::GetInstance()->IsEmpty());

  NSEvent* cmd_q_event = cocoa_test_event_utils::KeyEventWithKeyCode(
      'q', 'q', NSEventTypeKeyDown, NSEventModifierFlagCommand);
  ScopedMockCurrentEvent mock_event(cmd_q_event);
  ScopedMockConfirmQuitPanel mock_quit_panel;

  EXPECT_EQ([AppController.sharedController confirmQuitIfNeeded],
            ConfirmQuitResultNotPrompted);
}

// Verifies that the Confirm Quit panel is not shown when the quit is not
// keyboard-initiated.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       ConfirmQuitPanelNotShownWithMouseQuit) {
  g_browser_process->local_state()->SetBoolean(prefs::kConfirmToQuitEnabled,
                                               true);

  ASSERT_FALSE(GlobalBrowserCollection::GetInstance()->IsEmpty());

  ScopedMockCurrentEvent mock_event(nil);
  ScopedMockConfirmQuitPanel mock_quit_panel;

  EXPECT_EQ([AppController.sharedController confirmQuitIfNeeded],
            ConfirmQuitResultNotPrompted);
}

// Verifies that the Confirm Quit panel is not shown when a keyboard shortcut
// other than the quit shortcut is pressed.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       ConfirmQuitPanelNotShownWithNonQuitKeyEvent) {
  g_browser_process->local_state()->SetBoolean(prefs::kConfirmToQuitEnabled,
                                               true);

  ASSERT_FALSE(GlobalBrowserCollection::GetInstance()->IsEmpty());

  // Cmd+S is not the Quit shortcut.
  NSEvent* cmd_s_event = cocoa_test_event_utils::KeyEventWithKeyCode(
      's', 's', NSEventTypeKeyDown, NSEventModifierFlagCommand);
  ScopedMockCurrentEvent mock_event(cmd_s_event);
  ScopedMockConfirmQuitPanel mock_quit_panel;

  EXPECT_EQ([AppController.sharedController confirmQuitIfNeeded],
            ConfirmQuitResultNotPrompted);
}

// Verifies that the Confirm Quit panel is not shown if there are no open
// browser/app windows.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       ConfirmQuitPanelNotShownWithNoWindows) {
  content::ScopedAllowRendererCrashes allow_renderer_crashes(
      browser()
          ->tab_strip_model()
          ->GetActiveWebContents()
          ->GetPrimaryMainFrame()
          ->GetProcess());
  g_browser_process->local_state()->SetBoolean(prefs::kConfirmToQuitEnabled,
                                               true);

  ui_test_utils::BrowserDestroyedObserver observer(browser());
  chrome::CloseAllBrowsers();
  observer.Wait();
  ASSERT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());

  NSEvent* cmd_q_event = cocoa_test_event_utils::KeyEventWithKeyCode(
      'q', 'q', NSEventTypeKeyDown, NSEventModifierFlagCommand);
  ScopedMockCurrentEvent mock_event(cmd_q_event);
  ScopedMockConfirmQuitPanel mock_quit_panel;

  EXPECT_EQ([AppController.sharedController confirmQuitIfNeeded],
            ConfirmQuitResultNotPrompted);
}

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()->GetProfile();

  // 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/40809975.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       DISABLED_ReloadingDestroyedProfileDoesNotCrash) {
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  AppController* app_controller = AppController.sharedController;

  Profile* profile = browser()->GetProfile();
  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()->GetProfile();
  WaitForBookmarkMergedSurfaceServiceToLoad(
      BookmarkMergedSurfaceServiceFactory::GetForProfile(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);
  WaitForBookmarkMergedSurfaceServiceToLoad(
      BookmarkMergedSurfaceServiceFactory::GetForProfile(profile2_ptr));

  // Switch to profile 1, create bookmark 1 and force the menu to build.
  [app_controller setLastProfile:profile1];
  [app_controller bookmarkMenuBridge]->GetBookmarkModelForTesting()
      -> AddURL([app_controller bookmarkMenuBridge]
                    ->GetBookmarkModelForTesting() -> 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]->GetBookmarkModelForTesting()
      -> AddURL([app_controller bookmarkMenuBridge]
                    ->GetBookmarkModelForTesting() -> 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/40181046
// TODO(crbug.com/514579535): Re-enable. This test is flaky.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       DISABLED_ForcedIncognito_NewWindow) {
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  // Close the current non-incognito browser.
  Profile* profile = browser()->GetProfile();
  ui_test_utils::BrowserDestroyedObserver observer(browser());
  chrome::CloseAllBrowsers();
  observer.Wait();
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  // Force incognito mode.
  IncognitoModePrefs::SetAvailability(
      profile->GetPrefs(), policy::IncognitoModeAvailability::kForced);
  // Simulate click on "New window".
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  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_created_observer.Wait();
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_TRUE(new_browser->GetProfile()->IsPrimaryOTRProfile());
  EXPECT_EQ(profile, new_browser->GetProfile()->GetOriginalProfile());
}

// Regression test for https://crbug.com/487338374.
// Destroys the browser that set _lastActiveBrowser, then forces a
// HistoryMenuBridge rebuild that calls AddGroupEntryToMenu →
// lastActiveColorProvider → GetColor(). Before the fix, this
// dereferenced a dangling cached ColorProvider pointer.
IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
                       LastActiveColorProviderNulledOnBrowserClose) {
  AppController* app_controller = AppController.sharedController;
  Profile* profile = browser()->GetProfile();

  // Close a tab group to create a GROUP entry in TRS.
  ASSERT_TRUE(AddTabAtIndex(1, GURL("about:blank"), ui::PAGE_TRANSITION_LINK));
  TabStripModel* tab_strip = browser()->tab_strip_model();
  auto group_id = tab_strip->AddToNewGroup({0});
  tab_strip->CloseAllTabsInGroup(group_id);

  // Destroy the browser that set _lastActiveBrowser.
  Browser* browser2 = CreateBrowser(profile);
  CloseBrowserSynchronously(browser());

  // Force a HistoryMenuBridge rebuild. setLastProfile: early-returns when
  // the profile hasn't changed, so nil it first.
  [app_controller setLastProfile:nil];
  [app_controller setLastProfile:profile];

  const ui::ColorProvider& provider = [app_controller lastActiveColorProvider];
  EXPECT_NE(provider.GetColor(ui::kColorSysPrimary), SK_ColorTRANSPARENT);

  CloseBrowserSynchronously(browser2);
}

}  // 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(&params);
  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");
  auto browser_created_observer =
      std::make_optional<ui_test_utils::BrowserCreatedObserver>();
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), GURL(test_url3), WindowOpenDisposition::NEW_WINDOW,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  Browser* const browser2 = browser_created_observer->Wait();
  EXPECT_EQ(g_handoff_url, test_url3);
  EXPECT_EQ(g_handoff_title, u"Title Of More Awesomeness");

  // Check that there are exactly 2 browsers.
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());

  // Close the second browser window (which only has 1 tab left).
  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");
  browser_created_observer.emplace();
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), GURL(test_url4), WindowOpenDisposition::OFF_THE_RECORD,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
  Browser* const browser3 = browser_created_observer->Wait();
  EXPECT_EQ(g_handoff_url, GURL());
  EXPECT_EQ(g_handoff_title, u"");

  // Open a new tab in the incognito window.
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  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()->GetWindow()->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);
}

// Tests for the Smart Restart feature's interaction with the Dock.
class AppControllerSmartRestartBrowserTest : public InProcessBrowserTest {
 public:
  AppControllerSmartRestartBrowserTest() {
    feature_list_.InitAndEnableFeature(features::kSmartRestart);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    InProcessBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kNoStartupWindow);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

// Tests that clicking the Dock icon successfully opens a browser window when
// Smart Restart is enabled, even if the process started with
// --no-startup-window.
IN_PROC_BROWSER_TEST_F(AppControllerSmartRestartBrowserTest,
                       SmartRestartDockClickUnblocksWindows) {
  base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  EXPECT_TRUE(cmd_line->HasSwitch(switches::kNoStartupWindow));

  // The browser should have started with 0 windows due to the flag.
  EXPECT_EQ(0u, GlobalBrowserCollection::GetInstance()->GetSize());

  // Observe for the new browser that will be created.
  ui_test_utils::BrowserCreatedObserver observer;

  // Simulate Dock click (applicationShouldHandleReopen:).
  AppController* app_controller = AppController.sharedController;
  [app_controller applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];

  // The original process command line should remain unchanged (poisoned).
  EXPECT_TRUE(cmd_line->HasSwitch(switches::kNoStartupWindow));

  // A window should eventually open because LaunchBrowserStartup used a fresh
  // command line (without the flag) for this specific user action.
  observer.Wait();
  EXPECT_EQ(1u, GlobalBrowserCollection::GetInstance()->GetSize());
}

class AppControllerSmartRestartDisabledBrowserTest
    : public InProcessBrowserTest {
 public:
  AppControllerSmartRestartDisabledBrowserTest() {
    feature_list_.InitAndDisableFeature(features::kSmartRestart);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    InProcessBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kNoStartupWindow);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

// Tests that the user is NOT marked as interacted if Smart Restart is
// disabled.
IN_PROC_BROWSER_TEST_F(AppControllerSmartRestartDisabledBrowserTest,
                       SmartRestartDisabledDockClickDoesNotSetState) {
  base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  EXPECT_TRUE(cmd_line->HasSwitch(switches::kNoStartupWindow));

  EXPECT_EQ(0u, GlobalBrowserCollection::GetInstance()->GetSize());

  AppController* app_controller = AppController.sharedController;
  [app_controller applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];

  // The flag should STAY because the feature is disabled.
  EXPECT_TRUE(cmd_line->HasSwitch(switches::kNoStartupWindow));

  // No window should open because the flag still suppresses it.
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return GlobalBrowserCollection::GetInstance()->GetSize() == 0u;
  }));
}

}  // namespace
