910e62b5创建于 1月15日历史提交
// Copyright 2021 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/browser/extensions/api/identity/web_auth_flow.h"

#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/api/identity/web_auth_flow_info_bar_delegate.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/nuke_profile_directory_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

class MockWebAuthFlowDelegate : public WebAuthFlow::Delegate {
 public:
  MOCK_METHOD(void, OnAuthFlowURLChange, (const GURL&), (override));
  MOCK_METHOD(void, OnAuthFlowTitleChange, (const std::string&), (override));
  MOCK_METHOD(void, OnAuthFlowFailure, (WebAuthFlow::Failure), (override));
};

class WebAuthFlowBrowserTest : public InProcessBrowserTest {
 public:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());

    // Delete the flow early if OnAuthFlowFailure is called. Simulates real
    // usages.
    ON_CALL(mock(), OnAuthFlowFailure(testing::_))
        .WillByDefault(
            [this](WebAuthFlow::Failure failure) { DeleteWebAuthFlow(); });
  }

  void DeleteWebAuthFlow() {
    DCHECK(web_auth_flow_);
    // Delete the web auth flow (uses DeleteSoon).
    web_auth_flow_.release()->DetachDelegateAndDelete();
  }

  void TearDownOnMainThread() override {
    // Ensures any timer tasks finish.
    timeout_task_runner_->RunUntilIdle();
    if (web_auth_flow_) {
      DeleteWebAuthFlow();
    }
    // Ensures `web_auth_flow_` is deleted before teardown. This cannot be run
    // in |DeleteWebAuthFlow| as it can be called from `timeout_task_runner_`'s
    // loop.
    base::RunLoop().RunUntilIdle();
    InProcessBrowserTest::TearDownOnMainThread();
  }

  void StartWebAuthFlow(
      const GURL& url,
      WebAuthFlow::Mode mode = WebAuthFlow::Mode::INTERACTIVE,
      Profile* profile = nullptr,
      WebAuthFlow::AbortOnLoad abort_on_load_for_non_interactive =
          WebAuthFlow::AbortOnLoad::kYes,
      std::optional<base::TimeDelta> timeout_for_non_interactive = std::nullopt,
      std::optional<gfx::Rect> popup_bounds = std::nullopt) {
    if (!profile)
      profile = GetProfile();

    web_auth_flow_ = std::make_unique<WebAuthFlow>(
        &mock_web_auth_flow_delegate_, profile, url, mode,
        /*user_gesture=*/true, abort_on_load_for_non_interactive,
        timeout_for_non_interactive, popup_bounds);

    timeout_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
    web_auth_flow_->SetClockForTesting(timeout_task_runner_->GetMockTickClock(),
                                       timeout_task_runner_);
    web_auth_flow_->Start();
  }

  WebAuthFlow* web_auth_flow() { return web_auth_flow_.get(); }

  content::WebContents* web_contents() {
    if (!web_auth_flow_) {
      return nullptr;
    }
    return web_auth_flow_->web_contents();
  }

  MockWebAuthFlowDelegate& mock() { return mock_web_auth_flow_delegate_; }

  scoped_refptr<base::TestMockTimeTaskRunner> timeout_task_runner() {
    return timeout_task_runner_;
  }

 private:
  std::unique_ptr<WebAuthFlow> web_auth_flow_;
  MockWebAuthFlowDelegate mock_web_auth_flow_delegate_;
  scoped_refptr<base::TestMockTimeTaskRunner> timeout_task_runner_;
};

class WebAuthFlowInBrowserTabParamBrowserTest : public WebAuthFlowBrowserTest {
 public:
  bool JsRedirectToUrl(const GURL& url) {
    content::TestNavigationObserver redirect_observer(url);
    redirect_observer.WatchExistingWebContents();
    const std::string script =
        base::StringPrintf("window.location.href = '%s'", url.spec().c_str());
    bool result = content::ExecJs(web_contents(), script);
    if (result) {
      redirect_observer.Wait();
    }
    return result;
  }
};

IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowURLChangeCalled) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  // Observer for waiting until a navigation to a url has finished.
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url);

  navigation_observer.WaitForNavigationFinished();
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowFailureChangeCalled) {
  // Navigate to a url that doesn't exist.
  const GURL error_url = embedded_test_server()->GetURL("/error");

  content::TestNavigationObserver navigation_observer(error_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowFailure should be called
  // by DidFinishNavigation.
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::LOAD_FAILED));
  StartWebAuthFlow(error_url);

  navigation_observer.WaitForNavigationFinished();
}

