// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string_view>

#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_manager_browsertest.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/site_instance_process_assignment.h"
#include "content/public/common/content_switches.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/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/features.h"

namespace content {

namespace {

// Helper function for error page navigations that makes sure that the last
// committed origin on |node| is an opaque origin with a precursor that matches
// |url|'s origin.
// Returns true if the frame has an opaque origin with the expected precursor
// information. Otherwise returns false.
bool IsOriginOpaqueAndCompatibleWithURL(FrameTreeNode* node, const GURL& url) {
  url::Origin frame_origin =
      node->current_frame_host()->GetLastCommittedOrigin();

  if (!frame_origin.opaque()) {
    LOG(ERROR) << "Frame origin was not opaque. " << frame_origin;
    return false;
  }

  const GURL url_origin = url.DeprecatedGetOriginAsURL();
  const GURL precursor_origin =
      frame_origin.GetTupleOrPrecursorTupleIfOpaque().GetURL();
  if (url_origin != precursor_origin) {
    LOG(ERROR) << "url_origin '" << url_origin << "' !=  precursor_origin '"
               << precursor_origin << "'";
    return false;
  }
  return true;
}

bool IsMainFrameOriginOpaqueAndCompatibleWithURL(Shell* shell,
                                                 const GURL& url) {
  return IsOriginOpaqueAndCompatibleWithURL(
      static_cast<WebContentsImpl*>(shell->web_contents())
          ->GetPrimaryFrameTree()
          .root(),
      url);
}

bool HasErrorPageSiteInfo(SiteInstance* site_instance) {
  auto* site_instance_impl = static_cast<SiteInstanceImpl*>(site_instance);
  return site_instance_impl->GetSiteInfo().is_error_page();
}

class BrowsingContextGroupSwapObserver : public WebContentsObserver {
 public:
  explicit BrowsingContextGroupSwapObserver(WebContents* web_contents)
      : WebContentsObserver(web_contents) {}

  void DidFinishNavigation(NavigationHandle* navigation_handle) override {
    latest_swap_ = NavigationRequest::From(navigation_handle)
                       ->browsing_context_group_swap();
  }

  BrowsingContextGroupSwap GetLatestBrowsingContextGroupSwap() const {
    EXPECT_TRUE(latest_swap_.has_value());
    return latest_swap_.value();
  }

