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

#include <array>

#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharing_hub/sharing_hub_features.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_tabstrip.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/create_browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/frame/window_frame_util.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_view.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/find_bar_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/contents_web_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/omnibox/omnibox_popup_closer.h"
#include "chrome/browser/ui/views/toolbar/webui_test_utils.h"
#include "chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h"
#include "chrome/browser/ui/waap/initial_web_ui_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/base/web_view_focus_helper.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/omnibox_client.h"
#include "components/omnibox/common/omnibox_features.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.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/test_navigation_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/ui_base_features.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/interaction/view_focus_observer.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/widget/widget.h"

#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif

namespace {

using content::RenderViewHost;
using content::WebContents;

const char kSimplePage[] = "/focus/page_with_focus.html";
const char kStealFocusPage[] = "/focus/page_steals_focus.html";
const char kTypicalPage[] = "/focus/typical_page.html";

class BrowserFocusBasicTest : public InProcessBrowserTest {
 public:
  BrowserFocusBasicTest() {
    // interactive_ui_tests set `ui_test_utils::BringBrowserWindowToFront()` for
    // the setup function, which interferes with what the test wants to test so
    // unset it.
    set_global_browser_set_up_function(nullptr);
#if BUILDFLAG(IS_WIN)
    // For CHROME_HEADLESS, which is currently used for browser tests, native
    // window occlusion is turned off. Turn it on to match the production
    // environment.
    base::FieldTrialParams field_trial_params{
        { features::kApplyNativeOcclusionToCompositorType.name,
          features::kApplyNativeOcclusionToCompositorTypeRelease }};
    scoped_feature_list_.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {{features::kApplyNativeOcclusionToCompositor, field_trial_params},
         { features::kAlwaysTrackNativeWindowOcclusionForTest,
           {} }},
        /*disabled_features=*/{});
#endif
  }

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

  bool IsBrowserActive(BrowserWindowInterface* browser) {
    return GetWidgetForBrowser(browser)->IsActive();
  }

  void WaitForToolbarReady(
      BrowserWindowInterface* browser_interface = nullptr) {
    if (!browser_interface) {
      browser_interface = browser();
    }
    WaitForInitialWebUIToolbar(browser_interface);
  }

 private:
#if BUILDFLAG(IS_WIN)
  base::test::ScopedFeatureList scoped_feature_list_;
#endif
};

// A basic test to check that a newly opened browser window has focus and the
// focus is on the omnibox.
IN_PROC_BROWSER_TEST_F(BrowserFocusBasicTest, BrowserFocusedOnCreation) {
  WaitForToolbarReady();
  // Ensure that the initialization of the browser window is completed.
  ui_test_utils::CreateAsyncWidgetRequestWaiter(*browser()).Wait();
  // Widget activation happens asynchronously after window creation on some
  // platforms like Linux so absorb the difference by waiting for the
  // activation.
  views::test::WaitForWidgetActive(GetWidgetForBrowser(browser()), true);
  // Check that when a browser is created, it's active.
  EXPECT_TRUE(IsBrowserActive(browser()));
  EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));

  // Use `chrome::OpenEmptyWindow()` instead of directly creating a `Browser`
  // instance with `Browser::Create()` and calling `BrowserView::Show()` like
  // some tests do because this is what the production code does when opening a
  // new window. The difference is that it makes sure that there is at least one
  // tab on the window before calling `BrowserView::Show()`.
  BrowserWindowInterface* browser2 =
      chrome::OpenEmptyWindow(browser()->GetProfile());
  WaitForToolbarReady(browser2);
  ui_test_utils::CreateAsyncWidgetRequestWaiter(*browser2).Wait();
  views::test::WaitForWidgetActive(GetWidgetForBrowser(browser2), true);
  EXPECT_TRUE(IsBrowserActive(browser2));
  EXPECT_TRUE(ui_test_utils::IsViewFocused(browser2, VIEW_ID_OMNIBOX));
}

DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);

