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

#ifndef CHROME_TEST_BASE_BROWSER_WITH_TEST_WINDOW_TEST_H_
#define CHROME_TEST_BASE_BROWSER_WITH_TEST_WINDOW_TEST_H_

#include <memory>
#include <optional>
#include <vector>

#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/performance_manager/test_support/test_user_performance_tuning_manager_environment.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_profile.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "components/variations/variations_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_renderer_host.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(TOOLKIT_VIEWS)
#include "chrome/test/views/chrome_test_views_delegate.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/test/ash_test_helper.h"
#include "ash/test/ash_test_views_delegate.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
#include "chrome/browser/ash/app_mode/kiosk_cryptohome_remover.h"
#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "components/session_manager/core/fake_session_manager_delegate.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#else
#include "ui/views/test/scoped_views_test_helper.h"
#endif
#endif

#if BUILDFLAG(IS_WIN)
#include "ui/base/win/scoped_ole_initializer.h"
#endif

class GURL;
class GaiaId;

namespace content {
class NavigationController;
}

#if BUILDFLAG(IS_CHROMEOS)
namespace session_manager {
class SessionManager;
}  // namespace session_manager
#endif  // BUILDFLAG(IS_CHROMEOS)

class TestingProfileManager;

// WARNING WARNING WARNING WARNING
// Do not use this class. See docs/chrome_browser_design_principles.md for
// details. Either write a browser test which provides both a "class Browser"
// and a "class BrowserView" or a unit test which requires neither.
// Historically, features were written that take a "class Browser" as an input
// parameter. "class Browser" cannot be stubbed/faked/mocked, and this class was
// written as a workaround to create a "class Browser" without a "class
// BrowserView". This cannot happen in production code and thus results in test
// logic leaking into production code. New features should not take "class
// Browser" as input, and should instead perform dependency injection.
//
// Base class for browser based unit tests. BrowserWithTestWindowTest creates a
// Browser with a TestingProfile and TestBrowserWindow. To add a tab use
// AddTab. For example, the following adds a tab and navigates to two URLs:
//
//   // Add a new tab and navigate it. This will be at index 0.
//   // WARNING: this creates a real WebContents. If you want to add a test
//   // WebContents create it directly and insert it into the TabStripModel.
//   AddTab(browser(), GURL("http://foo/1"));
//   WebContents* contents = browser()->tab_strip_model()->GetWebContentsAt(0);
//
//   // Navigate somewhere else.
//   GURL url2("http://foo/2");
//   NavigateAndCommit(contents, url2);
//
//   // This is equivalent to the above, and lets you test pending navigations.
//   browser()->OpenURL(OpenURLParams(
//       GURL("http://foo/2"), GURL(), WindowOpenDisposition::CURRENT_TAB,
//       ui::PAGE_TRANSITION_TYPED, false), /*navigation_handle_callback=*/{});
//   CommitPendingLoad(&contents->GetController());
//
// Subclasses must invoke BrowserWithTestWindowTest::SetUp as it is responsible
// for creating the various objects of this class.
class BrowserWithTestWindowTest : public testing::Test, public ProfileObserver {
 public:
  // Trait which requests construction of a hosted app.
  struct HostedApp {};

  // List of traits that are valid inputs for the constructor below.
  using ValidTraits =
      base::ConcatParameterPacks<content::BrowserTaskEnvironment::ValidTraits,
                                 base::ParameterPack<HostedApp, Browser::Type>>;

  // Creates a BrowserWithTestWindowTest with zero or more traits. By default
  // the initial window will be a tabbed browser created on the native desktop,
  // which is not a hosted app.
  template <typename... TaskEnvironmentTraits>
    requires base::trait_helpers::AreValidTraits<ValidTraits,
                                                 TaskEnvironmentTraits...>
  NOINLINE explicit BrowserWithTestWindowTest(TaskEnvironmentTraits... traits)
      : BrowserWithTestWindowTest(
            std::make_unique<content::BrowserTaskEnvironment>(
                base::trait_helpers::Exclude<HostedApp, Browser::Type>::Filter(
                    traits)...),
            base::trait_helpers::GetEnum<Browser::Type, Browser::TYPE_NORMAL>(
                traits...),
            base::trait_helpers::HasTrait<HostedApp,
                                          TaskEnvironmentTraits...>()) {}

  BrowserWithTestWindowTest(const BrowserWithTestWindowTest&) = delete;
  BrowserWithTestWindowTest& operator=(const BrowserWithTestWindowTest&) =
      delete;

