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

#include "chrome/test/base/ui_test_utils.h"

#include <stddef.h>

#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/task_manager/providers/web_contents/web_contents_tag.h"
#include "chrome/browser/task_manager/providers/web_contents/web_contents_tags_manager.h"
#include "chrome/browser/task_manager/web_contents_tags.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_active_state_manager/browser_active_state_manager.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/omnibox/omnibox_controller.h"
#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/autocomplete_change_observer.h"
#include "chrome/test/base/find_result_waiter.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/download/public/common/download_item.h"
#include "components/find_in_page/find_notification_details.h"
#include "components/find_in_page/find_tab_helper.h"
#include "components/history/core/browser/history_service_observer.h"
#include "components/javascript_dialogs/app_modal_dialog_controller.h"
#include "components/javascript_dialogs/app_modal_dialog_queue.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/prefs/pref_service.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_util.h"
#include "services/device/public/mojom/geoposition.mojom.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/test/widget_activation_waiter.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/multi_user/multi_user_window_manager.h"
#include "ash/shell.h"
#include "base/check_is_test.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "components/account_id/account_id.h"
#include "ui/base/base_window.h"
#endif

#if defined(TOOLKIT_VIEWS)
#include "ui/views/test/widget_test_api.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#endif

using content::NavigationController;
using content::NavigationEntry;
using content::OpenURLParams;
using content::Referrer;
using content::WebContents;

namespace ui_test_utils {

namespace {

BrowserWindowInterface* WaitForBrowserNotInSet(
    std::set<SessionID> excluded_browsers) {
  BrowserWindowInterface* new_browser = nullptr;
  GlobalBrowserCollection::GetInstance()->ForEach(
      [&](BrowserWindowInterface* browser) {
        if (!excluded_browsers.contains(browser->GetSessionID())) {
          new_browser = browser;
          return false;  // Stop iterating.
        }
        return true;  // Continue iterating.
      });
  if (!new_browser) {
    new_browser = WaitForBrowserToOpen();
    // The new browser should never be in |excluded_browsers|.
    DCHECK(!excluded_browsers.contains(new_browser->GetSessionID()));
  }
  return new_browser;
}

class AppModalDialogWaiter : public javascript_dialogs::AppModalDialogObserver {
 public:
  AppModalDialogWaiter() = default;
  AppModalDialogWaiter(const AppModalDialogWaiter&) = delete;
  AppModalDialogWaiter& operator=(const AppModalDialogWaiter&) = delete;
  ~AppModalDialogWaiter() override = default;

  javascript_dialogs::AppModalDialogController* Wait() {
    if (dialog_)
      return dialog_;
    message_loop_runner_ = new content::MessageLoopRunner;
    message_loop_runner_->Run();
    EXPECT_TRUE(dialog_);
    return dialog_;
  }

  // AppModalDialogObserver:
  void Notify(javascript_dialogs::AppModalDialogController* dialog) override {
    DCHECK(!dialog_);
    dialog_ = dialog;
    CheckForHangMonitorDisabling(dialog);
    if (message_loop_runner_.get() && message_loop_runner_->loop_running())
      message_loop_runner_->Quit();
  }

  static void CheckForHangMonitorDisabling(
      javascript_dialogs::AppModalDialogController* dialog) {
    // If a test waits for a beforeunload dialog but hasn't disabled the
    // beforeunload hang timer before triggering it, there will be a race
    // between the dialog and the timer and the test will be flaky. We can't
    // disable the timer here, as it's too late, but we can tell when we've won
    // a race that we shouldn't have been in.
    if (!dialog->is_before_unload_dialog())
      return;

    // Unfortunately we don't know which frame spawned this dialog and should
    // have the hang monitor disabled, so we cheat a bit and search for *a*
    // frame with the hang monitor disabled. The failure case that's worrisome
    // is someone who doesn't know the requirement to disable the hang monitor,
    // and this will catch that case.
    auto* contents = dialog->web_contents();
    bool found_disabled_for_testing = false;
    contents->GetPrimaryMainFrame()->ForEachRenderFrameHostWithAction(
        [&found_disabled_for_testing](content::RenderFrameHost* frame) {
          if (frame->IsBeforeUnloadHangMonitorDisabledForTesting()) {
            found_disabled_for_testing = true;
            return content::RenderFrameHost::FrameIterationAction::kStop;
          }
          return content::RenderFrameHost::FrameIterationAction::kContinue;
        });

    ASSERT_TRUE(found_disabled_for_testing)
        << "If waiting for a beforeunload dialog, the beforeunload timer "
           "must be disabled on the spawning frame to avoid flakiness.";
  }

 private:
  raw_ptr<javascript_dialogs::AppModalDialogController> dialog_ = nullptr;
  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
};

// Helper class to notify AllTabsObserver that a WebContents has been destroyed.
class WebContentsDestructionObserver : public content::WebContentsObserver {
 public:
  WebContentsDestructionObserver(base::OnceClosure destruction_cb,
                                 content::WebContents* web_contents)
      : WebContentsObserver(web_contents),
        destruction_cb_(std::move(destruction_cb)) {}
  ~WebContentsDestructionObserver() override = default;

  // WebContentsObserver
  void WebContentsDestroyed() override { std::move(destruction_cb_).Run(); }