 private:
  std::optional<BrowsingContextGroupSwap> latest_swap_;
};

}  // namespace

class ProactivelySwapBrowsingInstancesTest : public RenderFrameHostManagerTest {
 public:
  // Enable BFCache so that BrowsingInstance swap will happen.
  ProactivelySwapBrowsingInstancesTest() {
    feature_list_.InitWithFeaturesAndParameters(
        GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

  ~ProactivelySwapBrowsingInstancesTest() override = default;

  void ExpectTotalCount(std::string_view name,
                        base::HistogramBase::Count32 count) {
    FetchHistogramsFromChildProcesses();
    histogram_tester_.ExpectTotalCount(name, count);
  }

  template <typename T>
  void ExpectBucketCount(std::string_view name,
                         T sample,
                         base::HistogramBase::Count32 expected_count) {
    FetchHistogramsFromChildProcesses();
    histogram_tester_.ExpectBucketCount(name, sample, expected_count);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
  base::HistogramTester histogram_tester_;
};

// Test to ensure that the error page navigation does not change
// BrowsingInstances when window.open is present.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesTest,
    ErrorPageNavigationWithWindowOpenDoesNotChangeBrowsingInstance) {
  StartEmbeddedServer();
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  GURL error_url(embedded_test_server()->GetURL("/empty.html"));
  std::unique_ptr<URLLoaderInterceptor> url_interceptor =
      SetupRequestFailForURL(error_url);
  NavigationControllerImpl& nav_controller =
      static_cast<NavigationControllerImpl&>(
          shell()->web_contents()->GetController());

  // Start with a successful navigation to a document and verify there is
  // only one entry in session history.
  EXPECT_TRUE(NavigateToURL(shell(), url));
  scoped_refptr<SiteInstance> success_site_instance =
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
  EXPECT_EQ(1, nav_controller.GetEntryCount());

  // Open a new window to ensure that we can't swap BrowsingInstances
  // as we have to preserve the scripting relationship.
  EXPECT_TRUE(OpenPopup(shell(), GURL(url::kAboutBlankURL), ""));

  // Navigate to an url resulting in an error page and ensure a new entry
  // was added to session history.
  EXPECT_FALSE(NavigateToURL(shell(), error_url));
  EXPECT_EQ(2, nav_controller.GetEntryCount());

  scoped_refptr<SiteInstance> initial_instance =
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
  EXPECT_TRUE(HasErrorPageSiteInfo(initial_instance.get()));
  EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // Reload of the error page that still results in an error should stay in
  // the related SiteInstance. Ensure this works for both browser-initiated
  // reloads and renderer-initiated ones.
  {
    TestNavigationObserver reload_observer(shell()->web_contents());
    shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
    reload_observer.Wait();
    EXPECT_FALSE(reload_observer.last_navigation_succeeded());
    EXPECT_EQ(2, nav_controller.GetEntryCount());
    EXPECT_TRUE(
        IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
        shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  }
  {
    TestNavigationObserver reload_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(shell(), "location.reload();"));
    reload_observer.Wait();
    EXPECT_FALSE(reload_observer.last_navigation_succeeded());
    EXPECT_EQ(2, nav_controller.GetEntryCount());
    EXPECT_TRUE(
        IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
        shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  }

  // Allow the navigation to succeed and ensure the new SiteInstance
  // stays related.
  url_interceptor.reset();
  {
    TestNavigationObserver reload_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(shell(), "location.reload();"));
    reload_observer.Wait();
    EXPECT_TRUE(reload_observer.last_navigation_succeeded());
    EXPECT_EQ(2, nav_controller.GetEntryCount());
    EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
        shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  }
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       ReloadShouldNotChangeBrowsingInstance) {
  StartEmbeddedServer();
  GURL url(embedded_test_server()->GetURL("/title1.html"));

  // 1) Navigate to the page.
  EXPECT_TRUE(NavigateToURL(shell(), url));
  scoped_refptr<SiteInstance> site_instance =
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();

  // 2) Reload page.
  shell()->web_contents()->GetPrimaryMainFrame()->Reload();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // Ensure that we do not change BrowsingInstances for reload.
  // We should keep this even when we start swapping BrowsingInstances
  // for same-site navigations.
  EXPECT_EQ(site_instance,
            shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
}

class ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest
    : public RenderFrameHostManagerTest {
 public:
  ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest() {
    feature_list_.InitWithFeaturesAndParameters(
        GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

  ~ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest() override =
      default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    RenderFrameHostManagerTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kDisableSiteIsolation);
    if (AreAllSitesIsolatedForTesting()) {
      LOG(WARNING)
          << "This test should be run without strict site isolation. "
          << "It's going to fail when  --site-per-process is specified.";
    }
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// When we do a BrowsingInstance swap on renderer-initiated cross-site
// navigation, the current process will not be reused.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    RendererInitiatedCrossSiteNavigationDoesNotReuseProcess) {
  if (AreAllSitesIsolatedForTesting())
    return;
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  scoped_refptr<SiteInstanceImpl> a_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Navigate to B. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that A and B are in different BrowsingInstances and renderer
  // processes. Without full site isolation, A and B are either both default
  // SiteInstances or in the default SiteInstanceGroup of different
  // BrowsingInstances.
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
  if (ShouldUseDefaultSiteInstanceGroup()) {
    EXPECT_EQ(a_site_instance->group(),
              a_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
    EXPECT_EQ(b_site_instance->group(),
              b_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
  } else {
    EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance());
    EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance());
  }
}

// Different from renderer-initiated cross-site navigations, browser-initiated
// cross-site navigations do swap BrowsingInstances and processes without
// ProactivelySwapBrowsingInstance. Because of that, we shouldn't reuse the
// process for the new BrowsingInstance.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    BrowserInitiatedCrossSiteNavigationDoesNotReuseProcess) {
  if (AreAllSitesIsolatedForTesting())
    return;
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  scoped_refptr<SiteInstanceImpl> a_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Navigate to B. The navigation is browser initiated.
  EXPECT_TRUE(NavigateToURL(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that A and B are in different BrowsingInstances and renderer
  // processes. Without full site isolation, A and B are either both default
  // SiteInstances or in the default SiteInstanceGroup of different
  // BrowsingInstances.
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
  if (ShouldUseDefaultSiteInstanceGroup()) {
    EXPECT_EQ(a_site_instance->group(),
              a_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
    EXPECT_EQ(b_site_instance->group(),
              b_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
  } else {
    EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance());
    EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance());
  }
}

// A test ContentBrowserClient implementation that enforce process-per-site mode
// if |should_use_process_per_site_| is true. It is used to verify that we don't
// reuse the current page's renderer process when navigating to sites that uses
// process-per-site.
class ProcessPerSiteContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  ProcessPerSiteContentBrowserClient() = default;

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

  void SetShouldUseProcessPerSite(bool should_use_process_per_site) {
    should_use_process_per_site_ = should_use_process_per_site;
  }

  bool ShouldUseProcessPerSite(
      BrowserContext* browser_context,
      const SecurityPrincipal& security_principal) override {
    return should_use_process_per_site_;
  }

 private:
  bool should_use_process_per_site_ = false;
};

// We should not reuse the current process on renderer-initiated navigations to
// sites that needs to use process-per-site, and should create a new process for
// the site if there isn't already a process for that site.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    RendererInitiatedCrossSiteNavigationToProcessPerSiteURLCreatesNewProcess) {
  if (AreAllSitesIsolatedForTesting())
    return;
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  ProcessPerSiteContentBrowserClient content_browser_client;
  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  scoped_refptr<SiteInstanceImpl> a_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  RenderProcessHost* original_process = a_site_instance->GetProcess();

  // Navigate to B. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that A and B are in different BrowsingInstances and renderer
  // processes.
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  EXPECT_NE(b_site_instance->GetProcess(), original_process);
  if (ShouldUseDefaultSiteInstanceGroup()) {
    EXPECT_EQ(a_site_instance->group(),
              a_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
    EXPECT_EQ(b_site_instance->group(),
              b_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
  } else {
    EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance());
    EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance());
  }

  // Make sure we will use process-per-site for C.
  // Note this is enforcing process-per-site for all sites, which is why we turn
  // it off right after the navigation to C. We might reconsider after
  // crbug.com/1062211 is fixed.
  content_browser_client.SetShouldUseProcessPerSite(true);

  // Navigate to C. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), c_url));
  scoped_refptr<SiteInstanceImpl> c_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that B and C are in different BrowsingInstances and renderer
  // processes.
  EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(c_site_instance.get()));
  if (ShouldUseDefaultSiteInstanceGroup()) {
    EXPECT_EQ(c_site_instance->group(),
              c_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
  } else {
    EXPECT_TRUE(c_site_instance->IsDefaultSiteInstance());
  }
  EXPECT_NE(c_site_instance->GetProcess(), original_process);
  // C is using the process for C's site.
  EXPECT_EQ(c_site_instance->GetProcess(),
            RenderProcessHostImpl::GetSoleProcessHostForSite(
                c_site_instance->GetIsolationContext(),
                c_site_instance->GetSiteInfo()));

  // Make sure we will not use process-per-site for B.
  content_browser_client.SetShouldUseProcessPerSite(false);

  // Navigate to B again. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b2_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_FALSE(b2_site_instance->IsRelatedSiteInstance(c_site_instance.get()));
  EXPECT_FALSE(b2_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  if (ShouldUseDefaultSiteInstanceGroup()) {
    EXPECT_EQ(b2_site_instance->group(),
              b2_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
  } else {
    EXPECT_TRUE(b2_site_instance->IsDefaultSiteInstance());
  }
  EXPECT_NE(b2_site_instance->GetProcess(), original_process);
  // Check that B and C are in different renderer processes.
  EXPECT_NE(b2_site_instance->GetProcess(), c_site_instance->GetProcess());
}

// We should not reuse the current process on renderer-initiated navigations to
// sites that needs to use process-per-site, and should use the sole process for
// that site if it already exists.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    RendererInitiatedCrossSiteNavigationToProcessPerSiteURLUsesProcessForSite) {
  if (AreAllSitesIsolatedForTesting())
    return;
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  ProcessPerSiteContentBrowserClient content_browser_client;

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  scoped_refptr<SiteInstanceImpl> a_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  RenderProcessHost* original_process = a_site_instance->GetProcess();

  // Create a new process and set it as the sole process host for B.
  scoped_refptr<SiteInstanceImpl> placeholder_b_site_instance =
      SiteInstanceImpl::CreateForTesting(web_contents->GetBrowserContext(),
                                         b_url);
  RenderProcessHost* process_for_b =
      RenderProcessHostImpl::CreateRenderProcessHostForTesting(
          web_contents->GetBrowserContext(), placeholder_b_site_instance.get());
  RenderProcessHostImpl::RegisterSoleProcessHostForSite(
      process_for_b, placeholder_b_site_instance.get());
  EXPECT_EQ(process_for_b,
            RenderProcessHostImpl::GetSoleProcessHostForSite(
                placeholder_b_site_instance->GetIsolationContext(),
                placeholder_b_site_instance->GetSiteInfo()));
  // Make sure we will use process-per-site for B.
  content_browser_client.SetShouldUseProcessPerSite(true);

  // Navigate to B. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that A and B are in different BrowsingInstances but B should use the
  // sole process assigned to site B.
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  if (ShouldUseDefaultSiteInstanceGroup()) {
    EXPECT_EQ(a_site_instance->group(),
              a_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
    EXPECT_EQ(b_site_instance->group(),
              b_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
  } else {
    EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance());
    EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance());
  }
  EXPECT_NE(b_site_instance->GetProcess(), original_process);
  EXPECT_EQ(b_site_instance->GetProcess(), process_for_b);
  EXPECT_EQ(b_site_instance->GetProcess(),
            RenderProcessHostImpl::GetSoleProcessHostForSite(
                b_site_instance->GetIsolationContext(),
                b_site_instance->GetSiteInfo()));
}

// We should not reuse the current process on renderer-initiated navigations to
// sites that require a dedicated process.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    NavigationToSiteThatRequiresDedicatedProcess) {
  if (AreAllSitesIsolatedForTesting())
    return;
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // The client will make sure b.com require a dedicated process.
  EffectiveURLContentBrowserTestContentBrowserClient modified_client(
      b_url /* url_to_modify */, b_url, true /* requires_dedicated_process */);
  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  scoped_refptr<SiteInstanceImpl> a_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_FALSE(a_site_instance->RequiresDedicatedProcess());

  // 2) Navigate cross-site to B. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_TRUE(b_site_instance->RequiresDedicatedProcess());

  // Check that A and B are in different BrowsingInstances and processes.
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
}

// We should not reuse the current process on renderer-initiated navigations to
// sites that require a dedicated process.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    NavigationFromSiteThatRequiresDedicatedProcess) {
  if (AreAllSitesIsolatedForTesting())
    return;
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // The client will make sure a.com require a dedicated process.
  EffectiveURLContentBrowserTestContentBrowserClient modified_client(
      a_url /* url_to_modify */, a_url, true /* requires_dedicated_process */);
  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  scoped_refptr<SiteInstanceImpl> a_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_TRUE(a_site_instance->RequiresDedicatedProcess());

  // 2) Navigate cross-site to B. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  scoped_refptr<SiteInstanceImpl> b_site_instance =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_FALSE(b_site_instance->RequiresDedicatedProcess());

  // Check that A and B are in different BrowsingInstances and processes.
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       RendererInitiatedSameSiteNavigationReusesProcess) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Navigate to title2.html. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and title2.html are in different BrowsingInstances
  // but have the same renderer process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
            site_instance_2->GetLastProcessAssignmentOutcome());
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       BrowserInitiatedSameSiteNavigationReusesProcess) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // 2) Navigate to title2.html. The navigation is browser initiated.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and title2.html are in different BrowsingInstances
  // but have the same renderer process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());

  // 3) Do a back navigation to title1.html.
  shell()->web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
  scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We will reuse the SiteInstance and renderer process of |site_instance_1|.
  EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
  EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
            site_instance_1->GetProcess());
}