// Tests that the flow launched in silent mode with default parameters will
// terminate immediately with the "interacation required" error if the page
// loads and does not navigate to the redirect URL.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowFailureCalledInteractionRequired) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // In SILENT mode, DidStopLoading() will force the auth flow to fail if it has
  // not already redirected, because we did not specify a timeout.
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
  StartWebAuthFlow(auth_url, WebAuthFlow::SILENT);

  navigation_observer.Wait();
}

// Tests that the flow launched in silent mode with
// `abortOnLoadForNonInteractive` set to `false` will terminate with the
// "interaction required" after a specified timeout if the page loads and does
// not navigate to the redirect URL.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowInteractionRequiredWithTimeout) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
  // before calling OnAuthFlowFailure.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
                   WebAuthFlow::AbortOnLoad::kNo,
                   /*timeout_for_non_interactive=*/base::Milliseconds(50));
  navigation_observer.Wait();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Increment the time by 40ms - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Milliseconds(40));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Now we exceed our 50ms limit and expect a failure.
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
  timeout_task_runner()->FastForwardBy(base::Milliseconds(20));
}

// Tests that the flow launched in silent mode with
// `abortOnLoadForNonInteractive` set to `false` will terminate with the
// "interaction required" error after a default timeout if the page loads and
// does not navigate to the redirect URL.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowInteractionRequiredWithDefaultTimeout) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // In SILENT mode, DidStopLoading() will wait for the default 1 minute timeout
  // before calling OnAuthFlowFailure.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
                   WebAuthFlow::AbortOnLoad::kNo);
  navigation_observer.Wait();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Increment the time by 59s - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Seconds(59));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Now we exceed the 1 minute default limit and expect a failure.
  // The error is "interaction required" when the flow times out when the page
  // has already loaded.
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
  timeout_task_runner()->FastForwardBy(base::Seconds(2));
}

// Tests that the flow launched in silent mode with `timeoutMsForNonInteractive`
// set will terminate with the "timed out" error after a timeout if the page
// fails to load (distinct from the flow failing to navigate to the redirect URL
// in time).
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowPageLoadTimeout) {
  const GURL auth_url = embedded_test_server()->GetURL("/hung-after-headers");

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  base::RunLoop run_loop;
  ON_CALL(mock(), OnAuthFlowURLChange(testing::_))
      .WillByDefault([&run_loop](const GURL& url) { run_loop.Quit(); });
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
  // before calling OnAuthFlowFailure.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
                   WebAuthFlow::AbortOnLoad::kYes,
                   /*timeout_for_non_interactive=*/base::Milliseconds(50));
  // Wait for navigation to the failing page to start first.
  run_loop.Run();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Increment the time by 40ms - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Milliseconds(40));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Now we exceed our 50ms limit and expect a failure. The error is "load
  // timed out" when the flow times out while the page is still loading.
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::TIMED_OUT));
  timeout_task_runner()->FastForwardBy(base::Milliseconds(20));
}

// Tests that the flow launched in silent mode with
// `abortOnLoadForNonInteractive` set to `false` and
// `timeoutMsForNonInteractive` set will succeed if it navigates to the redirect
// URL before the timeout.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowRedirectBeforeTimeout) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
  // before calling OnAuthFlowFailure.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
                   WebAuthFlow::AbortOnLoad::kNo,
                   /*timeout_for_non_interactive=*/base::Milliseconds(50));

  navigation_observer.Wait();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Increment the time by 40ms - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Milliseconds(40));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Redirect after page load and check we get OnAuthFlowURLChange.
  const GURL redirect_url = embedded_test_server()->GetURL("/title2.html");
  EXPECT_CALL(mock(), OnAuthFlowURLChange(redirect_url));
  EXPECT_TRUE(JsRedirectToUrl(redirect_url));
}

// Tests that the loaded auth page can redirect multiple times and fails only
// after the timeout.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
                       OnAuthFlowMultipleRedirects) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  // The delegate method OnAuthFlowURLChange should be called
  // by DidStartNavigation.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
  // before calling OnAuthFlowFailure.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
                   WebAuthFlow::AbortOnLoad::kNo,
                   /*timeout_for_non_interactive=*/base::Milliseconds(50));

  navigation_observer.Wait();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Increment the time by 10ms - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Milliseconds(10));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Redirect after page load and check we get OnAuthFlowURLChange.
  const GURL redirect_url = embedded_test_server()->GetURL("/title2.html");
  EXPECT_CALL(mock(), OnAuthFlowURLChange(redirect_url));
  EXPECT_TRUE(JsRedirectToUrl(redirect_url));

  // Increment the time by 10ms - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Milliseconds(10));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Redirect after 2nd page load and check we get OnAuthFlowURLChange.
  const GURL redirect_url2 = embedded_test_server()->GetURL("/title3.html");
  EXPECT_CALL(mock(), OnAuthFlowURLChange(redirect_url2));
  EXPECT_TRUE(JsRedirectToUrl(redirect_url2));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Increment the time by 10ms - nothing should happen.
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
  timeout_task_runner()->FastForwardBy(base::Milliseconds(10));
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Now we exceed our 50ms limit and expect a failure.
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
  timeout_task_runner()->FastForwardBy(base::Milliseconds(30));
}