 private:
  base::OnceClosure destruction_cb_;
};

// Returns true if `browser` is currently shown on the desktop of a user whose
// original profile matches `profile`'s original profile. On non-ChromeOS
// platforms this is always true.
bool IsBrowserShownForProfile(BrowserWindowInterface& browser,
                              const Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
  // Get the profile on which the window is currently shown.
  // ash::Shell might be NULL under test scenario.
  // TODO(crbug.com/427889779): Consider to drop this check.
  if (ash::Shell::HasInstance()) {
    ash::MultiUserWindowManager* const multi_user_window_manager =
        ash::Shell::Get()->multi_user_window_manager();
    const AccountId& shown_account_id =
        multi_user_window_manager->GetUserPresentingWindow(
            browser.GetWindow()->GetNativeWindow());
    Profile* shown_profile =
        shown_account_id.is_valid()
            ? multi_user_util::GetProfileFromAccountId(shown_account_id)
            : nullptr;
    if (shown_profile &&
        shown_profile->GetOriginalProfile() != profile->GetOriginalProfile()) {
      return false;
    }
  } else {
    CHECK_IS_TEST();
  }
#endif
  return true;
}

}  // namespace

bool GetCurrentTabTitle(const BrowserWindowInterface* browser,
                        std::u16string* title) {
  WebContents* const web_contents =
      browser->GetTabStripModel()->GetActiveWebContents();
  if (!web_contents)
    return false;
  NavigationEntry* last_entry = web_contents->GetController().GetActiveEntry();
  if (!last_entry)
    return false;
  title->assign(last_entry->GetTitleForDisplay());
  return true;
}

BrowserWindowInterface* FindAnyBrowser(const Profile* profile,
                                       bool match_original_profiles) {
  BrowserWindowInterface* found = nullptr;
  auto matcher = [&found, profile,
                  match_original_profiles](BrowserWindowInterface* browser) {
    if (match_original_profiles) {
      if (browser->GetProfile()->GetOriginalProfile() !=
          profile->GetOriginalProfile()) {
        return true;
      }
    } else {
      if (browser->GetProfile() != profile) {
        return true;
      }
    }
    if (browser->IsDeleteScheduled() ||
        !IsBrowserShownForProfile(*browser, profile)) {
      return true;
    }
    found = browser;
    return false;
  };
  GlobalBrowserCollection::GetInstance()->ForEach(
      matcher, BrowserCollection::Order::kActivation);
  return found;
}

void NavigateToURL(NavigateParams* params) {
  Navigate(params);
  content::WaitForLoadStop(params->navigated_or_inserted_contents);
}

void NavigateToURLWithPost(BrowserWindowInterface* browser, const GURL& url) {
  NavigateParams params(browser, url, ui::PAGE_TRANSITION_FORM_SUBMIT);

  std::string post_data("test=body");
  params.post_data = network::ResourceRequestBody::CreateFromCopyOfBytes(
      base::as_byte_span(post_data));

  NavigateToURL(&params);
}

content::RenderFrameHost* NavigateToURL(BrowserWindowInterface* browser,
                                        const GURL& url) {
  return NavigateToURLWithDisposition(browser, url,
                                      WindowOpenDisposition::CURRENT_TAB,
                                      BROWSER_TEST_WAIT_FOR_LOAD_STOP);
}

content::RenderFrameHost*
NavigateToURLWithDispositionBlockUntilNavigationsComplete(
    BrowserWindowInterface* browser,
    const GURL& url,
    int number_of_navigations,
    WindowOpenDisposition disposition,
    int browser_test_flags) {
  TRACE_EVENT("test",
              "ui_test_utils::"
              "NavigateToURLWithDispositionBlockUntilNavigationsComplete",
              "url", url, "number_of_navigations", number_of_navigations,
              "disposition", disposition, "browser_test_flags",
              browser_test_flags);
  TabStripModel* tab_strip = browser->GetTabStripModel();
  if (disposition == WindowOpenDisposition::CURRENT_TAB &&
      tab_strip->GetActiveWebContents())
    content::WaitForLoadStop(tab_strip->GetActiveWebContents());
  content::TestNavigationObserver same_tab_observer(
      tab_strip->GetActiveWebContents(), number_of_navigations,
      content::MessageLoopRunner::QuitMode::DEFERRED,
      /*ignore_uncommitted_navigations=*/false);
  if (!blink::IsRendererDebugURL(url))
    same_tab_observer.set_expected_initial_url(url);

  std::set<SessionID> initial_browsers;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [&](BrowserWindowInterface* initial_browser) {
        initial_browsers.insert(initial_browser->GetSessionID());
        return true;  // Continue iterating.
      });

  AllBrowserTabAddedWaiter tab_added_waiter;