  ~BrowserWithTestWindowTest() override;

  void SetUp() override;
  void TearDown() override;

  // Set up process for `profile_manager_`. If a `profile_manager` is supplied,
  // it will be set as the underlying profile manager that `profile_manager_`
  // uses, aka `profile_manager_->profile_manager()`. This can only be called
  // before ::SetUp().
  void SetUpProfileManager(
      const base::FilePath& profiles_path = base::FilePath(),
      std::unique_ptr<ProfileManager> profile_manager = nullptr);

 protected:
  BrowserWindow* window() const { return window_.get(); }

  Browser* browser() const { return browser_.get(); }

  std::unique_ptr<Browser> release_browser();

  TestingProfile* profile() const { return profile_.get(); }

  TestingProfile* GetProfile() { return profile_.get(); }

  TestingProfileManager* profile_manager() { return profile_manager_.get(); }

  content::BrowserTaskEnvironment* task_environment() {
    return task_environment_.get();
  }

  network::TestURLLoaderFactory* test_url_loader_factory() {
    return &test_url_loader_factory_;
  }

#if BUILDFLAG(IS_CHROMEOS)
  ash::AshTestHelper* ash_test_helper() { return &ash_test_helper_.value(); }
  user_manager::FakeUserManager* user_manager() { return user_manager_.Get(); }
  // NOTE: `KioskCryptohomeRemover` is owned by `KioskControllerImpl` in
  // production.
  ash::KioskCryptohomeRemover* kiosk_cryptohome_remover() {
    return kiosk_cryptohome_remover_.get();
  }
  virtual void OnAshTestHelperCreated();
#endif

  // The context to help determine desktop type when creating new Widgets.
  gfx::NativeWindow GetContext();

  // Adds a tab to |browser| with the given URL and commits the load.
  // This is a convenience function. The new tab will be added at index 0.
  // WARNING: this creates a real WebContents. If you want to add a test
  // WebContents create it directly and insert it into the TabStripModel.
  void AddTab(Browser* browser, const GURL& url);

  // Commits the pending load on the given controller. It will keep the
  // URL of the pending load. If there is no pending load, this does nothing.
  void CommitPendingLoad(content::NavigationController* controller);

  // Creates a pending navigation on the given WebContents to the given URL with
  // the default parameters and the commits the load with a page ID one larger
  // than any seen. This emulates what happens on a new navigation.
  void NavigateAndCommit(content::WebContents* web_contents, const GURL& url);

  // Navigates the current tab. This is a wrapper around NavigateAndCommit.
  void NavigateAndCommitActiveTab(const GURL& url);

  // Set the |title| of the current tab.
  void NavigateAndCommitActiveTabWithTitle(Browser* browser,
                                           const GURL& url,
                                           const std::u16string& title);

  // Sets the focused frame to the main frame of the active WebContents, for
  // tests that rely on the focused frame not being null.
  void FocusMainFrameOfActiveWebContents();

  // Returns the profile name used for the profile created in SetUp() by
  // default.  Subclasses can override to change the profile name. If it returns
  // std::nullopt, this will skip creating profile and browser window.
  virtual std::optional<std::string> GetDefaultProfileName();

  // Creates the profile used by this test. The caller doesn't own the return
  // value.
  virtual TestingProfile* CreateProfile(const std::string& profile_name);

  // Deletes the specified profile.
  // If `profile_name` is the one returned from GetDefaultProfileName(),
  // because this instance creates Browser for the profile in SetUp() and keeps
  // it in a member, the Browser instance will also be destroyed to avoid leak.
  virtual void DeleteProfile(const std::string& profile_name);

  // Returns a vector of testing factories to be used when creating the profile.
  // This is only used by CreateProfile(), and will be irrelevant if that
  // method is overridden.
  virtual TestingProfile::TestingFactories GetTestingFactories();

  // Creates the BrowserWindow used by this test. Subclasses can provide their
  // own test BrowserWindow. If the provided BrowserWindow is null then Browser
  // will create a production BrowserWindow and the subclass is responsible for
  // cleaning it up (usually by NativeWidget destruction).
  virtual std::unique_ptr<BrowserWindow> CreateBrowserWindow();

  // Creates the browser given |profile|, |browser_type|, |hosted_app|, and
  // |browser_window|.
  virtual std::unique_ptr<Browser> CreateBrowser(Profile* profile,
                                                 Browser::Type browser_type,
                                                 bool hosted_app,
                                                 BrowserWindow* browser_window);