// Tests that navigations that started but haven't committed yet will be
// overridden by navigations started later if both navigations created
// speculative RFHs.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       MultipleNavigationsStarted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a1_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL a2_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
  GURL b1_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  GURL b2_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to A1.
  EXPECT_TRUE(NavigateToURL(shell(), a1_url));
  auto* a1_rfh = web_contents->GetPrimaryMainFrame();
  FrameTreeNode* node = a1_rfh->frame_tree_node();
  scoped_refptr<SiteInstanceImpl> a1_site_instance =
      static_cast<SiteInstanceImpl*>(a1_rfh->GetSiteInstance());

  // 2) Start same-site navigation to A2 without committing.
  TestNavigationManager navigation_a2(shell()->web_contents(), a2_url);
  shell()->LoadURL(a2_url);
  // Chrome in Android will use unisolated site instance, we have to
  // use `WaitForResponse` since the speculative RFH won't be necessariliy
  // created.
  EXPECT_TRUE(navigation_a2.WaitForResponse());
  // Verify that we're now navigating to |a2_url|.
  EXPECT_EQ(node->navigation_request()->GetURL(), a2_url);
  // We should have a speculative RFH for this navigation.
  RenderFrameHostImpl* a2_speculative_rfh =
      node->render_manager()->speculative_frame_host();
  EXPECT_TRUE(a2_speculative_rfh);
  EXPECT_NE(a1_rfh, a2_speculative_rfh);
  // The speculative RFH should use a different BrowsingInstance than the
  // current RFH.
  scoped_refptr<SiteInstanceImpl> a2_site_instance =
      static_cast<SiteInstanceImpl*>(a2_speculative_rfh->GetSiteInstance());
  EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(a2_site_instance.get()));

  // 3) Start cross-site navigation to B1 without committing.
  TestNavigationManager navigation_b1(shell()->web_contents(), b1_url);
  shell()->LoadURL(b1_url);
  EXPECT_TRUE(navigation_b1.WaitForResponse());
  // Verify that we're now navigating to |b1_url|.
  EXPECT_EQ(node->navigation_request()->GetURL(), b1_url);
  // We should have a speculative RFH for this navigation.
  RenderFrameHostImpl* b1_speculative_rfh =
      node->render_manager()->speculative_frame_host();
  EXPECT_TRUE(b1_speculative_rfh);
  EXPECT_NE(a1_rfh, b1_speculative_rfh);
  // The speculative RFH should use a different BrowsingInstance than the
  // current RFH.
  scoped_refptr<SiteInstanceImpl> b1_site_instance =
      static_cast<SiteInstanceImpl*>(b1_speculative_rfh->GetSiteInstance());
  EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b1_site_instance.get()));

  // 4) Start same-site navigation to B2 without committing.
  TestNavigationManager navigation_b2(shell()->web_contents(), b2_url);
  // B2 shall reuse the speculative RFH. No RFH creation.
  shell()->LoadURL(b2_url);
  // Verify that we're now navigating to |b2_url|.
  EXPECT_EQ(node->navigation_request()->GetURL(), b2_url);
  EXPECT_TRUE(navigation_b2.WaitForResponse());
  // We should have a speculative RFH for this navigation.
  RenderFrameHostImpl* b2_speculative_rfh =
      node->render_manager()->speculative_frame_host();
  EXPECT_TRUE(b2_speculative_rfh);
  EXPECT_NE(a1_rfh, b2_speculative_rfh);
  // The speculative RFH should use a different BrowsingInstance than the
  // current RFH.
  scoped_refptr<SiteInstanceImpl> b2_site_instance =
      static_cast<SiteInstanceImpl*>(b2_speculative_rfh->GetSiteInstance());
  EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b2_site_instance.get()));
}

// Tests history same-site process reuse:
// 1. Visit A1, A2, B.
// 2. Go back to A2 (should use new process).
// 3. Go back to A1 (should reuse A2's process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       HistoryNavigationReusesProcess) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title3.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // 2) Navigate same-site to title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and title2.html are in different BrowsingInstances
  // but have the same renderer process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());

  // 3) Navigate cross-site to b.com/title3.html.
  RenderFrameDeletedObserver rfh_2_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache. So, we should flush back-forward
  // cache.
  web_contents->GetController().GetBackForwardCache().Flush();
  // Wait until the RFH for title2.html got deleted, and check that
  // title2.html and b.com/title3.html are in different BrowsingInstances and
  // renderer processes (We check this by checking whether |site_instance_2|
  // still has a process or not - if it's gone then that means
  // |site_instance_3| uses a different process).
  rfh_2_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
  EXPECT_FALSE(site_instance_2->HasProcess());

  // 4) Do a back navigation to title2.html.
  RenderFrameDeletedObserver rfh_3_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  shell()->web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
  scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache. So, we should flush back-forward
  // cache.
  web_contents->GetController().GetBackForwardCache().Flush();
  // We should use different BrowsingInstances and processes after going back to
  // title2.html because it's a cross-site navigation.
  rfh_3_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_2_history_nav->IsRelatedSiteInstance(
      site_instance_3.get()));
  EXPECT_FALSE(site_instance_3->HasProcess());

  // 5) Do a back navigation to title1.html.
  shell()->web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
  scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should use different BrowsingInstances for title1.html and title2.html,
  // but reuse the process (because in the original navigation, the BI change
  // was caused by proactive BI swap).
  EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
      site_instance_2_history_nav.get()));
  EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
  EXPECT_TRUE(site_instance_2_history_nav->HasProcess());
  EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
            site_instance_2_history_nav->GetProcess());
}

// Tests history same-site process reuse:
// 1. Visit A1, A2, B.
// 2. Go back two entries to A1 (should use new process).
// 3. Go forward to A2 (should reuse A1's process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       HistoryNavigationReusesProcess_SkipSameSiteEntry) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title3.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // 2) Navigate same-site to title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and title2.html are in different BrowsingInstances
  // but have the same renderer process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());

  // 3) Navigate cross-site to b.com/title3.html.
  RenderFrameDeletedObserver rfh_2_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache. So, we should flush back-forward
  // cache.
  web_contents->GetController().GetBackForwardCache().Flush();
  // Wait until the RFH for title2.html got deleted, and check that
  // title2.html and b.com/title3.html are in different BrowsingInstances and
  // renderer processes (We check this by checking whether |site_instance_2|
  // still has a process or not - if it's gone then that means
  // |site_instance_3| uses a different process).
  rfh_2_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
  EXPECT_FALSE(site_instance_2->HasProcess());

  // 4) Navigate back 2 entries to title1.html.
  RenderFrameDeletedObserver rfh_3_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
  scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache. So, we should flush back-forward
  // cache.
  web_contents->GetController().GetBackForwardCache().Flush();
  // We should use different BrowsingInstances and processes after going back to
  // title2.html because it's a cross-site navigation.
  rfh_3_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
      site_instance_3.get()));
  EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
  EXPECT_FALSE(site_instance_3->HasProcess());

  // 5) Navigate 1 entry forward to title2.html.
  EXPECT_TRUE(ExecJs(shell(), "history.go(1)"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
  scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should use different BrowsingInstances for title1.html and title2.html,
  // but reuse the process (because in the original navigation, the BI change
  // was caused by proactive BI swap).
  EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
      site_instance_2_history_nav.get()));
  EXPECT_EQ(site_instance_2_history_nav, site_instance_2);
  EXPECT_TRUE(site_instance_1_history_nav->HasProcess());
  EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
            site_instance_2_history_nav->GetProcess());
}

// Tests history same-site process reuse:
// 1. Visit A1, B, A3.
// 2. Go back two entries to A1 (should use A3's process).
// 3. Go forward to B (should use new process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       HistoryNavigationReusesProcess_SkipCrossSiteEntry) {
  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache.  So, we should disable back-forward
  // cache for this test.
  DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
  GURL url_3(embedded_test_server()->GetURL("/title3.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  RenderFrameDeletedObserver rfh_1_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  // 2) Navigate cross-site to b.com/title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and b.com/title2.html are in different
  // BrowsingInstances and renderer processes (We check this by checking
  // whether |site_instance_1| still has a process or not - if it's gone then
  // that means |site_instance_2| uses a different process).
  rfh_1_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_FALSE(site_instance_1->HasProcess());

  // 3) Navigate cross-site to title3.html.
  RenderFrameDeletedObserver rfh_2_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(NavigateToURL(shell(), url_3));
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Wait until the RFH for title2.html got deleted, and check that
  // b.com/title2.html and title3.html are in different BrowsingInstances and
  // renderer processes (We check this by checking whether |site_instance_2|
  // still has a process or not - if it's gone then that means
  // |site_instance_3| uses a different process).
  rfh_2_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
  EXPECT_FALSE(site_instance_2->HasProcess());

  // 4) Navigate back 2 entries from title3.html to title1.html.
  EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
  scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // We should use different BrowsingInstances but reuse the process when going
  // back from title3.html to title1.html because it's a same-site history
  // navigation.
  EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
      site_instance_3.get()));
  EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
  EXPECT_TRUE(site_instance_3->HasProcess());
  EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
            site_instance_3->GetProcess());
}