  WebContents* const web_contents =
      browser->OpenURL(OpenURLParams(url, Referrer(), disposition,
                                     ui::PAGE_TRANSITION_TYPED, false),
                       /*navigation_handle_callback=*/{});
  if (browser_test_flags & BROWSER_TEST_WAIT_FOR_BROWSER) {
    // `WaitForBrowserNotInSet()` waits until the new browser is created, and
    // `WaitForBrowserSetLastActive()` waits until the new browser is active.
    // The latter is important because tests might rely on methods like
    // `GetLastActiveBrowserWindowInterfaceWithAnyProfile()` or
    // `ForEachCurrentBrowserWindowInterfaceOrderedByActivation()` to retrieve
    // the browser window that is most recently active.
    // It's possible for a browser to be created but not yet active. For
    // example, when the `kWebUIReloadButtonDeferBrowserViewShow` feature
    // parameter is true, the new window defers its initial show (and thus
    // activation) until some initial WebUI has finished loading.
    browser = WaitForBrowserNotInSet(initial_browsers);
    WaitForBrowserSetLastActive(browser);
    tab_strip = browser->GetTabStripModel();
  }
  if (browser_test_flags & BROWSER_TEST_WAIT_FOR_TAB) {
    tab_added_waiter.Wait();
  }
  if (!(browser_test_flags & BROWSER_TEST_WAIT_FOR_LOAD_STOP)) {
    // Some other flag caused the wait prior to this.
    return nullptr;
  }
  if (disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) {
    EXPECT_TRUE(web_contents)
        << " Unable to wait for navigation to \"" << url.spec()
        << "\" because the new tab is not available yet";
    if (!web_contents) {
      return nullptr;
    }
  } else if ((disposition == WindowOpenDisposition::CURRENT_TAB) ||
             (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB) ||
             (disposition == WindowOpenDisposition::SINGLETON_TAB)) {
    // The tab we navigated should be the active one.
    EXPECT_EQ(web_contents, tab_strip->GetActiveWebContents());
  }
  if (disposition == WindowOpenDisposition::CURRENT_TAB) {
    // Waiting for the navigation may destroy `web_contents` (e.g. navigating
    // to a resource that closes its own tab, such as a corrupted extension
    // page that gets disabled mid-navigation under enforced content
    // verification). Guard against use-after-free before touching it again.
    base::WeakPtr<content::WebContents> weak_web_contents =
        web_contents->GetWeakPtr();
    same_tab_observer.Wait();
    if (!weak_web_contents) {
      return nullptr;
    }
    content::SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents);
    return web_contents->GetPrimaryMainFrame();
  } else if (web_contents) {
    content::TestNavigationObserver observer(
        web_contents, number_of_navigations,
        content::MessageLoopRunner::QuitMode::DEFERRED,
        /*ignore_uncommitted_navigations=*/false);
    if (!blink::IsRendererDebugURL(url))
      observer.set_expected_initial_url(url);
    // See the comment above: the navigation may destroy `web_contents`.
    base::WeakPtr<content::WebContents> weak_web_contents =
        web_contents->GetWeakPtr();
    observer.Wait();
    if (!weak_web_contents) {
      return nullptr;
    }
    content::SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents);
    return web_contents->GetPrimaryMainFrame();
  }
  EXPECT_TRUE(web_contents)
      << " Unable to wait for navigation to \"" << url.spec() << "\""
      << " because we can't get the tab contents";
  return nullptr;
}

content::RenderFrameHost* NavigateToURLWithDisposition(
    BrowserWindowInterface* browser,
    const GURL& url,
    WindowOpenDisposition disposition,
    int browser_test_flags) {
  return NavigateToURLWithDispositionBlockUntilNavigationsComplete(
      browser, url, 1, disposition, browser_test_flags);
}

content::RenderFrameHost* NavigateToURLBlockUntilNavigationsComplete(
    BrowserWindowInterface* browser,
    const GURL& url,
    int number_of_navigations) {
  return NavigateToURLWithDispositionBlockUntilNavigationsComplete(
      browser, url, number_of_navigations, WindowOpenDisposition::CURRENT_TAB,
      BROWSER_TEST_WAIT_FOR_LOAD_STOP);
}

bool GetRelativeBuildDirectory(base::FilePath* build_dir) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  // This function is used to find the build directory so TestServer can serve
  // built files (nexes, etc).  TestServer expects a path relative to the source
  // root.
  base::FilePath exe_dir =
      base::CommandLine::ForCurrentProcess()->GetProgram().DirName();
  base::FilePath src_dir;
  if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &src_dir)) {
    return false;
  }

  // We must first generate absolute paths to SRC and EXE and from there
  // generate a relative path.
  if (!exe_dir.IsAbsolute())
    exe_dir = base::MakeAbsoluteFilePath(exe_dir);
  if (!src_dir.IsAbsolute())
    src_dir = base::MakeAbsoluteFilePath(src_dir);
  if (!exe_dir.IsAbsolute())
    return false;
  if (!src_dir.IsAbsolute())
    return false;

  size_t match, exe_size, src_size;

  // Determine point at which src and exe diverge.
  auto exe_parts = exe_dir.GetComponents();
  auto src_parts = src_dir.GetComponents();
  exe_size = exe_parts.size();
  src_size = src_parts.size();
  for (match = 0; match < exe_size && match < src_size; ++match) {
    if (exe_parts[match] != src_parts[match])
      break;
  }

  // Create a relative path.
  *build_dir = base::FilePath();
  for (size_t tmp_itr = match; tmp_itr < src_size; ++tmp_itr)
    *build_dir = build_dir->Append(FILE_PATH_LITERAL(".."));
  for (; match < exe_size; ++match)
    *build_dir = build_dir->Append(exe_parts[match]);
  return true;
}

javascript_dialogs::AppModalDialogController* WaitForAppModalDialog() {
  auto* dialog_queue = javascript_dialogs::AppModalDialogQueue::GetInstance();
  if (dialog_queue->HasActiveDialog()) {
    AppModalDialogWaiter::CheckForHangMonitorDisabling(
        dialog_queue->active_dialog());
    return dialog_queue->active_dialog();
  }
  AppModalDialogWaiter waiter;
  return waiter.Wait();
}

#if defined(TOOLKIT_VIEWS)
void WaitForViewVisibility(BrowserWindowInterface* browser,
                           ViewID vid,
                           bool visible) {
  views::View* view = views::Widget::GetWidgetForNativeWindow(
                          browser->GetWindow()->GetNativeWindow())
                          ->GetContentsView()
                          ->GetViewByID(vid);
  ASSERT_TRUE(view);
  if (view->GetVisible() == visible)
    return;

  base::RunLoop run_loop;
  auto subscription = view->AddVisibleChangedCallback(run_loop.QuitClosure());
  run_loop.Run();
  EXPECT_EQ(visible, view->GetVisible());
}
#endif