class BrowserFocusTest : public InteractiveBrowserTest {
 public:
  // InteractiveBrowserTest overrides:
  void SetUpOnMainThread() override {
    ASSERT_TRUE(embedded_test_server()->Start());
    InteractiveBrowserTest::SetUpOnMainThread();
    WaitForToolbarReady();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Slow bots are flaky due to slower loading interacting with
    // deferred commits.
    command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
  }

  bool IsViewFocused(ViewID vid) {
    return ui_test_utils::IsViewFocused(browser(), vid);
  }

  void ClickOnView(ViewID vid) { ui_test_utils::ClickOnView(browser(), vid); }

  void WaitForToolbarReady(
      BrowserWindowInterface* browser_interface = nullptr) {
    if (!browser_interface) {
      browser_interface = browser();
    }
    WaitForInitialWebUIToolbar(browser_interface);
  }

  void FocusNextElement(bool reverse) {
    std::vector<content::WebContents*> web_contents = {
        browser()->tab_strip_model()->GetActiveWebContents()};

    ToolbarButtonProvider* toolbar_button_provider =
        BrowserView::GetBrowserViewForBrowser(browser())
            ->toolbar_button_provider();
    if (WebUIToolbarWebView* webui_toolbar =
            toolbar_button_provider->GetWebUIToolbarViewForTesting()) {
      web_contents.push_back(
          webui_toolbar->GetWebViewForTesting()->web_contents());
    }

    ui_test_utils::FocusChangeObserver obs{GetFocusManager(), web_contents};
    ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_TAB, false,
                                                reverse, false, false));
    obs.WaitForFocusChange();
  }

  void TestFocusTraversal(bool reverse) {
    SCOPED_TRACE(base::StrCat(
        {"Started focus traversal, reverse=", base::ToString(reverse)}));

    // Move focus one element away from the omnibox.
    ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
    FocusNextElement(reverse);
    EXPECT_FALSE(IsViewFocused(VIEW_ID_OMNIBOX));

    // Traverse the whole focus chain until the omnibox is focused again.
    size_t c = 0;
    while (!IsViewFocused(VIEW_ID_OMNIBOX) && c < kMaxIterations) {
      FocusNextElement(reverse);
      ++c;
    }
    EXPECT_TRUE(c <= kMaxIterations);
    EXPECT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
  }

  views::FocusManager* GetFocusManager() {
    BrowserWindow* browser_window = BrowserWindow::FromBrowser(browser());
    DCHECK(browser_window);
    gfx::NativeWindow window = browser_window->GetNativeWindow();
    DCHECK(window);
    views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
    DCHECK(widget);
    views::FocusManager* focus_manager = widget->GetFocusManager();
    DCHECK(focus_manager);
    return focus_manager;
  }

  views::Widget* GetWidgetForBrowser(Browser* browser) {
    BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
    CHECK(browser_view);
    views::Widget* widget = browser_view->GetWidget();
    CHECK(widget);
    return widget;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  constexpr static size_t kMaxIterations = 20;
};

// Test that adding a new foreground tab to a background (inactive) window
// activates that window, while adding a background tab does not.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest,
                       BackgroundWindowActivatedOnNewForegroundTab) {
#if BUILDFLAG(IS_OZONE)
  // TODO(crbug.com/430097333): Wayland doesn't support programmatic window
  // activation. Re-enable when activation is supported.
  if (::ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP() << "Wayland doesn't support programmatic window activation";
  }
#endif

  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  EXPECT_TRUE(browser()->GetWindow()->IsActive());

  // Create a second browser window and activate it.
  BrowserWindowInterface* browser2 =
      chrome::OpenEmptyWindow(browser()->GetProfile());
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser2));
  EXPECT_TRUE(browser2->GetWindow()->IsActive());
  EXPECT_FALSE(browser()->GetWindow()->IsActive());

  // Adding a background tab to the inactive window should not activate it.
  chrome::AddTabAt(browser(), GURL(chrome::kChromeUINewTabURL), -1,
                   /*foreground=*/false);
  EXPECT_FALSE(browser()->GetWindow()->IsActive());
  EXPECT_TRUE(browser2->GetWindow()->IsActive());

  // Adding a foreground tab to the inactive window should activate it.
  ui_test_utils::BrowserActivationWaiter activation_waiter(browser());
  chrome::AddTabAt(browser(), GURL(chrome::kChromeUINewTabURL), -1,
                   /*foreground=*/true, std::nullopt, /*pinned=*/false,
                   NavigateParams::WindowAction::kShowWindow);
  activation_waiter.WaitForActivation();
  EXPECT_TRUE(browser()->GetWindow()->IsActive());
}