// Tests history same-site process reuse:
// 1. Visit A1 (which window.opens A2) then B.
// 2. Visit A3, which should use a new process (can't use A2's process).
// 2. Go back two entries to A1 (should use A2's process - the same process it
// used originally).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       HistoryNavigationReusesProcessThatIsStillAlive) {
  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache.  So, we should disable back-forward
  // cache for this test.
  DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_to_open(embedded_test_server()->GetURL("/empty.html"));
  GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
  GURL url_3(embedded_test_server()->GetURL("/title3.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // 1) Navigate to title1.html and open a popup.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  OpenPopup(shell(), url_to_open, "foo");
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Navigate cross-site to b.com/title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and b.com/title2.html are in different
  // BrowsingInstances and renderer processes. title1.html's process will still
  // be around because the window it opened earlier is still alive.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_TRUE(site_instance_1->HasProcess());
  EXPECT_NE(site_instance_1->GetProcess(), site_instance_2->GetProcess());

  // 3) Navigate cross-site to title3.html (same-site with title1.html).
  RenderFrameDeletedObserver rfh_2_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(NavigateToURL(shell(), url_3));
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Wait until the RFH for b.com/title2.html got deleted, and check that
  // b.com/title2.html and title3.html are in different BrowsingInstances and
  // renderer processes (We check this by checking whether |site_instance_2|
  // still has a process or not - if it's gone then that means
  // |site_instance_3| uses a different process).
  rfh_2_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
  EXPECT_FALSE(site_instance_2->HasProcess());
  // Even though title1.html and title3.html are same-site, they should use
  // different processes.
  EXPECT_NE(site_instance_1->GetProcess(), site_instance_3->GetProcess());

  // 4) Navigate back 2 entries from title3.html to title1.html.
  RenderFrameDeletedObserver rfh_3_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
  scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // We should use different BrowsingInstances and not reuse the process when
  // going back from title3.html to title1.html because the original process
  // for title1.html is still around (also title3.html shouldn't be able to
  // script the window opened by title1.html).
  rfh_3_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
      site_instance_3.get()));
  EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
  EXPECT_FALSE(site_instance_3->HasProcess());
}

// If the navigation is same-document or ends up using the same NavigationEntry
// (e.g., enter in omnibox converted to a reload), we should not do a proactive
// BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       SameEntryAndSameDocumentNavigationDoesNotSwap) {
  ASSERT_TRUE(embedded_test_server()->Start());
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html#foo.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/title1.html#foo")));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Navigate from title1.html#foo to title1.html.
  // This is a same-document, different-entry navigation.
  EXPECT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that #1 and #2 are in the same SiteInstance.
  EXPECT_EQ(site_instance_1, site_instance_2);

  // 3) Navigate from title1.html to title1.html.
  // This is a different-document, same-entry navigation.
  EXPECT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should keep the same SiteInstance again.
  EXPECT_EQ(site_instance_2, site_instance_3);

  // 4) Navigate from title1.html to title1.html#foo.
  // This is a same document navigation.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/title1.html#foo")));
  scoped_refptr<SiteInstanceImpl> site_instance_4 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should keep the same SiteInstance again.
  EXPECT_EQ(site_instance_3, site_instance_4);

  // 5) Navigate from title1.html#foo to title1.html#foo.
  // This is a different-document, same-entry navigation.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/title1.html#foo")));
  scoped_refptr<SiteInstanceImpl> site_instance_5 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should keep the same SiteInstance again.
  EXPECT_EQ(site_instance_4, site_instance_5);

  // 6) Navigate from title1.html#foo to title1.html#bar.
  // This is a same document navigation.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/title1.html#bar")));
  scoped_refptr<SiteInstanceImpl> site_instance_6 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should keep the same SiteInstance again.
  EXPECT_EQ(site_instance_5, site_instance_6);

  // 7) Do a history navigation from title1.html#bar to title1.html#foo.
  // This is a same-document, different-entry history navigation.
  shell()->web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  scoped_refptr<SiteInstanceImpl> site_instance_7 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should keep the same SiteInstance again.
  EXPECT_EQ(site_instance_6, site_instance_7);
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       ReloadDoesNotSwap) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Request a reload to happen when the controller becomes active (e.g.
  // after the renderer gets killed in background on Android).
  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
      shell()->web_contents()->GetController());
  ASSERT_FALSE(controller.NeedsReload());
  controller.SetNeedsReload();
  // Set the restore type to `kRestored`, since `SetNeedsReload()` should only
  // be used for session restore.
  controller.GetLastCommittedEntry()->set_restore_type(RestoreType::kRestored);
  ASSERT_TRUE(controller.NeedsReload());

  // Set the controller as active, triggering the requested reload.
  controller.SetActive(true);
  EXPECT_TRUE(WaitForLoadStop(web_contents));
  ASSERT_FALSE(controller.NeedsReload());
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Check that we're still in the same SiteInstance.
  EXPECT_EQ(site_instance_1, site_instance_2);

  // 3) Trigger reload using Reload().
  {
    TestNavigationObserver reload_observer(shell()->web_contents());
    shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
    reload_observer.Wait();
    EXPECT_TRUE(reload_observer.last_navigation_succeeded());
  }
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Check that we're still in the same SiteInstance.
  EXPECT_EQ(site_instance_2, site_instance_3);

  // 4) Trigger reload using location.reload().
  {
    TestNavigationObserver reload_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(shell(), "location.reload();"));
    reload_observer.Wait();
    EXPECT_TRUE(reload_observer.last_navigation_succeeded());
  }
  scoped_refptr<SiteInstanceImpl> site_instance_4 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Check that we're still in the same SiteInstance.
  EXPECT_EQ(site_instance_3, site_instance_4);

  // 5) Do a replaceState to another URL.
  {
    TestNavigationObserver observer(web_contents);
    std::string script = "history.replaceState({}, '', '/title2.html')";
    EXPECT_TRUE(ExecJs(root, script));
    observer.Wait();
  }
  scoped_refptr<SiteInstanceImpl> site_instance_5 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Check that we're still in the same SiteInstance.
  EXPECT_EQ(site_instance_4, site_instance_5);

  // 6) Reload after a replaceState by simulating the user hitting Enter in the
  // omnibox without changing the URL.
  {
    TestNavigationObserver observer(web_contents);
    web_contents->GetController().LoadURL(web_contents->GetLastCommittedURL(),
                                          Referrer(), ui::PAGE_TRANSITION_LINK,
                                          std::string());
    observer.Wait();
  }
  scoped_refptr<SiteInstanceImpl> site_instance_6 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // Check that we're still in the same SiteInstance.
  EXPECT_EQ(site_instance_5, site_instance_6);
}

// Regression test for crbug.com/340606786. This test ensures that the browser
// doesn't crash if a reload happens on a post-commit error page.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       ReloadPostCommitErrorPage) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
      shell()->web_contents()->GetController());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // 2) Load post commit error page.
  RenderFrameHost* root = shell()->web_contents()->GetPrimaryMainFrame();
  std::string error_html = "Error page";
  TestNavigationObserver error_observer(shell()->web_contents());
  controller.LoadPostCommitErrorPage(root, url, error_html);
  error_observer.Wait();

  // 3) Request a reload to happen when the controller becomes active (e.g.
  // after the renderer gets killed in background on Android).
  ASSERT_FALSE(controller.NeedsReload());
  controller.SetNeedsReload();
  // Set the restore type to `kRestored`, since `SetNeedsReload()` should only
  // be used for session restore.
  controller.GetLastCommittedEntry()->set_restore_type(RestoreType::kRestored);
  ASSERT_TRUE(controller.NeedsReload());

  // Set the controller as active, triggering the requested reload.
  controller.SetActive(true);
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  // The reload should not crash.
  ASSERT_FALSE(controller.NeedsReload());
}

class ProactivelySwapBrowsingInstancesTestWithoutSpeculativeRFHDelay
    : public ProactivelySwapBrowsingInstancesTest {
 public:
  ProactivelySwapBrowsingInstancesTestWithoutSpeculativeRFHDelay() {
    feature_list_for_defer_speculative_rfh_.InitAndEnableFeatureWithParameters(
        features::kDeferSpeculativeRFHCreation,
        {{"create_speculative_rfh_delay_ms", "0"}});
  }

 private:
  base::test::ScopedFeatureList feature_list_for_defer_speculative_rfh_;
};