int FindInPage(WebContents* tab,
               const std::u16string& search_string,
               bool forward,
               bool match_case,
               int* ordinal,
               gfx::Rect* selection_rect) {
  find_in_page::FindTabHelper* find_tab_helper =
      find_in_page::FindTabHelper::FromWebContents(tab);
  find_tab_helper->StartFinding(search_string, forward, match_case,
                                true, /* find_match */
                                true /* run_synchronously_for_testing */);
  FindResultWaiter observer(tab);
  observer.Wait();
  if (ordinal)
    *ordinal = observer.active_match_ordinal();
  if (selection_rect)
    *selection_rect = observer.selection_rect();
  return observer.number_of_matches();
}

void DownloadURL(BrowserWindowInterface* browser, const GURL& download_url) {
  content::DownloadManager* const download_manager =
      browser->GetProfile()->GetDownloadManager();
  std::unique_ptr<content::DownloadTestObserver> observer(
      new content::DownloadTestObserverTerminal(
          download_manager, 1,
          content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT));

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser, download_url));
  observer->WaitForFinished();
}

void WaitForAutocompleteDone(BrowserWindowInterface* browser) {
  auto* controller = BrowserWindow::FromBrowser(browser)
                         ->GetLocationBar()
                         ->GetOmniboxController()
                         ->autocomplete_controller();
  while (!controller->done())
    AutocompleteChangeObserver(browser->GetProfile()).Wait();
}

bool WaitForMinimized(BrowserWindowInterface* browser) {
  views::test::PropertyWaiter minimize_waiter(
      base::BindRepeating(&BrowserWindow::IsMinimized,
                          base::Unretained(browser->GetWindow())),
      true);
  return minimize_waiter.Wait();
}

bool WaitForMaximized(BrowserWindowInterface* browser) {
  views::test::PropertyWaiter maximize_waiter(
      base::BindRepeating(&BrowserWindow::IsMaximized,
                          base::Unretained(browser->GetWindow())),
      true);
  return maximize_waiter.Wait();
}

views::AsyncWidgetRequestWaiter CreateAsyncWidgetRequestWaiter(
    BrowserWindowInterface& browser) {
  auto* widget = views::Widget::GetWidgetForNativeWindow(
      browser.GetWindow()->GetNativeWindow());
  CHECK(widget);
  return views::AsyncWidgetRequestWaiter(*widget);
}

void SetAndWaitForBounds(BrowserWindowInterface& browser,
                         const gfx::Rect& bounds) {
  auto waiter = CreateAsyncWidgetRequestWaiter(browser);
  auto* window = browser.GetWindow();
  window->SetBounds(bounds);
  waiter.Wait();
}

bool MaximizeAndWaitUntilUIUpdateDone(BrowserWindowInterface& browser) {
  auto waiter = ui_test_utils::CreateAsyncWidgetRequestWaiter(browser);
  browser.GetWindow()->Maximize();
  waiter.Wait();
  return browser.GetWindow()->IsMaximized();
}

FullscreenWaiter::FullscreenWaiter(BrowserWindowInterface* browser,
                                   FullscreenWaiter::Expectation expectation)
    : FullscreenWaiter(browser->GetFeatures()
                           .exclusive_access_manager()
                           ->fullscreen_controller(),
                       std::move(expectation)) {}

FullscreenWaiter::FullscreenWaiter(FullscreenController* controller,
                                   FullscreenWaiter::Expectation expectation)
    : expectation_(std::move(expectation)),
      controller_(controller),
      // Sometimes, the wait is called on a sequeunce, e.g.
      // as a part of interactive_ui_tests's RunTestSequence.
      // To handle that case, we can process pending task posted to the
      // sequence in nested RunLoop.
      run_loop_(base::RunLoop::Type::kNestableTasksAllowed),
      satisfied_(controller_ ? IsSatisfied() : true) {
  if (controller_) {
    subscription_ = controller_->RegisterOnFullscreenStateChanged(
        base::BindRepeating(&FullscreenWaiter::OnFullscreenStateChanged,
                            base::Unretained(this)));
  }
}

FullscreenWaiter::~FullscreenWaiter() = default;

void FullscreenWaiter::Wait() {
  if (satisfied_) {
    return;
  }
  run_loop_.Run();
}

void FullscreenWaiter::OnFullscreenStateChanged() {
  if (!IsSatisfied()) {
    return;
  }
  satisfied_ = true;
  if (run_loop_.running()) {
    run_loop_.Quit();
  }
}

bool FullscreenWaiter::IsSatisfied() const {
  if (expectation_.browser_fullscreen.has_value() &&
      expectation_.browser_fullscreen.value() !=
          controller_->IsFullscreenForBrowser()) {
    return false;
  }

  if (expectation_.tab_fullscreen.has_value() &&
      expectation_.tab_fullscreen.value() != controller_->IsTabFullscreen()) {
    return false;
  }

  if (expectation_.display_id.has_value()) {
    // Display ID is valid iff the tab fullscreen mode is active.
    if (!controller_->IsTabFullscreen()) {
      return false;
    }
    if (expectation_.display_id.value() !=
        FullscreenController::GetDisplayId(
            *controller_->exclusive_access_tab())) {
      return false;
    }
  }

  return true;
}

void ToggleFullscreenModeAndWait(BrowserWindowInterface* browser) {
  // The waiting condition is following the current implementation.
  // If the mode is either browser/tab fullscreen, it will be existed.
  // Otherwise, entering into browser fullscreen.
  bool current = browser->GetFeatures()
                     .exclusive_access_manager()
                     ->context()
                     ->IsFullscreen();
  FullscreenWaiter waiter(browser, current ? FullscreenWaiter::kNoFullscreen
                                           : FullscreenWaiter::Expectation{
                                                 .browser_fullscreen = true});
  chrome::ToggleFullscreenMode(browser);
  waiter.Wait();
}