IN_PROC_BROWSER_TEST_F(BrowserFocusTest, ClickingMovesFocus) {
  RunTestSequence(
      InstrumentTab(kWebContentsId),
      ObserveState(views::test::kCurrentFocusedViewId,
                   GetWidgetForBrowser(browser())),
      WaitForState(views::test::kCurrentFocusedViewId, kOmniboxElementId),
      // Click on the tab container and check that it has focus.
      MoveMouseTo(ContentsWebView::kContentsWebViewElementId), ClickMouse(),
      WaitForState(views::test::kCurrentFocusedViewId,
                   ContentsWebView::kContentsWebViewElementId),
      // Click on the omnibox and check that it has focus.
      MoveMouseTo(kOmniboxElementId), ClickMouse(),
      WaitForState(views::test::kCurrentFocusedViewId, kOmniboxElementId));
}

IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BrowsersRememberFocus) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  const GURL url = embedded_test_server()->GetURL(kSimplePage);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  gfx::NativeWindow window = browser()->GetWindow()->GetNativeWindow();

  // The focus should be on the Tab contents.
  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
  // Now hide the window, show it again, the focus should not have changed.
  ui_test_utils::HideNativeWindow(window);
  ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(window));
  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));

  chrome::FocusLocationBar(browser());
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
  // Hide the window, show it again, the focus should not have changed.
  ui_test_utils::HideNativeWindow(window);
  ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(window));
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
}

// Tabs remember focus.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, TabsRememberFocus) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  const GURL url = embedded_test_server()->GetURL(kSimplePage);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Create several tabs.
  for (int i = 0; i < 4; ++i) {
    chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);
  }

  // Alternate focus for the tab.
  const std::array<std::array<const bool, 5>, 3> kFocusPage = {{
      {true, true, true, true, false},
      {false, false, false, false, false},
      {false, true, false, true, false},
  }};

  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 5; j++) {
      // Activate the tab.
      browser()->tab_strip_model()->ActivateTabAt(
          j, TabStripUserGestureDetails(
                 TabStripUserGestureDetails::GestureType::kOther));

      // Activate the location bar or the page.
      if (kFocusPage[i][j]) {
        browser()->tab_strip_model()->GetWebContentsAt(j)->Focus();
      } else {
        chrome::FocusLocationBar(browser());
      }
    }

    // Now come back to the tab and check the right view is focused.
    for (int j = 0; j < 5; j++) {
      // Activate the tab.
      browser()->tab_strip_model()->ActivateTabAt(
          j, TabStripUserGestureDetails(
                 TabStripUserGestureDetails::GestureType::kOther));

      ViewID vid = kFocusPage[i][j] ? VIEW_ID_TAB_CONTAINER : VIEW_ID_OMNIBOX;
      ui_test_utils::WaitForViewFocus(browser(), vid, true);
    }

    browser()->tab_strip_model()->ActivateTabAt(
        0, TabStripUserGestureDetails(
               TabStripUserGestureDetails::GestureType::kOther));
    // Try the above, but with ctrl+tab. Since tab normally changes focus,
    // this has regressed in the past. Loop through several times to be sure.
    for (int j = 0; j < 15; j++) {
      ViewID vid =
          kFocusPage[i][j % 5] ? VIEW_ID_TAB_CONTAINER : VIEW_ID_OMNIBOX;
      ui_test_utils::WaitForViewFocus(browser(), vid, true);
      ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_TAB, true,
                                                  false, false, false));
      // Wait for tab switch to complete.
      int expected_next_tab = (j + 1) % 5;
      ASSERT_TRUE(base::test::RunUntil([&]() {
        return browser()->tab_strip_model()->active_index() ==
               expected_next_tab;
      }));
    }

    // As above, but with ctrl+shift+tab.
    browser()->tab_strip_model()->ActivateTabAt(
        4, TabStripUserGestureDetails(
               TabStripUserGestureDetails::GestureType::kOther));
    for (int j = 14; j >= 0; --j) {
      ViewID vid =
          kFocusPage[i][j % 5] ? VIEW_ID_TAB_CONTAINER : VIEW_ID_OMNIBOX;
      ui_test_utils::WaitForViewFocus(browser(), vid, true);
      ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_TAB, true,
                                                  true, false, false));
      // Wait for tab switch to complete.
      int expected_next_tab = (j + 4) % 5;
      ASSERT_TRUE(base::test::RunUntil([&]() {
        return browser()->tab_strip_model()->active_index() ==
               expected_next_tab;
      }));
    }
  }
}