// The test disables delaying the speculative RFH creation when navigation
// starts since it checks the behavior of speculative RFH during redirection.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesTestWithoutSpeculativeRFHDelay,
    SwapOnNavigationToPageThatRedirects) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  // This is a same-site URL, and will redirect to another same-site URL.
  GURL same_site_redirector_url(
      embedded_test_server()->GetURL("/server-redirect?" + url_2.spec()));
  GURL url_3(embedded_test_server()->GetURL("/title3.html"));
  // This is a cross-site URL, but will redirect to a same-site URL.
  GURL cross_site_redirector_url(embedded_test_server()->GetURL(
      "b.com", "/server-redirect?" + url_3.spec()));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Go to a same-site URL that will redirect us same-site to /title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), same_site_redirector_url,
                            url_2 /* expected_commit_url */));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that we are using a different BrowsingInstance but still using the
  // same renderer process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());

  // 3) Go to a cross-site URL that will redirect us same-site to /title3.html.
  // Note that we're using a renderer-initiated navigation here. If we do a
  // browser-initiated navigation, it will hit the case at crbug.com/1094147
  // where we can't reuse |url_2|'s process even though |url_3| is same-site.
  // TODO(crbug.com/40135315): Test with browser-initiated navigation too once
  // the issue is fixed.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), cross_site_redirector_url,
                                        url_3 /* expected_commit_url */));
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that we are using a different BrowsingInstance but still using the
  // same renderer process.
  // If site isolation is turned off, it will hit the case at crbug.com/1094147.
  EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
  if (AreStrictSiteInstancesEnabled()) {
    EXPECT_EQ(site_instance_2->GetProcess(), site_instance_3->GetProcess());
  } else {
    EXPECT_NE(site_instance_2->GetProcess(), site_instance_3->GetProcess());
  }
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       DoNotSwapWhenReplacingHistoryEntry) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Do a location.replace() to title2.html.
  {
    TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
    EXPECT_TRUE(
        ExecJs(shell(), JsReplace("window.location.replace($1)", url_2)));
    navigation_observer.Wait();
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
  }
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_EQ(site_instance_1, site_instance_2);
}

// When we do a same-document navigation from A to A#foo then a navigation that
// does replacement (e.g., cross-process reload, or location.replace, or other
// client redirects) such that B takes the place of A#foo, we can go back to A
// with the back navigation. In this case, we might want to do a proactive BI
// swap so that page A can be bfcached.
// However, this test is currently disabled because we won't swap on any
// navigation that will replace the current history entry.
// TODO(rakina): Support this case.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesTest,
    DISABLED_ShouldSwapWhenReplacingEntryWithSameDocumentPreviousEntry) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_1_anchor(embedded_test_server()->GetURL("/title1.html#foo"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Navigate same-document to title1.html#foo.
  EXPECT_TRUE(NavigateToURL(shell(), url_1_anchor));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_EQ(site_instance_1, site_instance_2);

  // 3) Do a location.replace() to title2.html.
  {
    TestNavigationObserver navigation_observer(web_contents, 1);
    EXPECT_TRUE(
        ExecJs(shell(), JsReplace("window.location.replace($1)", url_2)));
    navigation_observer.Wait();
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    EXPECT_EQ(web_contents->GetLastCommittedURL(), url_2);
  }
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // We should swap BrowsingInstance here so that the page at url_1 (which is
  // now the previous history entry) can be bfcached.
  EXPECT_NE(site_instance_2, site_instance_3);

  // Assert that a back navigation will go to |url_1|.
  {
    TestNavigationObserver navigation_observer(web_contents);
    web_contents->GetController().GoBack();
    navigation_observer.Wait();
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    EXPECT_EQ(web_contents->GetLastCommittedURL(), url_1);
  }
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       DoNotSwapWhenRelatedContentsPresent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate and open a new window.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  OpenPopup(shell(), url_1, "foo");
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Navigate to title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and title2.html are using the same SiteInstance.
  EXPECT_EQ(site_instance_1, site_instance_2);
}

// We should reuse the current process on same-site navigations even if the
// site requires a dedicated process (because we are still in the same site).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       NavigationToSiteThatRequiresDedicatedProcess) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/title2.html"));

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // Make sure the site require a dedicated process.
  EffectiveURLContentBrowserTestContentBrowserClient modified_client(
      url_1 /* url_to_modify */, url_1, /* requires_dedicated_process */ true);

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_TRUE(site_instance_1->RequiresDedicatedProcess());

  // 2) Navigate cross-site to B. The navigation is document/renderer initiated.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_TRUE(site_instance_2->RequiresDedicatedProcess());

  // Check that A and B are in different BrowsingInstances but reuse the same
  // process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
            site_instance_2->GetLastProcessAssignmentOutcome());
}

// Tests that pagehide handlers of the old RFH are run during the commit
// of the new RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       PagehideRunsDuringCommit) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
  // Create a pagehide handler that sets item "pagehide_storage" in
  // localStorage.
  EXPECT_TRUE(ExecJs(
      main_frame_1,
      base::StringPrintf(R"(
            localStorage.setItem('pagehide_storage', 'not_dispatched');
            var dispatched_pagehide = false;
            window.onpagehide = function(e) {
              if (dispatched_pagehide) {
                // We shouldn't dispatch pagehide more than once.
                localStorage.setItem('pagehide_storage',
                  'dispatched_more_than_once');
              } else if (e.persisted != %s) {
                localStorage.setItem('pagehide_storage', 'wrong_persisted');
              } else {
                localStorage.setItem('pagehide_storage',
                  'dispatched_once');
              }
              dispatched_pagehide = true;
            })",
                         IsBackForwardCacheEnabled() ? "true" : "false")));

  // 2) Navigate to local_storage.html.
  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // Check that title1.html and local_storage.html are in different RFHs.
  RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
  EXPECT_NE(main_frame_1, main_frame_2);

  // Check that the value set by |main_frame_1|'s pagehide handler can be
  // accessed by |main_frame_2| at load time (the first time the new page runs
  // scripts), setting the |pagehide_storage_at_load| variable correctly.
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "pagehide_storage_at_load"));

  // Check that the value for 'pagehide_storage' stays the same after
  // |main_frame_2| finished loading (or |main_frame_1| deleted if bfcache is
  // not enabled).
  if (!IsBackForwardCacheEnabled())
    main_frame_1_deleted_observer.WaitUntilDeleted();
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
}

// Tests that visibilitychange handlers of the old RFH are run during the commit
// of the new RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       VisibilitychangeRunsDuringCommit) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
  // Create a visibilitychange handler that sets item "visibilitychange_storage"
  // localStorage.
  EXPECT_TRUE(ExecJs(main_frame_1, R"(
            localStorage.setItem('visibilitychange_storage', 'not_dispatched');
            var dispatched_visibilitychange = false;
            document.onvisibilitychange = function(e) {
              if (dispatched_visibilitychange) {
                // We shouldn't dispatch visibilitychange more than once.
                localStorage.setItem('visibilitychange_storage',
                  'dispatched_more_than_once');
              } else if (document.visibilityState != 'hidden') {
                // We should dispatch the event when the visibilityState is
                // 'hidden'.
                localStorage.setItem('visibilitychange_storage', 'not_hidden');
              } else {
                localStorage.setItem('visibilitychange_storage',
                  'dispatched_once');
              }
              dispatched_visibilitychange = true;
            })"));

  // 2) Navigate to local_storage.html.
  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // Check that title1.html and local_storage.html are in different RFHs.
  RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
  EXPECT_NE(main_frame_1, main_frame_2);

  // Check that the value set by |main_frame_1|'s pagehide handler can be
  // accessed by |main_frame_2| at load time (the first time the new page runs
  // scripts), setting the |visibilitychange_storage_at_load| variable
  // correctly.
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "visibilitychange_storage_at_load"));

  // Check that the value for 'visibilitychange_storage' stays the same after
  // |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
  // is not enabled).
  if (!IsBackForwardCacheEnabled())
    main_frame_1_deleted_observer.WaitUntilDeleted();
  EXPECT_EQ(
      "dispatched_once",
      EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
}

// Tests that unload handlers of the old RFH are run during commit of the new
// RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap.
// TODO(crbug.com/40142288): support this.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       DISABLED_UnloadRunsDuringCommit) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
  // Create an unload handler that sets item "unload_storage" in localStorage.
  EXPECT_TRUE(ExecJs(main_frame_1, R"(
            localStorage.setItem('unload_storage', 'not_dispatched');
            var dispatched_unload = false;
            window.onunload = function(e) {
              if (dispatched_unload) {
                // We shouldn't dispatch unload more than once.
                localStorage.setItem('unload_storage',
                  'dispatched_more_than_once');
              } else {
                localStorage.setItem('unload_storage', 'dispatched_once');
              }
              dispatched_unload = true;
            };)"));

  // 2) Navigate to local_storage.html.
  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // Check that title1.html and local_storage.html are in different RFHs.
  RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
  EXPECT_NE(main_frame_1, main_frame_2);

  // Check that the value set by |main_frame_1|'s unload handler can be
  // accessed by |main_frame_2| at load time (the first time the new page runs
  // scripts), setting the |unload_storage_at_load| variable correctly.
  EXPECT_EQ("dispatched_once", EvalJs(main_frame_2, "unload_storage_at_load"));

  // Check that the value for 'unload_storage' stays the same after
  // |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
  // is not enabled).
  if (!IsBackForwardCacheEnabled())
    main_frame_1_deleted_observer.WaitUntilDeleted();
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "localStorage.getItem('unload_storage')"));
}