void WaitUntilBrowserBecomeActive(BrowserWindowInterface* browser) {
  CHECK(browser);
  views::Widget* widget =
      BrowserView::GetBrowserViewForBrowser(browser)->GetWidget();
  views::test::WaitForWidgetActive(widget, /*active=*/true);
}

bool IsBrowserActive(BrowserWindowInterface* browser) {
  CHECK(browser);
  views::Widget* widget =
      BrowserView::GetBrowserViewForBrowser(browser)->GetWidget();
  return widget->native_widget_active();
}

Browser* OpenNewEmptyWindowAndWaitUntilActivated(
    Profile* profile,
    bool should_trigger_session_restore) {
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  chrome::NewEmptyWindow(profile, should_trigger_session_restore);
  BrowserWindowInterface* new_browser = browser_created_observer.Wait();
  WaitUntilBrowserBecomeActive(new_browser);
  return new_browser->GetBrowserForMigrationOnly();
}

BrowserDidBecomeActiveWaiter::BrowserDidBecomeActiveWaiter(
    BrowserWindowInterface* browser,
    bool wait_for_set_last_active_observed)
    : wait_for_set_last_active_observed_(wait_for_set_last_active_observed) {
  browser_did_become_active_subscription_ =
      browser->RegisterDidBecomeActive(base::BindRepeating(
          &BrowserDidBecomeActiveWaiter::OnBrowserDidBecomeActive,
          base::Unretained(this)));
  if (GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser() ==
          browser &&
      !wait_for_set_last_active_observed_) {
    satisfied_ = true;
  }
}

BrowserDidBecomeActiveWaiter::~BrowserDidBecomeActiveWaiter() = default;

// Runs a loop until |browser_| becomes the last active browser.
void BrowserDidBecomeActiveWaiter::Wait() {
  if (satisfied_) {
    return;
  }

  run_loop_.Run();
}

void BrowserDidBecomeActiveWaiter::OnBrowserDidBecomeActive(
    BrowserWindowInterface* Browser) {
  satisfied_ = true;
  if (run_loop_.running()) {
    run_loop_.Quit();
  }
}

void WaitForBrowserSetLastActive(BrowserWindowInterface* browser,
                                 bool wait_for_set_last_active_observed) {
  BrowserDidBecomeActiveWaiter waiter(browser,
                                      wait_for_set_last_active_observed);
  waiter.Wait();
}

void DeprecatedFakeActivateBrowser(BrowserWindowInterface* browser) {
  CHECK(browser);

  // We must deactivate the currently active browser first.
  BrowserActiveStateManager::From(
      GetLastActiveBrowserWindowInterfaceWithAnyProfile())
      ->DidBecomeInactive();

  // Fake activation of the target browser.
  BrowserActiveStateManager::From(browser)->DidBecomeActive();
}

void SendToOmniboxAndSubmit(BrowserWindowInterface* browser,
                            std::string_view input,
                            base::TimeTicks match_selection_timestamp,
                            bool wait_for_autocomplete_done) {
  LocationBar* location_bar =
      BrowserWindow::FromBrowser(browser)->GetLocationBar();
  OmniboxView* omnibox = location_bar->GetOmniboxView();
  location_bar->GetOmniboxController()->edit_model()->OnSetFocus(
      /*control_down=*/false);
  omnibox->SetUserText(base::ASCIIToUTF16(input));
  location_bar->GetOmniboxController()->edit_model()->OpenCurrentSelection(
      match_selection_timestamp);

  if (wait_for_autocomplete_done) {
    WaitForAutocompleteDone(browser);
  }
}

Browser* GetBrowserNotInSet(
    const std::set<BrowserWindowInterface*>& excluded_browsers) {
  BrowserWindowInterface* browser_not_in_set = nullptr;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [&](BrowserWindowInterface* browser) {
        if (excluded_browsers.find(browser) == excluded_browsers.end()) {
          browser_not_in_set = browser;
          return false;  // Stop iterating.
        }
        return true;  // Continue iterating.
      });
  return browser_not_in_set ? browser_not_in_set->GetBrowserForMigrationOnly()
                            : nullptr;
}

std::vector<BrowserWindowInterface*> FindMatchingBrowsers(
    BrowserMatcher matcher) {
  std::vector<BrowserWindowInterface*> browser_matches;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [&](BrowserWindowInterface* browser) {
        if (matcher(browser)) {
          browser_matches.push_back(browser);
        }
        return true;  // Continue iterating.
      });
  return browser_matches;
}

namespace {

void GetCookieCallback(base::RepeatingClosure callback,
                       net::CookieList* cookies,
                       const net::CookieAccessResultList& cookie_list,
                       const net::CookieAccessResultList& excluded_cookies) {
  *cookies = net::cookie_util::StripAccessResults(cookie_list);
  callback.Run();
}

}  // namespace

void GetCookies(const GURL& url,
                WebContents* contents,
                int* value_size,
                std::string* value) {
  *value_size = -1;
  if (url.is_valid() && contents) {
    base::RunLoop loop;
    auto* storage_partition =
        contents->GetPrimaryMainFrame()->GetProcess()->GetStoragePartition();
    net::CookieList cookie_list;
    storage_partition->GetCookieManagerForBrowserProcess()->GetCookieList(
        url, net::CookieOptions::MakeAllInclusive(),
        net::CookiePartitionKeyCollection(),
        base::BindOnce(GetCookieCallback, loop.QuitClosure(), &cookie_list));
    loop.Run();

    *value = net::CanonicalCookie::BuildCookieLine(cookie_list);
    *value_size = static_cast<int>(value->size());
  }
}