// Tabs remember focus with find-in-page box.
#if BUILDFLAG(IS_MAC)
// TODO(https://crbug.com/40268465): Re-enable when child widget focus manager
// relationship is fixed.
#define MAYBE_TabsRememberFocusFindInPage DISABLED_TabsRememberFocusFindInPage
#else
#define MAYBE_TabsRememberFocusFindInPage TabsRememberFocusFindInPage
#endif
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_TabsRememberFocusFindInPage) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  const GURL url = embedded_test_server()->GetURL(kSimplePage);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  chrome::Find(browser());
  ui_test_utils::FindInPage(
      browser()->tab_strip_model()->GetActiveWebContents(), u"a", true, false,
      nullptr, nullptr);
  ASSERT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));

  // Focus the location bar.
  chrome::FocusLocationBar(browser());

  // Create a 2nd tab.
  chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);

  // Focus should be on the recently opened tab page.
  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));

  // Select 1st tab, focus should still be on the location-bar.
  // (bug http://crbug.com/41007822)
  browser()->tab_strip_model()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));

  // Now open the find box again, switch to another tab and come back, the focus
  // should return to the find box.
  chrome::Find(browser());
  ASSERT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
  browser()->tab_strip_model()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
  browser()->tab_strip_model()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  ASSERT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
}

// Background window does not steal focus.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BackgroundBrowserDontStealFocus) {
  // Ensure the browser process state is in sync with the WindowServer process.
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));

  // Open a new browser window.
  Browser* background_browser =
      CreateBrowserWindow(BrowserWindowCreateParams(browser()->GetProfile(),
                                                    /*from_user_gesture=*/true))
          ->GetBrowserForMigrationOnly();
  chrome::AddTabAt(background_browser, GURL(), -1, true);
  background_browser->GetWindow()->Show();

  const GURL steal_focus_url = embedded_test_server()->GetURL(kStealFocusPage);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(background_browser, steal_focus_url));

  // The navigation will activate |background_browser|. Except, on some
  // platforms, that may be asynchronous. Ensure the activation is properly
  // reflected in the browser process by activating again.
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(background_browser));
  EXPECT_TRUE(background_browser->GetWindow()->IsActive());

  // Activate the first browser (again). Note BringBrowserWindowToFront() does
  // Show() and Focus(), but not Activate(), which is needed for Desktop Linux.
  browser()->GetWindow()->Activate();
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  EXPECT_TRUE(browser()->GetWindow()->IsActive());
  ASSERT_TRUE(content::ExecJs(
      background_browser->tab_strip_model()->GetActiveWebContents(),
      "stealFocus();"));

  // Try flushing tasks. Note that on Mac and Desktop Linux, window activation
  // is asynchronous. There's no way to guarantee that the WindowServer process
  // has actually activated a window without waiting for the activation event.
  // But this test is checking that _no_ activation event occurs. So there is
  // nothing to wait for. So, assuming the test fails and |unfocused_browser|
  // _did_ activate, the expectation below still isn't guaranteed to fail after
  // flushing run loops.
  content::RunAllTasksUntilIdle();

  // Make sure the first browser is still active.
  EXPECT_TRUE(browser()->GetWindow()->IsActive());
}