// Tests that pagehide and visibilitychange handlers of a subframe in the old
// page are run during the commit of a new main RFH when swapping RFH for
// same-site navigations due to proactive BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesTest,
    PagehideAndVisibilitychangeInSubframesAreRunDuringCommit) {
  if (IsBackForwardCacheEnabled()) {
    // bfcached subframes with unload/pagehide/visibilitychange handlers will
    // crash on a failed DCHECK due to crbug.com/1109742.
    // TODO(crbug.com/40141877): don't skip this test when bfcache is enabled.
    return;
  }
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a.com(a.com)"));
  GURL child_url = embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a.com()");
  GURL url_2(embedded_test_server()->GetURL("a.com", "/local_storage.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to |main_url|.
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
  FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  // Check if the subframe is navigated to the correct URL.
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(child_url, child->current_url());

  // Create a pagehide handler that sets item "pagehide_storage" and a
  // visibilitychange handler that sets item "visibilitychange_storage" in
  // localStorage in the subframe.
  EXPECT_TRUE(ExecJs(
      child,
      base::StringPrintf(R"(
          localStorage.setItem('pagehide_storage', 'not_dispatched');
          var dispatched_pagehide = false;
          window.onpagehide = function(e) {
            if (dispatched_pagehide) {
              // We shouldn't dispatch pagehide more than once.
              localStorage.setItem('pagehide_storage',
                'dispatched_more_than_once');
            } else if (e.persisted != %s) {
              localStorage.setItem('pagehide_storage', 'wrong_persisted');
            } else {
              localStorage.setItem('pagehide_storage', 'dispatched_once');
            }
            dispatched_pagehide = true;
          }

          localStorage.setItem('visibilitychange_storage', 'not_dispatched');
          var dispatched_visibilitychange = false;
          document.onvisibilitychange = function(e) {
            if (dispatched_visibilitychange) {
              // We shouldn't dispatch visibilitychange more than once.
              localStorage.setItem('visibilitychange_storage',
                'dispatched_more_than_once');
            } else if (document.visibilityState != 'hidden') {
              // We should dispatch the event when the visibilityState is
              // 'hidden'.
              localStorage.setItem('visibilitychange_storage', 'not_hidden');
            } else {
              localStorage.setItem('visibilitychange_storage',
                'dispatched_once');
            }
            dispatched_visibilitychange = true;
          })",
                         IsBackForwardCacheEnabled() ? "true" : "false")));
  // 2) Navigate to local_storage.html.
  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // Check that |main_url| and local_storage.html are in different RFHs.
  RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
  EXPECT_NE(main_frame_1, main_frame_2);

  // Check that the value set by |child|'s pagehide and visibilitychange
  // handlers can be accessed by |main_frame_2| at load time (the first time the
  // new page runs scripts), setting the |pagehide_storage_at_load| and
  // |visibilitychange_storage_at_load| variable correctly.
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "pagehide_storage_at_load"));
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "visibilitychange_storage_at_load"));

  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
  // stays the same after |main_frame_2| finished loading (or |main_frame_1| got
  // deleted, if bfcache is not enabled).
  if (!IsBackForwardCacheEnabled())
    main_frame_1_deleted_observer.WaitUntilDeleted();
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
  EXPECT_EQ(
      "dispatched_once",
      EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
}

// Tests that pagehide handlers of the old RFH are run during the commit
// of the new RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap even if the page is already hidden (and
// visibilitychange won't run).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesTest,
                       PagehideRunsDuringCommitOfHiddenPage) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to |url_1| and hide the tab.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
  // We need to set it to Visibility::VISIBLE first in case this is the first
  // time the visibility is updated.
  web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
  web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
  EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());

  // Create a pagehide handler that sets item "pagehide_storage" and a
  // visibilitychange handler that sets item "visibilitychange_storage" in
  // localStorage.
  EXPECT_TRUE(ExecJs(
      main_frame_1,
      base::StringPrintf(R"(
          localStorage.setItem('pagehide_storage', 'not_dispatched');
          var dispatched_pagehide = false;
          window.onpagehide = function(e) {
            if (dispatched_pagehide) {
              // We shouldn't dispatch pagehide more than once.
              localStorage.setItem('pagehide_storage',
                'dispatched_more_than_once');
            } else if (e.persisted != %s) {
              localStorage.setItem('pagehide_storage', 'wrong_persisted');
            } else {
              localStorage.setItem('pagehide_storage', 'dispatched_once');
            }
            dispatched_pagehide = true;
          }

          localStorage.setItem('visibilitychange_storage', 'not_dispatched');
          document.onvisibilitychange = function(e) {
            localStorage.setItem('visibilitychange_storage',
                'should_not_be_dispatched');
          })",
                         IsBackForwardCacheEnabled() ? "true" : "false")));
  // |visibilitychange_storage| should be set to its initial correct value.
  EXPECT_EQ(
      "not_dispatched",
      EvalJs(main_frame_1, "localStorage.getItem('visibilitychange_storage')"));

  // 2) Navigate to local_storage.html.
  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // Check that |url_1| and local_storage.html are in different RFHs.
  RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
  EXPECT_NE(main_frame_1, main_frame_2);

  // Check that the value set by |main_frame_1|'s pagehide handler can be
  // accessed by |main_frame_2| at load time (the first time the new page runs
  // scripts), setting the |pagehide_storage_at_load| and variable correctly.
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "pagehide_storage_at_load"));
  // |visibilitychange_storage_at_load| should not be modified.
  EXPECT_EQ("not_dispatched",
            EvalJs(main_frame_2, "visibilitychange_storage_at_load"));

  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
  // stays the same after |main_frame_2| finished loading (or |main_frame_1| got
  // deleted, if bfcache is not enabled).
  if (!IsBackForwardCacheEnabled())
    main_frame_1_deleted_observer.WaitUntilDeleted();
  EXPECT_EQ("dispatched_once",
            EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
  EXPECT_EQ(
      "not_dispatched",
      EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
}

// To address breakage of named window reuse across a back navigation
// when a proactive BrowsingInstance swap has happened, we offer the
// option to use an explicit opener relation for same-window navigations
// as an opt-out from proactive swaps. These tests cover cases that should (and
// should not) have the opt-out mechanism take effect.
class ProactivelySwapBrowsingInstancesOptOutTest
    : public ProactivelySwapBrowsingInstancesTest {
 public:
  ProactivelySwapBrowsingInstancesOptOutTest() {
    feature_list_.InitAndEnableFeature(
        blink::features::kRelOpenerBcgDependencyHint);
  }

  ~ProactivelySwapBrowsingInstancesOptOutTest() override = default;

 protected:
  void RunOptOutTest(base::FunctionRef<void()> perform_navigation,
                     bool expect_opt_out_applies = true) {
    ASSERT_TRUE(CanSameSiteMainFrameNavigationsChangeSiteInstances());

    ASSERT_TRUE(embedded_test_server()->Start());
    const GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());

    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetPrimaryMainFrame()->GetSiteInstance());

    BrowsingContextGroupSwapObserver swap_observer(web_contents);

    perform_navigation();
    EXPECT_NE(url_1,
              web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());

    BrowsingContextGroupSwap bcg_swap =
        swap_observer.GetLatestBrowsingContextGroupSwap();

    EXPECT_NE(expect_opt_out_applies, bcg_swap.ShouldSwap());

    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetPrimaryMainFrame()->GetSiteInstance());
    if (expect_opt_out_applies) {
      EXPECT_TRUE(
          site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
      EXPECT_EQ(
          bcg_swap.reason(),
          ShouldSwapBrowsingInstance::kNo_InitiatorRequestedNoProactiveSwap);
    } else {
      EXPECT_FALSE(
          site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    }
  }

  void CreateAnchorAndNavigate(RenderFrameHost* rfh,
                               const GURL& url,
                               const std::string& target_name,
                               const std::string& rel) {
    TestNavigationObserver nav_observer(WebContents::FromRenderFrameHost(rfh));
    ExecuteScriptAsync(rfh, JsReplace(R"(
      let anchor = document.createElement('a');
      anchor.href = $1;
      anchor.target = $2;
      anchor.rel = $3;
      anchor.text = 'Link';
      document.body.appendChild(anchor);
      anchor.click();
    )",
                                      url, target_name, rel));
    nav_observer.Wait();
  }

  void WindowOpenNavigate(RenderFrameHost* rfh,
                          const GURL& url,
                          const std::string& target_name,
                          const std::string& window_features) {
    TestNavigationObserver nav_observer(WebContents::FromRenderFrameHost(rfh));
    ExecuteScriptAsync(rfh, JsReplace("window.open($1, $2, $3);", url,
                                      target_name, window_features));
    nav_observer.Wait();
  }

  void CreateFormAndSubmit(RenderFrameHost* rfh,
                           const GURL& action,
                           const std::string& target_name,
                           const std::string& rel) {
    TestNavigationObserver nav_observer(WebContents::FromRenderFrameHost(rfh));
    ExecuteScriptAsync(rfh, JsReplace(R"(
      let form = document.createElement('form');
      form.action = $1;
      form.target = $2;
      form.rel = $3;
      form.method = 'POST';
      document.body.appendChild(form);
      form.submit();
    )",
                                      action, target_name, rel));
    nav_observer.Wait();
  }

  FrameTreeNode* CreateIframe(RenderFrameHost* parent, const GURL& url) {
    RenderFrameHostImpl* parent_impl =
        static_cast<RenderFrameHostImpl*>(parent);
    EXPECT_TRUE(ExecJs(parent_impl,
                       "var child = document.createElement('iframe');"
                       "document.body.appendChild(child);"));

    FrameTreeNode* child =
        parent_impl->child_at(parent_impl->child_count() - 1);
    EXPECT_TRUE(child);

    TestFrameNavigationObserver observer(child);
    EXPECT_TRUE(ExecJs(parent_impl, JsReplace("child.src = $1;", url)));
    observer.Wait();

    return child;
  }

  test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_test_helper_;
  }

 private:
  test::FencedFrameTestHelper fenced_frame_test_helper_;
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithAnchorRelOpener) {
  RunOptOutTest([this]() {
    const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
    CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                            next_url,
                            /*target_name=*/"", /*rel=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithWindowOpener) {
  RunOptOutTest([this]() {
    const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
    WindowOpenNavigate(shell()->web_contents()->GetPrimaryMainFrame(), next_url,
                       /*target_name=*/"_self", /*window_features=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithFormRelOpener) {
  RunOptOutTest([this]() {
    const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
    CreateFormAndSubmit(shell()->web_contents()->GetPrimaryMainFrame(),
                        /*action=*/next_url,
                        /*target_name=*/"", /*rel=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       PreventingSwapAllowsFutureScripting) {
  RunOptOutTest([this]() {
    const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
    CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                            next_url,
                            /*target_name=*/"", /*rel=*/"opener");
  });

  // Beyond confirming the BrowsingInstance didn't change, we'll explicitly
  // check the behaviour seen in crbug.com/40281878 to ensure that named windows
  // can be reused across back navigations.
  const GURL popup_url(embedded_test_server()->GetURL("/title3.html"));
  ShellAddedObserver new_shell_observer;
  EXPECT_TRUE(
      ExecJs(shell(), JsReplace("var w = window.open($1, 'namedWindow');"
                                "w.previouslyOpened = true;",
                                popup_url)));
  Shell* popup = new_shell_observer.GetShell();
  EXPECT_TRUE(WaitForLoadStop(popup->web_contents()));

  NavigationController& opener_controller =
      shell()->web_contents()->GetController();
  ASSERT_TRUE(opener_controller.CanGoBack());
  TestNavigationObserver back_observer(shell()->web_contents());
  opener_controller.GoBack();
  back_observer.Wait();

  EXPECT_EQ(true, EvalJs(shell(),
                         "!!window.open('', 'namedWindow').previouslyOpened;"));
  // The window opened previously should be reused here, so the navigation
  // should be in the same WebContents.
  const GURL next_popup_url(embedded_test_server()->GetURL("/title4.html"));
  TestNavigationObserver popup_nav_observer(popup->web_contents());
  EXPECT_TRUE(ExecJs(
      shell(), JsReplace("window.open($1, 'namedWindow');", next_popup_url)));
  popup_nav_observer.Wait();
  EXPECT_EQ(popup_nav_observer.last_navigation_url(), next_popup_url);
}