const std::vector<raw_ptr<task_manager::WebContentsTag, VectorExperimental>>
GetAllTrackedTags(bool exclude_web_ui) {
  auto all_tags =
      task_manager::WebContentsTagsManager::GetInstance()->tracked_tags();
  if (!exclude_web_ui) {
    return all_tags;
  }

  std::vector<raw_ptr<task_manager::WebContentsTag, VectorExperimental>>
      filtered_tags;
  std::ranges::copy_if(
      all_tags, std::back_inserter(filtered_tags),
      [](const auto& tag) { return !tag->web_contents()->GetWebUI(); });
  return filtered_tags;
}

const std::vector<std::string> GetAllTrackedTagWebContentTitles(
    bool exclude_web_ui) {
  std::vector<std::string> titles;
  std::ranges::transform(
      GetAllTrackedTags(exclude_web_ui), std::back_inserter(titles),
      [&](const auto& tag) {
        return base::UTF16ToUTF8(tag->web_contents()->GetTitle());
      });
  return titles;
}

// It would be nice to `AddAllBrowsers()` here, but we have to wait until our
// subclass is constructed to `ProcessOneBrowser()`.  We can't put it off
// until `Wait()` since we need to watch for anything that happens between now
// and then.
AllTabsObserver::AllTabsObserver() = default;

AllTabsObserver::~AllTabsObserver() = default;

void AllTabsObserver::AddAllBrowsers() {
  added_all_browsers_ = true;
  browser_collection_observation_.Observe(
      GlobalBrowserCollection::GetInstance());
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [&](BrowserWindowInterface* browser) {
        AddBrowser(browser);
        return true;  // Continue iterating.
      });
}

void AllTabsObserver::Wait() {
  // For subclasses that can detect if their condition is met without
  // necessarily needing to watch for transitions, we could wait until now to
  // call `AddAllBrowsers()` if it hasn't happened yet.
  CHECK(added_all_browsers_)
      << "Subclasses must call `AddAllBrowsers()` during construction";

  if (!condition_met_) {
    run_loop_ = std::make_unique<base::RunLoop>(
        base::RunLoop::Type::kNestableTasksAllowed);
    run_loop_->Run();
    run_loop_.reset();
  }
  EXPECT_TRUE(condition_met_);
}

// impls should call this to tell us to stop waiting.
void AllTabsObserver::ConditionMet() {
  condition_met_ = true;
  if (run_loop_) {
    run_loop_->Quit();
  }
}

void AllTabsObserver::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  if (change.type() != TabStripModelChange::kInserted) {
    return;
  }

  AddWebContents(change.GetInsert()->contents[0].contents.get());
}

void AllTabsObserver::OnBrowserCreated(BrowserWindowInterface* browser) {
  AddBrowser(browser);
}

void AllTabsObserver::AddBrowser(BrowserWindowInterface* browser) {
  browser->GetTabStripModel()->AddObserver(this);
  // Enumerate all WebContents in this browser, and add a WebContentsObserver
  // for it.
  for (int index = 0; index < browser->GetTabStripModel()->count(); index++) {
    auto* web_contents = browser->GetTabStripModel()->GetWebContentsAt(index);
    AddWebContents(web_contents);
  }
}

void AllTabsObserver::AddWebContents(content::WebContents* web_contents) {
  // If the condition is already met, then don't bother.
  if (condition_met_) {
    return;
  }

  auto observer = ProcessOneContents(web_contents);
  if (observer) {
    // Store both the subclass's observer and our own with this WebContents.
    // We'll handle cleaning them both up on destruction.  It's okay if our
    // subclass does not create an observer, but does notify us to stop waiting
    // before returning.
    tab_navigation_map_[web_contents].subclass_observer = std::move(observer);
    tab_navigation_map_[web_contents].destruction_observer =
        std::make_unique<WebContentsDestructionObserver>(
            base::BindOnce(&AllTabsObserver::OnWebContentsDestroyed,
                           base::Unretained(this),
                           base::Unretained(web_contents)),
            web_contents);
  }
}

// called by our destruction observers
void AllTabsObserver::OnWebContentsDestroyed(WebContents* web_contents) {
  auto iter = tab_navigation_map_.find(web_contents);
  CHECK(iter != tab_navigation_map_.end());
  // This clears both our observer and the one created by our subclass.
  tab_navigation_map_.erase(iter);
}

AllTabsObserver::TabNavigationMapEntry::TabNavigationMapEntry() = default;
AllTabsObserver::TabNavigationMapEntry::~TabNavigationMapEntry() = default;

UrlLoadObserver::UrlLoadObserver(const GURL& url) : url_(url) {
  AddAllBrowsers();
}

UrlLoadObserver::~UrlLoadObserver() = default;

std::unique_ptr<base::CheckedObserver> UrlLoadObserver::ProcessOneContents(
    WebContents* web_contents) {
  return std::make_unique<LoadStopObserver>(this, web_contents);
}

void UrlLoadObserver::OnDidStopLoading(WebContents* web_contents) {
  NavigationController& controller = web_contents->GetController();
  NavigationEntry* entry = controller.GetVisibleEntry();
  if (!entry || entry->GetVirtualURL() != url_) {
    return;
  }

  // Record the first match.
  if (!web_contents_) {
    web_contents_ = web_contents;
  }
  ConditionMet();
}

UrlLoadObserver::LoadStopObserver::LoadStopObserver(UrlLoadObserver* owner,
                                                    WebContents* web_contents)
    : WebContentsObserver(web_contents), owner_(owner) {}