// Page cannot steal focus when focus is on location bar.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, LocationBarLockFocus) {
  // Open the page that steals focus.
  const GURL url = embedded_test_server()->GetURL(kStealFocusPage);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  chrome::FocusLocationBar(browser());

  ASSERT_TRUE(content::ExecJs(
      browser()->tab_strip_model()->GetActiveWebContents(), "stealFocus();"));

  // Make sure the location bar is still focused.
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
}

// Test forward and reverse focus traversal on a typical page.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FocusTraversal) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  const GURL url = embedded_test_server()->GetURL(kTypicalPage);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  std::vector<content::WebContents*> web_contents = {
      browser()->tab_strip_model()->GetActiveWebContents()};
  ToolbarButtonProvider* toolbar_button_provider =
      BrowserView::GetBrowserViewForBrowser(browser())
          ->toolbar_button_provider();
  if (WebUIToolbarWebView* webui_toolbar =
          toolbar_button_provider->GetWebUIToolbarViewForTesting()) {
    web_contents.push_back(
        webui_toolbar->GetWebViewForTesting()->web_contents());
  }

  ui_test_utils::FocusChangeObserver obs{GetFocusManager(), web_contents};
  chrome::FocusLocationBar(browser());
  obs.WaitForFocusChange();
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));

  // Simulate ESC being pressed to close the omnibox suggestions popup.
  omnibox::OmniboxPopupCloser::From(browser())->CloseWithReason(
      omnibox::PopupCloseReason::kEscapeKeyPressed);

  // Loop through the focus chain twice in each direction for good measure.
  TestFocusTraversal(false);
  TestFocusTraversal(false);
  TestFocusTraversal(true);
  TestFocusTraversal(true);
}

// Test that find-in-page UI can request focus, even when it is already open.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FindFocusTest) {
  RunTestSequence(
      InstrumentTab(kWebContentsId),
      ObserveState(views::test::kCurrentFocusedViewId,
                   GetWidgetForBrowser(browser())),
      Do([this]() { chrome::Find(browser()); }),
      WaitForState(views::test::kCurrentFocusedViewId, FindBarView::kTextField),
      MoveMouseTo(kOmniboxElementId), ClickMouse(),
      WaitForState(views::test::kCurrentFocusedViewId, kOmniboxElementId),
      Do([this]() { chrome::Find(browser()); }),
      WaitForState(views::test::kCurrentFocusedViewId,
                   FindBarView::kTextField));
}

// Makes sure the focus is in the right location when opening the different
// types of tabs.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, TabInitialFocus) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));

  // Open the history tab, focus should be on the tab contents.
  chrome::ShowHistory(browser());
  ASSERT_NO_FATAL_FAILURE(EXPECT_TRUE(content::WaitForLoadStop(
      browser()->tab_strip_model()->GetActiveWebContents())));
  EXPECT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));

  // Open the new tab, focus should be on the location bar.
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  ASSERT_NO_FATAL_FAILURE(EXPECT_TRUE(content::WaitForLoadStop(
      browser()->tab_strip_model()->GetActiveWebContents())));
  EXPECT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));

  // Open the download tab, focus should be on the tab contents.
  chrome::ShowDownloads(browser());
  ASSERT_NO_FATAL_FAILURE(EXPECT_TRUE(content::WaitForLoadStop(
      browser()->tab_strip_model()->GetActiveWebContents())));
  EXPECT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));

  // Open about:blank, focus should be on the location bar.
  chrome::AddSelectedTabWithURL(browser(), GURL(url::kAboutBlankURL),
                                ui::PAGE_TRANSITION_LINK);
  ASSERT_NO_FATAL_FAILURE(EXPECT_TRUE(content::WaitForLoadStop(
      browser()->tab_strip_model()->GetActiveWebContents())));
  EXPECT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
}