  // Creates the browser given `profile`, `browser_type` and `hosted_app` and
  // a window created via `CreateBrowserWindow()`.
  virtual std::unique_ptr<Browser> CreateBrowser(Profile* profile,
                                                 Browser::Type browser_type,
                                                 bool hosted_app);

#if defined(TOOLKIT_VIEWS)
  views::TestViewsDelegate* test_views_delegate() {
#if BUILDFLAG(IS_CHROMEOS)
    return test_views_delegate_.get();
#else
    return views_test_helper_->test_views_delegate();
#endif
  }
#endif

#if BUILDFLAG(IS_CHROMEOS)
  // Logs in an User as `email`.
  virtual void LogIn(std::string_view email, const GaiaId& gaia_id);

  // Handles the post-process for the newly created Profile.
  // Expected to be called on customizing CreateProfile for ash.
  virtual void OnUserProfileCreated(const std::string& email, Profile* profile);

  // Switches the active user to the one specified by the email.
  virtual void SwitchActiveUser(const std::string& email);

  // ProfileObserver:
  void OnProfileWillBeDestroyed(Profile* profile) override;

  ash::ScopedCrosSettingsTestHelper* GetCrosSettingsHelper();
  ash::StubInstallAttributes* GetInstallAttributes();
#endif

 private:
  // The template constructor has to be in the header but it delegates to this
  // constructor to initialize all other members out-of-line.
  BrowserWithTestWindowTest(
      std::unique_ptr<content::BrowserTaskEnvironment> task_environment,
      Browser::Type browser_type,
      bool hosted_app);

  // We need to create a MessageLoop, otherwise a bunch of things fails.
  std::unique_ptr<content::BrowserTaskEnvironment> task_environment_;

#if BUILDFLAG(IS_CHROMEOS)
  ash::TestSessionControllerClient* GetSessionControllerClient();

  // A template method (in Design Pattern) that execute post profile creation
  // steps.
  void PostUserProfileCreation(const std::string& email, Profile* profile);

  ash::ScopedCrosSettingsTestHelper cros_settings_test_helper_;

  std::unique_ptr<session_manager::SessionManager> session_manager_ =
      std::make_unique<session_manager::SessionManager>(
          std::make_unique<session_manager::FakeSessionManagerDelegate>());

  user_manager::TypedScopedUserManager<user_manager::FakeUserManager>
      user_manager_;
  std::vector<
      std::unique_ptr<base::ScopedObservation<Profile, ProfileObserver>>>
      profile_observations_;
  std::unique_ptr<ash::KioskCryptohomeRemover> kiosk_cryptohome_remover_;
  std::unique_ptr<ash::KioskChromeAppManager> kiosk_chrome_app_manager_;
#endif

  base::WeakPtr<TestingProfile> profile_ = nullptr;

  // test_url_loader_factory_ is declared before profile_manager_
  // to guarantee it outlives any profiles that might use it.
  network::TestURLLoaderFactory test_url_loader_factory_;

  std::unique_ptr<TestingProfileManager> profile_manager_;
  // Usually a TestBrowserWindow, owned by Browser.
  raw_ptr<BrowserWindow> window_;
  std::unique_ptr<Browser> browser_;

#if BUILDFLAG(IS_CHROMEOS)
  std::optional<ash::AshTestHelper> ash_test_helper_;
  std::unique_ptr<views::TestViewsDelegate> test_views_delegate_ =
      std::make_unique<ChromeTestViewsDelegate<ash::AshTestViewsDelegate>>();
#elif defined(TOOLKIT_VIEWS)
  std::unique_ptr<views::ScopedViewsTestHelper> views_test_helper_ =
      std::make_unique<views::ScopedViewsTestHelper>(
          std::make_unique<ChromeTestViewsDelegate<>>());
#endif

  // The existence of this object enables tests via RenderViewHostTester.
  std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;

#if BUILDFLAG(IS_WIN)
  ui::ScopedOleInitializer ole_initializer_;
#endif

  // The type of browser to create (tabbed or popup).
  const Browser::Type browser_type_;

  // Whether the browser is part of a hosted app.
  const bool hosted_app_;

  // Initialize the variations provider.
  variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
      variations::VariationsIdsProvider::Mode::kUseSignedInState};

  // Some of the UI elements in top chrome need to observe the
  // UserPerformanceTuningManager, so create and install a fake.
  performance_manager::user_tuning::TestUserPerformanceTuningManagerEnvironment
      user_performance_tuning_manager_environment_;
};

#endif  // CHROME_TEST_BASE_BROWSER_WITH_TEST_WINDOW_TEST_H_