UrlLoadObserver::LoadStopObserver::~LoadStopObserver() = default;

void UrlLoadObserver::LoadStopObserver::DidStopLoading() {
  owner_->OnDidStopLoading(web_contents());
}

HistoryEnumerator::HistoryEnumerator(Profile* profile) {
  base::RunLoop run_loop;
  base::CancelableTaskTracker tracker;

  HistoryServiceFactory::GetForProfile(profile,
                                       ServiceAccessType::EXPLICIT_ACCESS)
      ->QueryHistory(
          std::u16string(), history::QueryOptions(),
          base::BindLambdaForTesting([&](history::QueryResults results) {
            for (const auto& item : results)
              urls_.push_back(item.url());
            run_loop.Quit();
          }),
          &tracker);
  run_loop.Run();
}

HistoryEnumerator::~HistoryEnumerator() = default;

// Wait for HistoryService to load.
class WaitHistoryLoadedObserver : public history::HistoryServiceObserver {
 public:
  explicit WaitHistoryLoadedObserver(content::MessageLoopRunner* runner);
  ~WaitHistoryLoadedObserver() override;

  // history::HistoryServiceObserver:
  void OnHistoryServiceLoaded(history::HistoryService* service) override;

 private:
  // weak
  raw_ptr<content::MessageLoopRunner> runner_;
};

WaitHistoryLoadedObserver::WaitHistoryLoadedObserver(
    content::MessageLoopRunner* runner)
    : runner_(runner) {}

WaitHistoryLoadedObserver::~WaitHistoryLoadedObserver() = default;

void WaitHistoryLoadedObserver::OnHistoryServiceLoaded(
    history::HistoryService* service) {
  runner_->Quit();
}

void WaitForHistoryToLoad(history::HistoryService* history_service) {
  if (!history_service->BackendLoaded()) {
    scoped_refptr<content::MessageLoopRunner> runner =
        new content::MessageLoopRunner;
    WaitHistoryLoadedObserver observer(runner.get());
    base::ScopedObservation<history::HistoryService,
                            history::HistoryServiceObserver>
        scoped_observer(&observer);
    scoped_observer.Observe(history_service);
    runner->Run();
  }
}

Browser* WaitForBrowserToOpen() {
  return BrowserCreatedObserver().Wait();
}

void WaitForBrowserToClose(BrowserWindowInterface* browser) {
  BrowserDestroyedObserver(browser).Wait();
}

TabAddedWaiter::TabAddedWaiter(BrowserWindowInterface* browser) {
  browser->GetTabStripModel()->AddObserver(this);
}

content::WebContents* TabAddedWaiter::Wait() {
  TRACE_EVENT0("test", "TabAddedWaiter::Wait");
  run_loop_.Run();
  return web_contents_;
}

void TabAddedWaiter::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  if (web_contents_) {
    return;
  }
  if (change.type() == TabStripModelChange::kInserted) {
    web_contents_ = change.GetInsert()->contents[0].contents;
    run_loop_.Quit();
  }
}

AllBrowserTabAddedWaiter::AllBrowserTabAddedWaiter(
    std::optional<size_t> expected_count)
    : expected_count_(expected_count) {
  // If `expected_count` is 0, `Wait()` will hang indefinitely.
  CHECK_GE(expected_count_.value_or(1), 1u);
  browser_collection_observation_.Observe(
      GlobalBrowserCollection::GetInstance());
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [&](BrowserWindowInterface* browser) {
        browser->GetTabStripModel()->AddObserver(this);
        return true;  // Continue iterating.
      });
}

AllBrowserTabAddedWaiter::~AllBrowserTabAddedWaiter() = default;

content::WebContents* AllBrowserTabAddedWaiter::Wait() {
  run_loop_.Run();
  CHECK_GE(web_contents_.size(), 1u);
  return web_contents_[0];
}

void AllBrowserTabAddedWaiter::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  if (change.type() != TabStripModelChange::kInserted) {
    return;
  }
  web_contents_.push_back(change.GetInsert()->contents[0].contents.get());
  if (expected_count_) {
    EXPECT_LE(web_contents_.size(), expected_count_)
        << "Unexpected tab created";
  }
  if (web_contents_.size() == expected_count_.value_or(1)) {
    run_loop_.Quit();
  }
}

void AllBrowserTabAddedWaiter::OnBrowserCreated(
    BrowserWindowInterface* browser) {
  browser->GetTabStripModel()->AddObserver(this);
}

BrowserDestroyedObserver::BrowserDestroyedObserver(
    BrowserWindowInterface* browser)
    : session_id_(browser ? std::make_optional(browser->GetSessionID())
                          : std::nullopt) {
  if (browser) {
    // Handle the case where the browser has already been closed and is awaiting
    // destruction. ForEach will not iterate over closed browsers.
    browser_was_closed_ = true;
    GlobalBrowserCollection::GetInstance()->ForEach(
        [&](BrowserWindowInterface* collection_browser) {
          if (collection_browser == browser) {
            browser_was_closed_ = false;
            return false;
          }
          return true;
        });
    if (browser_was_closed_) {
      browser_ = browser->GetWeakPtr();
      return;
    }
  }
  browser_collection_observation_.Observe(
      GlobalBrowserCollection::GetInstance());
}

BrowserDestroyedObserver::~BrowserDestroyedObserver() = default;

void BrowserDestroyedObserver::Wait() {
  if (!browser_was_closed_) {
    run_loop_.Run();
  }
  // Wait for `browser_` to be destroyed.
  EXPECT_TRUE(base::test::RunUntil([&]() { return !browser_; }));
}