// Tests that focus goes where expected when using reload.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FocusOnReload) {
  // Open the new tab, reload.
  {
    auto& contents = chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
    content::WaitForLoadStop(&contents);
  }
  content::RunAllPendingInMessageLoop();

  {
    content::LoadStopObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
    observer.Wait();
  }
  // Focus should stay on the location bar.
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));

  // Open a regular page, focus the location bar, reload.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(kSimplePage)));
  chrome::FocusLocationBar(browser());
  ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
  {
    content::LoadStopObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
    observer.Wait();
  }

  // Focus should now be on the tab contents.
  chrome::ShowDownloads(browser());
  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
}

// Tests that focus goes where expected when using reload on a crashed tab.
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
// Hangy, http://crbug.com/41182734.
#define MAYBE_FocusOnReloadCrashedTab DISABLED_FocusOnReloadCrashedTab
#else
#define MAYBE_FocusOnReloadCrashedTab FocusOnReloadCrashedTab
#endif
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_FocusOnReloadCrashedTab) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));

  // Open a regular page, crash, reload.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(kSimplePage)));
  content::CrashTab(browser()->tab_strip_model()->GetActiveWebContents());
  {
    content::LoadStopObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
    observer.Wait();
  }

  // Focus should now be on the tab contents.
  chrome::ShowDownloads(browser());
  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
}

// Tests that focus goes to frame after crashed tab.
// TODO(shrikant): Find out where the focus should be deterministically.
// Currently focused_view after crash seem to be non null in debug mode
// (invalidated pointer 0xcccccc).
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, DISABLED_FocusAfterCrashedTab) {
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));

  content::CrashTab(browser()->tab_strip_model()->GetActiveWebContents());

  ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
}

// Tests that when omnibox triggers a navigation, then the focus is moved into
// the current tab.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, NavigateFromOmnibox) {
  const GURL url = embedded_test_server()->GetURL("/title1.html");

  // Focus the Omnibox.
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  chrome::FocusLocationBar(browser());
  OmniboxView* view =
      BrowserWindow::FromBrowser(browser())->GetLocationBar()->GetOmniboxView();

  // Simulate typing a URL into the omnibox.
  view->SetUserText(base::UTF8ToUTF16(url.spec()));
  EXPECT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
  EXPECT_FALSE(view->IsSelectAll());

  // Simulate pressing Enter and wait until the navigation starts.
  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  content::TestNavigationManager nav_manager(web_contents, url);
  ASSERT_TRUE(
      ui_controls::SendKeyPress(browser()->GetWindow()->GetNativeWindow(),
                                ui::VKEY_RETURN, false, false, false, false));
  ASSERT_TRUE(nav_manager.WaitForRequestStart());

  // Verify that a navigation has started.
  EXPECT_TRUE(web_contents->GetController().GetPendingEntry());
  // Verify that the Omnibox text is not selected - this is a regression test
  // for https://crbug.com/40672172.
  EXPECT_FALSE(view->IsSelectAll());
  // Intentionally not asserting anything about IsViewFocused in this
  // _intermediate_ state.

  // Wait for the navigation to finish and verify final, steady state.
  ASSERT_TRUE(nav_manager.WaitForNavigationFinished());
  EXPECT_TRUE(nav_manager.was_successful());
  EXPECT_EQ(url, web_contents->GetLastCommittedURL());
  EXPECT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
  EXPECT_FALSE(view->IsSelectAll());
}