class WebAuthFlowFencedFrameTest
    : public WebAuthFlowInBrowserTabParamBrowserTest {
 public:
  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(WebAuthFlowFencedFrameTest,
                       FencedFrameNavigationSuccess) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  // Observer for waiting until loading stops. A fenced frame will be created
  // after load has finished.
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.set_wait_event(
      content::TestNavigationObserver::WaitEvent::kLoadStopped);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url);

  navigation_observer.Wait();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Navigation for fenced frames should not affect to call the delegate methods
  // in the WebAuthFlow.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url)).Times(0);

  // Create a fenced frame into the inner WebContents of the WebAuthFlow.
  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
      web_contents()->GetPrimaryMainFrame(),
      embedded_test_server()->GetURL("/fenced_frames/title1.html")));
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowFencedFrameTest,
                       FencedFrameNavigationFailure) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  // Observer for waiting until loading stops. A fenced frame will be created
  // after load has finished.
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.set_wait_event(
      content::TestNavigationObserver::WaitEvent::kLoadStopped);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url);

  navigation_observer.Wait();
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Navigation for fenced frames should not affect to call the delegate methods
  // in the WebAuthFlow.
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url)).Times(0);
  EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);

  // Create a fenced frame into the inner WebContents of the WebAuthFlow.
  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
      web_contents()->GetPrimaryMainFrame(),
      embedded_test_server()->GetURL("/error"), net::Error::ERR_FAILED));
}

// This test is in two parts:
// - First create a WebAuthFlow in interactive mode that will create a new tab
// with the auth_url.
// - Close the new created tab, simulating the user declining the consent by
// closing the tab.
//
// These two tests are combined into one in order not to re-test the tab
// creation twice.
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                       InteractivePopupWindowCreatedWithAuthURL_ThenCloseTab) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);

  const char extension_name[] = "extension_name";
  web_auth_flow()->SetShouldShowInfoBar(extension_name);

  navigation_observer.Wait();

  Browser* popup_browser = chrome::FindBrowserWithTab(web_contents());
  EXPECT_EQ(popup_browser->type(), Browser::Type::TYPE_POPUP);
  EXPECT_NE(browser(), popup_browser);
  TabStripModel* tabs = popup_browser->tab_strip_model();
  EXPECT_EQ(tabs->GetActiveWebContents()->GetLastCommittedURL(), auth_url);

  // Check info bar exists and displays proper message with extension name.
  base::WeakPtr<WebAuthFlowInfoBarDelegate> infobar_delegate =
      web_auth_flow()->GetInfoBarDelegateForTesting();
  EXPECT_TRUE(infobar_delegate);
  EXPECT_EQ(
      infobar_delegate->GetIdentifier(),
      infobars::InfoBarDelegate::EXTENSIONS_WEB_AUTH_FLOW_INFOBAR_DELEGATE);
  EXPECT_TRUE(infobar_delegate->GetMessageText().find(
      base::UTF8ToUTF16(std::string(extension_name))));

  //---------------------------------------------------------------------
  // Part of the test that closes the tab, simulating declining the consent.
  //---------------------------------------------------------------------
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::Failure::WINDOW_CLOSED));
  tabs->CloseWebContentsAt(tabs->active_index(), 0);
}