void BrowserDestroyedObserver::OnBrowserClosed(
    BrowserWindowInterface* browser) {
  if (!session_id_.has_value() ||
      browser->GetSessionID() == session_id_.value()) {
    browser_ = browser->GetWeakPtr();
    browser_was_closed_ = true;
    run_loop_.Quit();
  }
}

BrowserCreatedObserver::BrowserCreatedObserver() {
  browser_collection_observation_.Observe(
      GlobalBrowserCollection::GetInstance());
}

BrowserCreatedObserver::~BrowserCreatedObserver() = default;

Browser* BrowserCreatedObserver::Wait() {
  if (!browser_) {
    run_loop_.Run();
  }
  CHECK(browser_);
  return browser_->GetBrowserForMigrationOnly();
}

void BrowserCreatedObserver::OnBrowserCreated(BrowserWindowInterface* browser) {
  browser_ = browser;
  run_loop_.Quit();
}

void BrowserCreatedObserver::OnBrowserClosed(BrowserWindowInterface* browser) {
  // Clear `browser_` in the event of a removal to mitigate the risk of dangling
  // refs.
  browser_ = nullptr;
}

///////////////////////////////////////////////////////////////////////////////
// CheckWaiter:

CheckWaiter::CheckWaiter(base::RepeatingCallback<bool()> callback,
                         bool expected,
                         const base::TimeDelta& timeout)
    : callback_(callback),
      expected_(expected),
      timeout_(base::TimeTicks::Now() + timeout) {}

CheckWaiter::~CheckWaiter() = default;

void CheckWaiter::Wait() {
  if (Check()) {
    return;
  }

  base::RunLoop run_loop;
  quit_ = run_loop.QuitClosure();
  run_loop.Run();
}

bool CheckWaiter::Check() {
  if (callback_.Run() != expected_ && base::TimeTicks::Now() < timeout_) {
    // Check again after a short timeout. Important: Don't use an immediate
    // task to check again, because the pump would be allowed to run it
    // immediately without processing system events (system events are
    // required for the state to change).
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(base::IgnoreResult(&CheckWaiter::Check),
                       base::Unretained(this)),
        TestTimeouts::tiny_timeout());
    return false;
  }

  // Quit the run_loop to end the wait.
  if (!quit_.is_null()) {
    std::move(quit_).Run();
  }
  return true;
}

ViewBoundsWaiter::ViewBoundsWaiter(views::View* observed_view)
    : observed_view_(observed_view) {
  observed_view_->AddObserver(this);
}

ViewBoundsWaiter::~ViewBoundsWaiter() {
  observed_view_->RemoveObserver(this);
}

void ViewBoundsWaiter::WaitForNonEmptyBounds() {
  if (observed_non_empty_bounds_ || !observed_view_->bounds().IsEmpty()) {
    return;
  }
  run_loop_.Run();
}

void ViewBoundsWaiter::OnViewBoundsChanged(views::View* observed_view) {
  if (!observed_view_->bounds().IsEmpty()) {
    // Record bounds changes that may occur before the RunLoop is started.
    // This is necessary to avoid deadlock in case a view's bounds change
    // to nonempty and then empty before the RunLoop is started.
    observed_non_empty_bounds_ = true;
    run_loop_.Quit();
  }
}

ViewVisibilityWaiter::ViewVisibilityWaiter(views::View* observed_view,
                                           bool expected_visible)
    : view_(observed_view), expected_visible_(expected_visible) {
  observation_.Observe(view_.get());
}

ViewVisibilityWaiter::~ViewVisibilityWaiter() = default;

void ViewVisibilityWaiter::Wait() {
  if (expected_visible_ != view_->GetVisible()) {
    run_loop_.Run();
  }
}

void ViewVisibilityWaiter::OnViewVisibilityChanged(views::View* observed_view,
                                                   views::View* starting_view,
                                                   bool visible) {
  if (expected_visible_ == observed_view->GetVisible()) {
    run_loop_.Quit();
  }
}

namespace {

class WebModalShowWaiter
    : public web_modal::WebContentsModalDialogManager::Observer {
 public:
  explicit WebModalShowWaiter(content::WebContents* web_contents) {
    web_modal::WebContentsModalDialogManager::CreateForWebContents(
        web_contents);
    manager_ =
        web_modal::WebContentsModalDialogManager::FromWebContents(web_contents);
    observation_.Observe(manager_);
  }

  void Wait() {
    if (manager_->IsDialogActive()) {
      return;
    }
    run_loop_.Run();
  }

 private:
  // WebContentsModalDialogManager::Observer:
  void OnWillShow() override { run_loop_.Quit(); }

  base::RunLoop run_loop_;
  raw_ptr<web_modal::WebContentsModalDialogManager> manager_;
  base::ScopedObservation<web_modal::WebContentsModalDialogManager,
                          web_modal::WebContentsModalDialogManager::Observer>
      observation_{this};
};

}  // namespace

void WaitForWebModalDialog(content::WebContents* web_contents) {
  WebModalShowWaiter(web_contents).Wait();
}

WebContentsFocusEventTracker::WebContentsFocusEventTracker(
    content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents) {}

WebContentsFocusEventTracker::~WebContentsFocusEventTracker() = default;

void WebContentsFocusEventTracker::OnWebContentsFocused(
    content::RenderWidgetHost* render_widget_host) {
  focused_count_++;
}

void WebContentsFocusEventTracker::OnWebContentsLostFocus(
    content::RenderWidgetHost* render_widget_host) {
  lost_focus_count_++;
}

void WebContentsFocusEventTracker::Reset() {
  focused_count_ = 0;
  lost_focus_count_ = 0;
}

}  // namespace ui_test_utils