// Tests that when a new tab is opened from the omnibox, the focus is moved from
// the omnibox for the current tab.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, NavigateFromOmniboxIntoNewTab) {
  GURL url("http://www.google.com/");
  GURL url2("http://maps.google.com/");

  // Navigate to url.
  NavigateParams p(browser(), url, ui::PAGE_TRANSITION_LINK);
  p.window_action = NavigateParams::WindowAction::kShowWindow;
  p.disposition = WindowOpenDisposition::CURRENT_TAB;
  Navigate(&p);

  // Focus the omnibox.
  chrome::FocusLocationBar(browser());

  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate an alt-enter.
  omnibox_client->OnAutocompleteAccept(
      url2, nullptr, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui::PAGE_TRANSITION_TYPED, AutocompleteMatchType::URL_WHAT_YOU_TYPED,
      base::TimeTicks(), false, false, std::u16string(), AutocompleteMatch(),
      AutocompleteMatch());

  // Make sure the second tab is selected.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  // The tab contents should have the focus in the second tab.
  EXPECT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));

  // Go back to the first tab. The focus should not be in the omnibox.
  chrome::SelectPreviousTab(browser());
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
  EXPECT_FALSE(IsViewFocused(VIEW_ID_OMNIBOX));
}

IN_PROC_BROWSER_TEST_F(BrowserFocusTest, OmniboxFocusesOnNewTab) {
  RunTestSequence(
      InstrumentTab(kWebContentsId),
      ObserveState(views::test::kCurrentFocusedViewId,
                   GetWidgetForBrowser(browser())),
      WaitForState(views::test::kCurrentFocusedViewId, kOmniboxElementId));
}

// TODO(crbug.com/370117091): This currently tests the incorrect behavior that
// causes the omnibox to *not* get focus when going back to a new tab page via
// the history. Update this test when the bug is fixed.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, OmniboxFocusStateAcrossHistory) {
  const GURL title1_url = embedded_test_server()->GetURL("/title1.html");
  const GURL ntp_url = chrome::ChromeUINewTabURLAsGURL();
  RunTestSequence(
      InstrumentTab(kWebContentsId),
      ObserveState(views::test::kCurrentFocusedViewId,
                   GetWidgetForBrowser(browser())),
      NavigateWebContents(kWebContentsId, ntp_url),
      NavigateWebContents(kWebContentsId, title1_url),
      MoveMouseTo(ContentsWebView::kContentsWebViewElementId), ClickMouse(),
      // Navigate back. Check that the location bar is not focused. This should
      // focus the location bar, but that is not the current behavior.
      PressButton(kToolbarBackButtonElementId),
      WaitForWebContentsNavigation(kWebContentsId, ntp_url),
      WaitForState(views::test::kCurrentFocusedViewId,
                   testing::Ne(kOmniboxElementId)),
      // Navigate forward. Should focus the body.
      PressButton(kToolbarForwardButtonElementId),
      WaitForWebContentsNavigation(kWebContentsId, title1_url),
      WaitForState(views::test::kCurrentFocusedViewId,
                   ContentsWebView::kContentsWebViewElementId));
}

// Ensure that crbug.com/40083350 does not regress. This test checks that the
// Omnibox does not get focused when loading about:blank in a case where it's
// not the startup URL, e.g. when a page opens a popup to about:blank, with a
// null opener, and then navigates it. This is a potential security issue; see
// comments in |WebContentsImpl::FocusLocationBarByDefault|.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, AboutBlankNavigationLocationTest) {
  const GURL url1 = embedded_test_server()->GetURL("/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));

  TabStripModel* tab_strip = browser()->tab_strip_model();
  WebContents* web_contents = tab_strip->GetActiveWebContents();

  const GURL url2 = embedded_test_server()->GetURL("/title2.html");
  const std::string spoof =
      "var w = window.open('about:blank'); w.opener = null;"
      "w.document.location = '" +
      url2.spec() + "';";

  ASSERT_TRUE(content::ExecJs(web_contents, spoof));
  EXPECT_EQ(url1, web_contents->GetVisibleURL());
  // After running the spoof code, |GetActiveWebContents| returns the new tab,
  // not the same as |web_contents|.
  ASSERT_NO_FATAL_FAILURE(EXPECT_TRUE(content::WaitForLoadStop(
      browser()->tab_strip_model()->GetActiveWebContents())));
  EXPECT_FALSE(IsViewFocused(VIEW_ID_OMNIBOX));
}