IN_PROC_BROWSER_TEST_F(
    WebAuthFlowBrowserTest,
    InteractivePopupWindowCreatedWithAuthURL_NavigationInURLDoesNotBreakTheFlow) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
  web_auth_flow()->SetShouldShowInfoBar("extension name");

  navigation_observer.Wait();

  //---------------------------------------------------------------------
  // Browser-initiated URL change in the opened tab before completing the auth
  // flow should not trigger an auth flow failure in popup window mode
  // specifically to allow Back/Forward navigation. Other types of URL changes
  // such as new URL input are actually disabled in the Popup window by the UI.
  //---------------------------------------------------------------------
  testing::Mock::VerifyAndClearExpectations(&mock());

  // Keeping a reference to the info bar delegate to check later.
  base::WeakPtr<WebAuthFlowInfoBarDelegate> auth_info_bar =
      web_auth_flow()->GetInfoBarDelegateForTesting();
  ASSERT_TRUE(auth_info_bar);

  Browser* popup_browser = chrome::FindBrowserWithTab(web_contents());
  EXPECT_EQ(popup_browser->type(), Browser::Type::TYPE_POPUP);
  EXPECT_NE(browser(), popup_browser);

  // Simulate an internal navigation, such as an authentication that needs an
  // input of username and password on two different pages/urls.
  GURL new_url = embedded_test_server()->GetURL("/title2.html");
  EXPECT_CALL(mock(), OnAuthFlowURLChange(new_url));
  ASSERT_TRUE(content::NavigateToURL(web_contents(), new_url));

  EXPECT_EQ(web_contents()->GetURL(), new_url);

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // TODO(crbug.com/40272465): Need to disable BackForwardCaching as it
  // causes crashes since the WebContent is initially loaded in an
  // unattached mode.
  content::DisableBackForwardCacheForTesting(
      web_contents(), content::BackForwardCache::DisableForTestingReason::
                          TEST_REQUIRES_NO_CACHING);
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));

  EXPECT_EQ(web_contents()->GetURL(), auth_url);
  // Popup window is still active.
  EXPECT_TRUE(popup_browser);
  EXPECT_EQ(chrome::FindBrowserWithTab(web_contents()), popup_browser);
  // Infobar should not be closed on navigation.
  EXPECT_TRUE(auth_info_bar);
}

IN_PROC_BROWSER_TEST_F(
    WebAuthFlowBrowserTest,
    InteractiveNoBrowser_WebAuthCreatesBrowserWithPopupWindow) {
  Profile* profile = GetProfile();
  // Simulates an extension being opened, in order for the profile not to be
  // added for destruction.
  ScopedProfileKeepAlive profile_keep_alive(
      profile, ProfileKeepAliveOrigin::kBackgroundMode);
  ScopedKeepAlive keep_alive{KeepAliveOrigin::BROWSER,
                             KeepAliveRestartOption::DISABLED};
  CloseBrowserSynchronously(browser());
  ASSERT_FALSE(chrome::FindBrowserWithProfile(profile));

  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE, profile);

  navigation_observer.Wait();

  Browser* new_browser = chrome::FindBrowserWithProfile(profile);
  EXPECT_TRUE(new_browser);
  EXPECT_EQ(new_browser->type(), Browser::Type::TYPE_POPUP);
  EXPECT_EQ(new_browser->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL(),
            auth_url);
}

// This is a regression test for crbug/1445824, makes sure the opened popup
// window does not trigger Session restore.
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                       InteractiveNoBrowser_NotActivatingSessionRestore) {
  Profile* profile = GetProfile();

  // Enable SessionRestore to last used pages.
  SessionStartupPref startup_pref(SessionStartupPref::LAST);
  SessionStartupPref::SetStartupPref(profile, startup_pref);

  // Simulates an extension being opened, with no active browser.
  ScopedProfileKeepAlive profile_keep_alive(
      profile, ProfileKeepAliveOrigin::kBackgroundMode);
  ScopedKeepAlive keep_alive{KeepAliveOrigin::BROWSER,
                             KeepAliveRestartOption::DISABLED};
  CloseBrowserSynchronously(browser());
  ASSERT_FALSE(chrome::FindBrowserWithProfile(profile));

  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE, profile);
  navigation_observer.Wait();

  // Makes sure only one browser is created and profile is not trying to restore
  // previous tabs.
  EXPECT_FALSE(SessionRestore::IsRestoring(profile));
  EXPECT_EQ(chrome::FindAllBrowsersWithProfile(profile).size(), 1u);

  Browser* new_browser = chrome::FindBrowserWithProfile(profile);
  EXPECT_TRUE(new_browser);
  EXPECT_EQ(new_browser->type(), Browser::Type::TYPE_POPUP);
  EXPECT_EQ(new_browser->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL(),
            auth_url);
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, SilentNewTabNotCreated) {
  TabStripModel* tabs = browser()->tab_strip_model();
  int initial_tab_count = tabs->count();

  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(),
              OnAuthFlowFailure(WebAuthFlow::Failure::INTERACTION_REQUIRED));
  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::SILENT);

  navigation_observer.Wait();

  // Tab not created, tab count did not increase.
  EXPECT_EQ(tabs->count(), initial_tab_count);
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                       InteractiveNewTabCreatedWithAuthURL_NoInfoBarByDefault) {
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);

  navigation_observer.Wait();

  Browser* popup_browser = chrome::FindBrowserWithTab(web_contents());
  TabStripModel* tabs = popup_browser->tab_strip_model();
  EXPECT_NE(browser(), popup_browser);
  EXPECT_EQ(tabs->GetActiveWebContents()->GetLastCommittedURL(), auth_url);

  // Check info bar is not created if not set via
  // `SetShouldShowInfoBar())`.
  base::WeakPtr<WebAuthFlowInfoBarDelegate> infobar_delegate =
      web_auth_flow()->GetInfoBarDelegateForTesting();
  EXPECT_FALSE(infobar_delegate);
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                       PopupWindowOpened_ThenCloseWindow) {
  size_t initial_browser_count = chrome::GetTotalBrowserCount();

  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);

  navigation_observer.Wait();

  // New popup window is a browser, browser count should increment by 1.
  EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count + 1);

  // Retrieve the browser used in the WebAuthFlow, the popup window.
  Browser* popup_window_browser = chrome::FindBrowserWithTab(web_contents());
  EXPECT_NE(popup_window_browser, browser());

  TabStripModel* popup_tabs = popup_window_browser->tab_strip_model();
  EXPECT_EQ(popup_tabs->count(), 1);
  EXPECT_EQ(popup_tabs->GetActiveWebContents()->GetLastCommittedURL(),
            auth_url);

  //---------------------------------------------------------------------
  // Closing the browser popup window, simulating declining the consent.
  //---------------------------------------------------------------------
  EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::Failure::WINDOW_CLOSED));
  CloseBrowserSynchronously(popup_window_browser);
}