// Like PreventingSwapAllowsFutureScripting, but performs another navigation
// after the back navigation, to confirm that the next page can also reuse the
// opened window.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       PreventingSwapAllowsFutureScriptingAfterAdditionalNav) {
  RunOptOutTest([this]() {
    const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
    CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                            next_url,
                            /*target_name=*/"", /*rel=*/"opener");
  });

  // Beyond confirming the BrowsingInstance didn't change, we'll explicitly
  // check the behaviour seen in crbug.com/40281878 to ensure that named windows
  // can be reused across back navigations.
  const GURL popup_url(embedded_test_server()->GetURL("/title3.html"));
  ShellAddedObserver new_shell_observer;
  EXPECT_TRUE(
      ExecJs(shell(), JsReplace("var w = window.open($1, 'namedWindow');"
                                "w.previouslyOpened = true;",
                                popup_url)));
  Shell* popup = new_shell_observer.GetShell();
  EXPECT_TRUE(WaitForLoadStop(popup->web_contents()));

  NavigationController& opener_controller =
      shell()->web_contents()->GetController();
  ASSERT_TRUE(opener_controller.CanGoBack());
  TestNavigationObserver back_observer(shell()->web_contents());
  opener_controller.GoBack();
  back_observer.Wait();

  // Navigate to another page. That page should also be able to reuse the opened
  // window.
  const GURL next_url2(
      embedded_test_server()->GetURL("/title2.html?additionalnav"));
  // We intentionally don't use rel=opener here as the usage in the original nav
  // to title2.html is sufficient.
  CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                          next_url2,
                          /*target_name=*/"", /*rel=*/"");

  EXPECT_EQ(true, EvalJs(shell(),
                         "!!window.open('', 'namedWindow').previouslyOpened;"));
  // The window opened previously should be reused here, so the navigation
  // should be in the same WebContents.
  const GURL next_popup_url(embedded_test_server()->GetURL("/title4.html"));
  TestNavigationObserver popup_nav_observer(popup->web_contents());
  EXPECT_TRUE(ExecJs(
      shell(), JsReplace("window.open($1, 'namedWindow');", next_popup_url)));
  popup_nav_observer.Wait();
  EXPECT_EQ(popup_nav_observer.last_navigation_url(), next_popup_url);
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithAnchorRelOpenerCrossSite) {
  RunOptOutTest([this]() {
    const GURL cross_site_next_url(
        embedded_test_server()->GetURL("b.com", "/title2.html"));
    CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                            cross_site_next_url,
                            /*target_name=*/"", /*rel=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithAnchorRelOpenerWithParent) {
  RunOptOutTest([this]() {
    const GURL iframe_url(embedded_test_server()->GetURL("/title2.html"));
    const GURL next_url(embedded_test_server()->GetURL("/title3.html"));
    FrameTreeNode* child = CreateIframe(
        shell()->web_contents()->GetPrimaryMainFrame(), iframe_url);
    CreateAnchorAndNavigate(child->current_frame_host(), next_url,
                            /*target_name=*/"_top", /*rel=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithAnchorRelOpenerInOopif) {
  RunOptOutTest([this]() {
    const GURL cross_site_iframe_url(
        embedded_test_server()->GetURL("b.com", "/title2.html"));
    const GURL next_url(embedded_test_server()->GetURL("/title3.html"));
    FrameTreeNode* child = CreateIframe(
        shell()->web_contents()->GetPrimaryMainFrame(), cross_site_iframe_url);
    CreateAnchorAndNavigate(child->current_frame_host(), next_url,
                            /*target_name=*/"_top", /*rel=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       DoNotSwapWithAnchorRelOpenerOpenURL) {
  RunOptOutTest([this]() {
    // This has the renderer navigate though the OpenURL method.
    shell()
        ->web_contents()
        ->GetMutableRendererPrefs()
        ->browser_handles_all_top_level_requests = true;
    shell()->web_contents()->SyncRendererPrefs();
    const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
    CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                            next_url,
                            /*target_name=*/"", /*rel=*/"opener");
  });
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       CannotOptOutOfCoop) {
  // Browsing instance swaps required by COOP cannot be bypassed by the opt out.
  RunOptOutTest(
      [this]() {
        const GURL next_url(
            embedded_test_server()->GetURL("/cross-origin-isolated.html"));
        CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                                next_url,
                                /*target_name=*/"", /*rel=*/"opener");
      },
      /*expect_opt_out_applies=*/false);
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       CannotOptOutFromFencedFrame) {
  // _unfencedTop navigations force a browsing instance swap, which should take
  // priority over the requested opt out.
  RunOptOutTest(
      [this]() {
        const GURL fenced_frame_url(
            embedded_test_server()->GetURL("/fenced_frames/title0.html"));
        const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
        RenderFrameHost* ff_rfh = fenced_frame_test_helper().CreateFencedFrame(
            shell()->web_contents()->GetPrimaryMainFrame(), fenced_frame_url,
            net::OK, blink::FencedFrame::DeprecatedFencedFrameMode::kOpaqueAds);
        CreateAnchorAndNavigate(ff_rfh, next_url,
                                /*target_name=*/"_unfencedTop",
                                /*rel=*/"opener");
      },
      /*expect_opt_out_applies=*/false);
}

IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesOptOutTest,
                       RelOpenerAndNoopener) {
  // Contradicting rel types of both opener and noopener should be treated as
  // noopener.
  RunOptOutTest(
      [this]() {
        const GURL next_url(embedded_test_server()->GetURL("/title2.html"));
        CreateAnchorAndNavigate(shell()->web_contents()->GetPrimaryMainFrame(),
                                next_url,
                                /*target_name=*/"", /*rel=*/"noopener opener");
      },
      /*expect_opt_out_applies=*/false);
}

class ProactivelySwapBrowsingInstancesSameSiteCoopTest
    : public ProactivelySwapBrowsingInstancesTest {
 public:
  ProactivelySwapBrowsingInstancesSameSiteCoopTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    std::vector<base::test::FeatureRef> features;
    feature_list_.InitAndEnableFeature(
        network::features::kCrossOriginOpenerPolicy);
  }

  ~ProactivelySwapBrowsingInstancesSameSiteCoopTest() override = default;

  net::EmbeddedTestServer* https_server() { return &https_server_; }

 private:
  void SetUpOnMainThread() override {
    ProactivelySwapBrowsingInstancesTest::SetUpOnMainThread();
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    host_resolver()->AddRule("*", "127.0.0.1");
    ASSERT_TRUE(embedded_test_server()->Start());
    https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
    SetupCrossSiteRedirector(https_server());
    net::test_server::RegisterDefaultHandlers(&https_server_);
    ASSERT_TRUE(https_server()->Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ProactivelySwapBrowsingInstancesTest::SetUpCommandLine(command_line);
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpInProcessBrowserTestFixture() override {
    ProactivelySwapBrowsingInstancesTest::SetUpInProcessBrowserTestFixture();
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    ProactivelySwapBrowsingInstancesTest::TearDownInProcessBrowserTestFixture();
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer https_server_;
  content::ContentMockCertVerifier mock_cert_verifier_;
};

// Tests history same-site process reuse:
// 1. Visit A1 (non-COOP), A2 (non-COOP, should reuse A1's process), A3 (uses
// COOP + COEP, should use new process).
// 2. Go back to A2 (should use new process).
// 3. Go back to A1 (should reuse A2's process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteCoopTest,
                       HistoryNavigationReusesProcess_COOP) {

  GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
  GURL url_2(https_server()->GetURL("a.com", "/title2.html"));
  GURL coop_url(
      https_server()->GetURL("a.com",
                             "/set-header?"
                             "Cross-Origin-Opener-Policy: same-origin&"
                             "Cross-Origin-Embedder-Policy: require-corp"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // 1) Navigate to title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  EXPECT_EQ(
      web_contents->GetPrimaryMainFrame()->cross_origin_opener_policy().value,
      network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // 2) Navigate same-site to title2.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  EXPECT_EQ(
      web_contents->GetPrimaryMainFrame()->cross_origin_opener_policy().value,
      network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
  scoped_refptr<SiteInstanceImpl> site_instance_2 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Check that title1.html and title2.html are in different BrowsingInstances
  // but have the same renderer process.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
  EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());

  // 3) Navigate same-site to a crossOriginIsolated page (uses COOP+COEP).
  RenderFrameDeletedObserver rfh_2_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  EXPECT_TRUE(NavigateToURL(shell(), coop_url));
  EXPECT_EQ(
      web_contents->GetPrimaryMainFrame()->cross_origin_opener_policy().value,
      network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep);

  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache. So, we should flush back-forward
  // cache.
  web_contents->GetController().GetBackForwardCache().Flush();
  // Wait until the RFH for title2.html got deleted, and check that
  // title2.html and title3.html are in different BrowsingInstances and
  // renderer processes (We check this by checking whether |site_instance_2|
  // still has a process or not - if it's gone then that means
  // |site_instance_3| uses a different process).
  rfh_2_deleted_observer.WaitUntilDeleted();
  scoped_refptr<SiteInstanceImpl> site_instance_3 =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
  EXPECT_FALSE(site_instance_2->HasProcess());
  EXPECT_TRUE(site_instance_3->GetProcess());

  // 4) Do a back navigation to title2.html.
  RenderFrameDeletedObserver rfh_3_deleted_observer(
      web_contents->GetPrimaryMainFrame());
  shell()->web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
  scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());
  // This test expects a renderer process to eventually get deleted when we
  // navigate away from the page using it, which won't happen if the page is
  // kept alive in the back-forward cache. So, we should flush back-forward
  // cache.
  web_contents->GetController().GetBackForwardCache().Flush();
  // We should use different BrowsingInstances and processes after going back to
  // title2.html because it's transitioning from a crossOriginIsolated page
  // (COOP+COEP) to a non-crossOriginIsolated page, even though the two are
  // same-site.
  rfh_3_deleted_observer.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_2_history_nav->IsRelatedSiteInstance(
      site_instance_3.get()));
  EXPECT_FALSE(site_instance_3->HasProcess());

  // 5) Do a back navigation to title1.html.
  shell()->web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
  scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
      static_cast<SiteInstanceImpl*>(
          web_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // We should use different BrowsingInstances for title1.html and title2.html,
  // but reuse the process (because in the original navigation, the BI change
  // was caused by proactive BI swap).
  EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
      site_instance_2_history_nav.get()));
  EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
  EXPECT_TRUE(site_instance_2_history_nav->HasProcess());
  EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
            site_instance_2_history_nav->GetProcess());
}

// Tests that enable clearing window.name on on cross-site
// cross-BrowsingInstance navigations when
// ProactivelySwapBrowsingInstancesSameSite is enabled.
class ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest
    : public ProactivelySwapBrowsingInstancesTest {
 public:
  ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest() {
    feature_list_.InitAndEnableFeature(
        features::kClearCrossSiteCrossBrowsingContextGroupWindowName);
  }
  ~ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest() override =
      default;

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Verify that same-site main frame navigation that swaps BrowsingInstances
// does not clear window.name.
IN_PROC_BROWSER_TEST_P(
    ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest,
    NotClearWindowNameSameSite) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Navigate to a.com/title1.html.
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
  // Set window.name.
  EXPECT_TRUE(content::ExecJs(web_contents, "window.name='foo'"));
  auto* frame_a1 = web_contents->GetPrimaryMainFrame();
  EXPECT_EQ("foo", frame_a1->GetFrameName());

  scoped_refptr<SiteInstance> site_instance_a1 = frame_a1->GetSiteInstance();

  // Navigate to a.com/title2.html. Even though we proactively swap
  // BrowsingInstances for same-site navigation as well, we should only clear
  // window.name for cross-BrowsingInstance navigation that's not same-site.
  // https://html.spec.whatwg.org/multipage/browsing-the-web.html#resetBCName.
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_a2));
  auto* frame_a2 = web_contents->GetPrimaryMainFrame();
  // Check that title1.html and title2.html are in different BrowsingInstances.
  scoped_refptr<SiteInstance> site_instance_a2 = frame_a2->GetSiteInstance();
  EXPECT_FALSE(site_instance_a1->IsRelatedSiteInstance(site_instance_a2.get()));
  // Window.name should not be cleared.
  EXPECT_EQ("foo", frame_a2->GetFrameName());
}

INSTANTIATE_TEST_SUITE_P(All,
                         ProactivelySwapBrowsingInstancesTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         ProactivelySwapBrowsingInstancesOptOutTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
    All,
    ProactivelySwapBrowsingInstancesCrossSiteDoesNotReuseProcessTest,
    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         ProactivelySwapBrowsingInstancesSameSiteCoopTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
    All,
    ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest,
    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
    All,
    ProactivelySwapBrowsingInstancesTestWithoutSpeculativeRFHDelay,
    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
}  // namespace content