// Regression test for https://crbug.com/40086361.  This ensures that the
// omnibox does not get focused if another tab in the same window navigates to
// the New Tab Page, since that can scroll the origin of the selected tab out of
// view.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, NoFocusForBackgroundNTP) {
  // Start at the NTP and navigate to a test page.  We will later go back to the
  // NTP, which gives the omnibox focus in some cases.
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));

  TabStripModel* tab_strip = browser()->tab_strip_model();
  WebContents* opener_web_contents = tab_strip->GetActiveWebContents();

  // Open a second tab from the test page.
  const GURL new_url = embedded_test_server()->GetURL("/title2.html");
  const std::string open_script = "window.open('" + new_url.spec() + "');";
  content::WebContentsAddedObserver open_observer;
  ASSERT_TRUE(content::ExecJs(opener_web_contents, open_script));
  WebContents* new_web_contents = open_observer.GetWebContents();

  // Tell the first (non-selected) tab to go back.  This should not give the
  // omnibox focus, since the navigation occurred in a different tab.  Otherwise
  // the focus may scroll the origin out of view, making a spoof possible.
  const std::string go_back_script = "window.opener.history.back();";
  content::TestNavigationObserver back_observer(opener_web_contents);
  ASSERT_TRUE(content::ExecJs(new_web_contents, go_back_script));
  back_observer.Wait();
  EXPECT_FALSE(IsViewFocused(VIEW_ID_OMNIBOX));
}

// Tests that the location bar is focusable when showing, which is the case in
// popup windows.
// TODO(crbug.com/40794922): Flaky on Linux.
// TODO(crbug.com/41493632): Broken since CR2023.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, DISABLED_PopupLocationBar) {
  Browser* popup_browser = CreateBrowserForPopup(browser()->GetProfile());

  // Make sure the popup is in the front. Otherwise the test is flaky.
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup_browser));

  ui_test_utils::FocusView(popup_browser, VIEW_ID_TAB_CONTAINER);
  EXPECT_TRUE(
      ui_test_utils::IsViewFocused(popup_browser, VIEW_ID_TAB_CONTAINER));

  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(popup_browser, ui::VKEY_TAB,
                                              false, false, false, false));
  ui_test_utils::WaitForViewFocus(popup_browser, VIEW_ID_LOCATION_ICON, true);

  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(popup_browser, ui::VKEY_TAB,
                                              false, false, false, false));
  ui_test_utils::WaitForViewFocus(popup_browser, VIEW_ID_OMNIBOX, true);

  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(popup_browser, ui::VKEY_TAB,
                                              false, false, false, false));
  if (sharing_hub::HasPageAction(browser()->GetProfile(), true)) {
    ui_test_utils::WaitForViewFocus(popup_browser, VIEW_ID_SHARING_HUB_BUTTON,
                                    true);
    ASSERT_TRUE(ui_test_utils::SendKeyPressSync(popup_browser, ui::VKEY_TAB,
                                                false, false, false, false));
  }

  ui_test_utils::WaitForViewFocus(popup_browser, VIEW_ID_TAB_CONTAINER, true);
}

// Tests that the location bar is not focusable when hidden, which is the case
// in app windows.
IN_PROC_BROWSER_TEST_F(BrowserFocusTest, AppLocationBar) {
  Browser* app_browser = CreateBrowserForApp("foo", browser()->GetProfile());

  // Make sure the app window is in the front. Otherwise the test is flaky.
  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(app_browser));

  ui_test_utils::FocusView(app_browser, VIEW_ID_TAB_CONTAINER);
  EXPECT_TRUE(ui_test_utils::IsViewFocused(app_browser, VIEW_ID_TAB_CONTAINER));

  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(app_browser, ui::VKEY_TAB, false,
                                              false, false, false));
  base::RunLoop().RunUntilIdle();
  ui_test_utils::WaitForViewFocus(app_browser, VIEW_ID_TAB_CONTAINER, true);
}

}  // namespace