IN_PROC_BROWSER_TEST_F(
    WebAuthFlowBrowserTest,
    Interactive_MarkedForDeletionProfileNotAllowedToCreatePopupWindow) {
  // Marking active profile for deletion.
  MarkProfileDirectoryForDeletion(GetProfile()->GetPath());

  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");

  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  // Profiles marked for deletion are not allowed to create a popup window and
  // should return an error.
  EXPECT_CALL(mock(),
              OnAuthFlowFailure(WebAuthFlow::Failure::CANNOT_CREATE_WINDOW));
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
  navigation_observer.Wait();
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, PopupWindowOpened_WithBounds) {
  size_t initial_browser_count = chrome::GetTotalBrowserCount();

  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();

  EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
  const gfx::Rect test_bounds(35, 47, 400, 400);
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE, nullptr,
                   WebAuthFlow::AbortOnLoad::kYes, std::nullopt, test_bounds);

  navigation_observer.Wait();

  // New popup window is a browser, browser count should increment by 1.
  EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count + 1);

  // Retrieve the browser used in the WebAuthFlow, the popup window.
  Browser* popup_window_browser = chrome::FindBrowserWithTab(web_contents());
  EXPECT_NE(popup_window_browser, browser());

  gfx::Rect bounds = popup_window_browser->window()->GetBounds();
  EXPECT_EQ(bounds.x(), test_bounds.x());
  EXPECT_EQ(bounds.y(), test_bounds.y());
  // The final width and height can contain platform-specific offsets for the
  // window title bar, which we don't want to assert exactly here.
  EXPECT_GE(bounds.width(), test_bounds.width());
  EXPECT_GE(bounds.height(), test_bounds.height());
}

IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
                       WebContentsDestroyedBeforeProfileShutDown) {
  // Default mock implementation of OnAuthFlowFailure deletes the flow. We do
  // not want that behavior in this test because we want to verify WebAuthFlow's
  // internal state before destruction.
  ON_CALL(mock(), OnAuthFlowFailure)
      .WillByDefault([](WebAuthFlow::Failure failure) {});

  size_t initial_browser_count = chrome::GetTotalBrowserCount();

  // Start a WebAuthFlow that will create a popup window.
  const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
  content::TestNavigationObserver navigation_observer(auth_url);
  navigation_observer.StartWatchingNewWebContents();
  StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
  navigation_observer.Wait();

  // Authentication flow should have created a popup window.
  EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count + 1);
  Browser* popup = chrome::FindBrowserWithTab(web_contents());

  // Simulate profile destruction notification and wait for the auth popup to
  // close.
  static_cast<ProfileObserver*>(web_auth_flow())
      ->OnProfileWillBeDestroyed(GetProfile());
  ui_test_utils::WaitForBrowserToClose(popup);

  // Verify that WebAuthFlow closed the WebContents.
  EXPECT_TRUE(web_auth_flow());
  EXPECT_FALSE(web_auth_flow()->web_contents());
  EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count);
}

}  //  namespace extensions