910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/network/public/cpp/document_isolation_policy.h"

#include "base/command_line.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/with_feature_override.h"
#include "build/build_config.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.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_isolation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.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/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.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/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/network/public/cpp/cross_origin_embedder_policy.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"

using ::testing::HasSubstr;

namespace content {

namespace {

network::DocumentIsolationPolicy DipIsolateAndRequireCorp() {
  network::DocumentIsolationPolicy dip;
  dip.value =
      network::mojom::DocumentIsolationPolicyValue::kIsolateAndRequireCorp;
  return dip;
}

network::DocumentIsolationPolicy DipIsolateAndCredentialless() {
  network::DocumentIsolationPolicy dip;
  dip.value =
      network::mojom::DocumentIsolationPolicyValue::kIsolateAndCredentialless;
  return dip;
}

network::DocumentIsolationPolicy DipNone() {
  return network::DocumentIsolationPolicy();
}

std::unique_ptr<net::test_server::HttpResponse> ServeDipOnSecondNavigation(
    unsigned int& navigation_counter,
    const net::test_server::HttpRequest& request) {
  ++navigation_counter;
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HttpStatusCode::HTTP_OK);
  http_response->AddCustomHeader("Cache-Control", "no-store, must-revalidate");
  if (navigation_counter > 1) {
    http_response->AddCustomHeader("Document-Isolation-Policy",
                                   "isolate-and-require-corp");
  }
  return http_response;
}

class DocumentIsolationPolicyBrowserTest
    : public ContentBrowserTest,
      public ::testing::WithParamInterface<
          std::tuple<std::string, bool, bool>> {
 public:
  DocumentIsolationPolicyBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &DocumentIsolationPolicyBrowserTest::prerender_web_contents,
            base::Unretained(this))),
        https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    // Enable DIP and disable speculative RFH creation deferral. Currently,
    // speculative RFH creation deferral makes it impossible to check that a
    // speculative RFH is not created at the start of the navigation. This is
    // needed to check that we do not create an extra process when navigating
    // between two same-origin documents with DIP. Once RenderDocument ships, a
    // speculative RFH will always be created, we'll then check that it is in
    // the same SiteInstance as the current one. Then, we'll be able to
    // re-enable the deferred speculative RFH creation, and just wait for the
    // deferred creation of the speculative RenderDocument.
    feature_list_.InitWithFeatures(
        {network::features::kDocumentIsolationPolicy},
        {features::kDeferSpeculativeRFHCreation, features::kSharedArrayBuffer});

    // Enable RenderDocument:
    InitAndEnableRenderDocumentFeature(&feature_list_for_render_document_,
                                       std::get<0>(GetParam()));
    // Enable BackForwardCache:
    if (IsBackForwardCacheEnabled()) {
      feature_list_for_back_forward_cache_.InitWithFeaturesAndParameters(
          GetDefaultEnabledBackForwardCacheFeaturesForTesting(
              /*ignore_outstanding_network_request=*/false),
          GetDefaultDisabledBackForwardCacheFeaturesForTesting());
    } else {
      feature_list_for_back_forward_cache_.InitWithFeatures(
          {}, {features::kBackForwardCache});
    }
  }

  // Provides meaningful param names instead of /0, /1, ...
  static std::string DescribeParams(
      const testing::TestParamInfo<ParamType>& info) {
    auto [render_document_level, enable_back_forward_cache, require_corp] =
        info.param;
    return base::StringPrintf(
        "%s_%s_%s",
        GetRenderDocumentLevelNameForTestParams(render_document_level).c_str(),
        enable_back_forward_cache ? "BFCacheEnabled" : "BFCacheDisabled",
        require_corp ? "RequireCorp" : "Credentialless");
  }

  bool IsBackForwardCacheEnabled() { return std::get<1>(GetParam()); }

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

  test::PrerenderTestHelper& prerender_helper() { return prerender_helper_; }

 protected:
  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  RenderFrameHostImpl* current_frame_host() {
    return web_contents()->GetPrimaryMainFrame();
  }

  void SetUpOnMainThread() override {
    ContentBrowserTest::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_);
    AddRedirectOnSecondNavigationHandler(&https_server_);
    unsigned int navigation_counter = 0;
    https_server_.RegisterDefaultHandler(base::BindRepeating(
        &net::test_server::HandlePrefixedRequest,
        "/serve-dip-on-second-navigation",
        base::BindRepeating(&ServeDipOnSecondNavigation,
                            base::OwnedRef(navigation_counter))));

    prerender_helper().RegisterServerRequestMonitor(&https_server_);

    ASSERT_TRUE(https_server()->Start());
  }

  GURL GetDocumentIsolationPolicyURL(
      const std::string& host,
      const std::optional<std::string>& additional_header = std::nullopt) {
    std::string headers = "/set-header?";

    if (std::get<2>(GetParam())) {
      // Isolate-and-require-corp version of the test.
      headers += "document-isolation-policy: isolate-and-require-corp";
    } else {
      // Isolate-and-credentialless version of the test.
      headers += "document-isolation-policy: isolate-and-credentialless";
    }

    if (additional_header.has_value()) {
      headers += "&" + additional_header.value();
    }
    return https_server()->GetURL(host, headers);
  }

  network::DocumentIsolationPolicy GetDocumentIsolationPolicy() {
    // Isolate-and-require-corp version of the test.
    if (std::get<2>(GetParam())) {
      return DipIsolateAndRequireCorp();
    }

    // Isolate-and-credentialless version of the test.
    return DipIsolateAndCredentialless();
  }

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

    // Enable strict SiteIsolation. Currently DIP only supports strict
    // SiteIsolation so force it in tests.
    IsolateAllSitesForTesting(command_line);
  }

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

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

  // Variation of web_contents(), that returns a WebContents* instead of a
  // WebContentsImpl*, required to bind the prerender_helper_ in the
  // constructor.
  WebContents* prerender_web_contents() { return shell()->web_contents(); }

  content::ContentMockCertVerifier mock_cert_verifier_;

  // This needs to be before ScopedFeatureLists, because it contains one
  // internally and the destruction order matters.
  test::PrerenderTestHelper prerender_helper_;

  base::test::ScopedFeatureList feature_list_;
  base::test::ScopedFeatureList feature_list_for_render_document_;
  base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
  net::EmbeddedTestServer https_server_;
};

class DocumentIsolationPolicyWithoutFeatureBrowserTest
    : public ContentBrowserTest,
      public ::testing::WithParamInterface<
          std::tuple<std::string, bool, bool>> {
 public:
  DocumentIsolationPolicyWithoutFeatureBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    // Disable the DocumentIsolationPolicy feature.
    feature_list_.InitWithFeatures(
        {}, {network::features::kDocumentIsolationPolicy});

    // Enable RenderDocument:
    InitAndEnableRenderDocumentFeature(&feature_list_for_render_document_,
                                       std::get<0>(GetParam()));
    // Enable BackForwardCache:
    if (IsBackForwardCacheEnabled()) {
      feature_list_for_back_forward_cache_.InitWithFeaturesAndParameters(
          GetDefaultEnabledBackForwardCacheFeaturesForTesting(
              /*ignore_outstanding_network_request=*/false),
          GetDefaultDisabledBackForwardCacheFeaturesForTesting());
    } else {
      feature_list_for_back_forward_cache_.InitWithFeatures(
          {}, {features::kBackForwardCache});
    }
  }

  // Provides meaningful param names instead of /0, /1, ...
  static std::string DescribeParams(
      const testing::TestParamInfo<ParamType>& info) {
    auto [render_document_level, enable_back_forward_cache, require_corp] =
        info.param;
    return base::StringPrintf(
        "%s_%s_%s",
        GetRenderDocumentLevelNameForTestParams(render_document_level).c_str(),
        enable_back_forward_cache ? "BFCacheEnabled" : "BFCacheDisabled",
        require_corp ? "RequireCorp" : "Credentialless");
  }

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

 protected:
  RenderFrameHostImpl* current_frame_host() {
    return static_cast<WebContentsImpl*>(shell()->web_contents())
        ->GetPrimaryMainFrame();
  }

  GURL GetDocumentIsolationPolicyURL(const std::string& host) {
    // Isolate-and-require-corp version of the test.
    if (std::get<2>(GetParam())) {
      return https_server()->GetURL(
          host,
          "/set-header?document-isolation-policy: isolate-and-require-corp");
    }

    // Isolate-and-credentialless version of the test.
    return https_server()->GetURL(
        host,
        "/set-header?document-isolation-policy: isolate-and-credentialless");
  }

  void SetUpOnMainThread() override {
    ContentBrowserTest::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());
  }

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

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

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

  content::ContentMockCertVerifier mock_cert_verifier_;

  base::test::ScopedFeatureList feature_list_;
  base::test::ScopedFeatureList feature_list_for_render_document_;
  base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
  net::EmbeddedTestServer https_server_;
};

class DocumentIsolationPolicyWithoutSiteIsolationBrowserTest
    : public DocumentIsolationPolicyBrowserTest {
 public:
  DocumentIsolationPolicyWithoutSiteIsolationBrowserTest() = default;

 private:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ContentBrowserTest::SetUpCommandLine(command_line);
    mock_cert_verifier_.SetUpCommandLine(command_line);

    // Disable SiteIsolation.
    command_line->AppendSwitch(switches::kDisableSiteIsolation);
  }

  content::ContentMockCertVerifier mock_cert_verifier_;
};

}  // namespace

// Checks that a Document-Isolation-Policy header is ignored if the
// DocumentIsolationPolicy feature flag is not enabled.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyWithoutFeatureBrowserTest,
                       DIP_Disabled) {
  GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));
  EXPECT_EQ(current_frame_host()
                ->policy_container_host()
                ->policies()
                .document_isolation_policy,
            DipNone());
}

// Checks that a Document-Isolation-Policy header is ignored if SiteIsolation is
// not enabled.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyWithoutSiteIsolationBrowserTest,
                       DIP_Disabled) {
  GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));
  EXPECT_EQ(current_frame_host()
                ->policy_container_host()
                ->policies()
                .document_isolation_policy,
            DipNone());
}

// Checks that DocumentIsolationPolicy is properly inherited from its creator by
// the about:blank document in a new popup.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       NewPopup_InheritsDIP) {
  GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
  GURL no_dip(https_server()->GetURL("a.test", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));

  RenderFrameHostImpl* main_rfh = current_frame_host();

  // Open a popup.
  ShellAddedObserver shell_observer;
  EXPECT_TRUE(ExecJs(main_rfh, "window.open('about:blank')"));

  auto* popup_webcontents =
      static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
  RenderFrameHostImpl* popup_rfh = popup_webcontents->GetPrimaryMainFrame();

  EXPECT_EQ(
      main_rfh->policy_container_host()->policies().document_isolation_policy,
      GetDocumentIsolationPolicy());
  EXPECT_EQ(
      popup_rfh->policy_container_host()->policies().document_isolation_policy,
      GetDocumentIsolationPolicy());

  // Navigate the popup to a page without DIP. It should not longer have a
  // DocumentIsolationPolicy.
  ASSERT_TRUE(NavigateToURL(popup_webcontents, no_dip));
  popup_rfh = popup_webcontents->GetPrimaryMainFrame();
  EXPECT_EQ(
      popup_rfh->policy_container_host()->policies().document_isolation_policy,
      DipNone());

  // Now add an iframe without DIP which will open a popup.
  ASSERT_TRUE(ExecJs(main_rfh, R"(
    const frame = document.createElement('iframe');
    frame.src = '/empty.html';
    document.body.appendChild(frame);
  )"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  ShellAddedObserver shell_observer_2;
  RenderFrameHostImpl* iframe_rfh = main_rfh->child_at(0)->current_frame_host();
  EXPECT_TRUE(ExecJs(iframe_rfh, "window.open('about:blank')"));

  RenderFrameHostImpl* popup_rfh_2 =
      static_cast<WebContentsImpl*>(shell_observer_2.GetShell()->web_contents())
          ->GetPrimaryMainFrame();

  EXPECT_EQ(
      iframe_rfh->policy_container_host()->policies().document_isolation_policy,
      DipNone());
  EXPECT_EQ(popup_rfh_2->policy_container_host()
                ->policies()
                .document_isolation_policy,
            DipNone());
}

// Checks that a navigation to a Blob URL inherits the DocumentIsolationPolicy
// of its creator.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, BlobInheritsDIP) {
  GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));

  // Create and open blob.
  ShellAddedObserver shell_observer;
  ASSERT_TRUE(ExecJs(current_frame_host(), R"(
    const blob = new Blob(['foo'], {type : 'text/html'});
    const url = URL.createObjectURL(blob);
    window.open(url);
  )"));
  EXPECT_TRUE(WaitForLoadStop(shell_observer.GetShell()->web_contents()));
  RenderFrameHostImpl* popup_rfh =
      static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents())
          ->GetPrimaryMainFrame();

  // DIP inherited from Blob creator
  EXPECT_EQ(
      popup_rfh->policy_container_host()->policies().document_isolation_policy,
      GetDocumentIsolationPolicy());
}

// Checks that an about:blank iframe inherits its DocumentIsolationPolicy from
// its creator.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       AboutBlankInheritsDip) {
  GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));

  EXPECT_EQ(current_frame_host()
                ->policy_container_host()
                ->policies()
                .document_isolation_policy,
            GetDocumentIsolationPolicy());

  // Add an about:blank iframe.
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     "g_iframe = document.createElement('iframe');"
                     "g_iframe.src = 'about:blank';"
                     "document.body.appendChild(g_iframe);"));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* iframe_rfh =
      current_frame_host()->child_at(0)->current_frame_host();

  // Document-Isolation-Policy should have been inherited.
  EXPECT_EQ(
      iframe_rfh->policy_container_host()->policies().document_isolation_policy,
      GetDocumentIsolationPolicy());
}

// Checks that an iframe can enable DocumentIsolationPolicy even if its parent
// does not, and that it will be placed in an appropriate SiteInstance.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, IframeCanSetDip) {
  GURL starting_page(
      https_server()->GetURL("a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL iframe_navigation_url = GetDocumentIsolationPolicyURL("b.com");
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));

  RenderFrameHostImpl* main_rfh = current_frame_host();
  FrameTreeNode* iframe_ftn = main_rfh->child_at(0);
  RenderFrameHostImpl* iframe_rfh = iframe_ftn->current_frame_host();
  scoped_refptr<SiteInstanceImpl> non_dip_iframe_site_instance =
      iframe_rfh->GetSiteInstance();

  // The iframe should not have a DocumentIsolationPolicy.
  EXPECT_EQ(
      iframe_rfh->policy_container_host()->policies().document_isolation_policy,
      DipNone());

  // Navigate the iframe same-origin to a document with DIP header. The
  // header should be taken into account.
  EXPECT_TRUE(NavigateToURLFromRenderer(iframe_ftn, iframe_navigation_url));
  iframe_rfh = iframe_ftn->current_frame_host();

  // The navigation should have used a different SiteInstance from the one
  // previously used as the DocumentIsolationPolicy do not match, even if the
  // navigation is same-origin.
  EXPECT_EQ(iframe_rfh->GetLastCommittedURL(), iframe_navigation_url);
  EXPECT_NE(iframe_rfh->GetSiteInstance(), non_dip_iframe_site_instance);

  EXPECT_EQ(
      iframe_rfh->policy_container_host()->policies().document_isolation_policy,
      GetDocumentIsolationPolicy());
}

// Checks that navigations are placed in the appropriate renderer process
// depending on their DocumentIsolationPolicy, even if the current renderer
// process is crashed or crashes during the navigation. In particular, this
// tests the navigation from a page without DIP to a page with DIP.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       NonDipPageCrashIntoDip) {
  GURL non_dip_page(https_server()->GetURL("a.test", "/title1.html"));
  GURL dip_page = GetDocumentIsolationPolicyURL("a.test");

  // Test a crash before the navigation.
  {
    // Navigate to a non dip page.
    EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Navigate to a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());
  }

  // Test a crash during the navigation.
  {
    // Navigate to a non dip page.
    EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Start navigating to a DIP page.
    TestNavigationManager dip_navigation(web_contents(), dip_page);
    shell()->LoadURL(dip_page);
    EXPECT_TRUE(dip_navigation.WaitForRequestStart());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Finish the navigation to the DIP page.
    ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());

    // The navigation will fail if we create speculative RFH when the navigation
    // started (instead of only when the response started), because the renderer
    // process will crash and trigger deletion of the speculative RFH and the
    // navigation using that speculative RFH. BFCache forces a BrowsingInstance
    // swap (even in this same-site case), hence it also necessitates a
    // speculative RFH.
    // TODO(crbug.com/40261276): If the final RenderFrameHost picked for
    // the navigation doesn't use the same process as the crashed process, we
    // can crash the process after the final RenderFrameHost has been picked
    // instead, and the navigation will commit normally.
    if (!base::FeatureList::IsEnabled(
            features::kResumeNavigationWithSpeculativeRFHProcessGone) &&
        (ShouldCreateNewHostForAllFrames() || IsBackForwardCacheEnabled())) {
      EXPECT_FALSE(dip_navigation.was_committed());
      return;
    }

    EXPECT_TRUE(dip_navigation.was_successful());
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());
  }
}

// Checks that navigations are placed in the appropriate renderer process
// depending on their DocumentIsolationPolicy, even if the current renderer
// process is crashed or crashes during the navigation. In particular, this
// tests the navigation from a page with DIP to a page without DIP.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       DipPageCrashIntoNonDip) {
  GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
  GURL non_dip_page(https_server()->GetURL("a.test", "/empty.html"));
  // Test a crash before the navigation.
  {
    // Navigate to a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Navigate to a non DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              DipNone());
  }

  // Test a crash during the navigation.
  {
    // Navigate to a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Start navigating to a non DIP page.
    TestNavigationManager non_dip_navigation(web_contents(), non_dip_page);
    shell()->LoadURL(non_dip_page);
    EXPECT_TRUE(non_dip_navigation.WaitForRequestStart());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Finish the navigation to the non DIP page.
    ASSERT_TRUE(non_dip_navigation.WaitForNavigationFinished());

    // The navigation will fail if we create speculative RFH when the navigation
    // started (instead of only when the response started), because the renderer
    // process will crash and trigger deletion of the speculative RFH and the
    // navigation using that speculative RFH. BFCache forces a BrowsingInstance
    // swap (even in this same-site case), hence it also necessitates a
    // speculative RFH.
    // TODO(crbug.com/40261276): If the final RenderFrameHost picked for
    // the navigation doesn't use the same process as the crashed process, we
    // can crash the process after the final RenderFrameHost has been picked
    // instead, and the navigation will commit normally.
    if (!base::FeatureList::IsEnabled(
            features::kResumeNavigationWithSpeculativeRFHProcessGone) &&
        (ShouldCreateNewHostForAllFrames() || IsBackForwardCacheEnabled())) {
      EXPECT_FALSE(non_dip_navigation.was_committed());
      EXPECT_EQ(current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .document_isolation_policy,
                GetDocumentIsolationPolicy());
      return;
    }

    EXPECT_TRUE(non_dip_navigation.was_successful());
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              DipNone());
  }

  // Test a crash during the navigation commit.
  {
    // Navigate to a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Start navigating to a non DIP page.
    TestNavigationManager non_dip_navigation(web_contents(), non_dip_page);
    shell()->LoadURL(non_dip_page);
    EXPECT_TRUE(non_dip_navigation.WaitForResponse());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Finish the navigation to the non DIP page.
    ASSERT_TRUE(non_dip_navigation.WaitForNavigationFinished());

    // The navigation will not fail after killing the process of the dip page.
    // A new render process has been created during the navigation to the
    // non-dip page.
    EXPECT_TRUE(non_dip_navigation.was_successful());
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              DipNone());
  }
}

// Checks that navigations are placed in the appropriate renderer process
// depending on their DocumentIsolationPolicy, even if the current renderer
// process is crashed or crashes during the navigation. In particular, this
// tests the navigation between two pages with DIP, including a reload of a
// crashed page.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       DipPageCrashIntoDip) {
  GURL dip_page = GetDocumentIsolationPolicyURL("a.test");

  // Test a crash before the navigation.
  {
    // Navigate to a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Reload the DIP page.
    ReloadBlockUntilNavigationsComplete(shell(), 1);
    EXPECT_TRUE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
        initial_site_instance.get()));
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());
  }

  // Test a crash during the navigation.
  {
    // Navigate to a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Start navigating to a DIP page.
    TestNavigationManager dip_navigation(web_contents(), dip_page);
    shell()->LoadURL(dip_page);
    EXPECT_TRUE(dip_navigation.WaitForRequestStart());

    // Simulate the renderer process crashing.
    RenderProcessHost* process = initial_site_instance->GetProcess();
    ASSERT_TRUE(process);
    std::unique_ptr<RenderProcessHostWatcher> crash_observer(
        new RenderProcessHostWatcher(
            process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
    process->Shutdown(0);
    crash_observer->Wait();
    crash_observer.reset();

    // Finish the navigation to the DIP page.
    ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());

    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());
  }
}

// Checks that a navigation to a document with DocumentIsolationPolicy will be
// placed in a separate process even if the process limit has been reached.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       IsolateInNewProcessDespiteLimitReached) {
  // Set a process limit of 1 for testing.
  RenderProcessHostImpl::SetMaxRendererProcessCount(1);

  // Navigate to a starting page.
  GURL starting_page(https_server()->GetURL("a.test", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));
  scoped_refptr<SiteInstance> initial_site_instance(
      current_frame_host()->GetSiteInstance());

  // Open a popup with DocumentIsolationPolicy set.
  GURL url_openee = GetDocumentIsolationPolicyURL("a.test");
  auto* popup_webcontents =
      OpenPopup(current_frame_host(), url_openee, "popup")->web_contents();
  EXPECT_TRUE(WaitForLoadStop(popup_webcontents));

  // The page and its popup should be in different processes even though the
  // process limit was reached.
  EXPECT_NE(initial_site_instance,
            popup_webcontents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_NE(current_frame_host()->GetProcess(),
            popup_webcontents->GetPrimaryMainFrame()->GetProcess());
}

// Checks that a process hosting a document with DocumentIsolationPolicy is not
// reused for documents without DocumentIsolationPolicy, even if the process
// limit has been reached.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       NoProcessReuseForDIPProcesses) {
  // Set a process limit of 1 for testing.
  RenderProcessHostImpl::SetMaxRendererProcessCount(1);

  // Navigate to a starting page with DocumentIsolationPolicy set.
  GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), starting_page));
  scoped_refptr<SiteInstance> initial_site_instance(
      current_frame_host()->GetSiteInstance());

  // Create a new shell.
  Shell* new_shell = CreateBrowser();

  // Navigate it to a same-origin page without DIP.
  GURL non_dip_url = https_server()->GetURL("a.test", "/title1.html");
  EXPECT_TRUE(NavigateToURL(new_shell, non_dip_url));

  // The original page and the page in the new shell should be in different
  // processes even though the process limit was reached.
  EXPECT_NE(
      initial_site_instance,
      new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_NE(current_frame_host()->GetProcess(),
            new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess());
}

IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       SpeculativeRfhsAndDip) {
  GURL non_dip_page(https_server()->GetURL("a.test", "/title1.html"));
  GURL dip_page = GetDocumentIsolationPolicyURL("a.test");

  // Non-DIP into DIP.
  {
    SCOPED_TRACE("Non-DIP to DIP");

    // Start on a non DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Navigate to a DIP page.
    TestNavigationManager dip_navigation(web_contents(), dip_page);
    shell()->LoadURL(dip_page);
    EXPECT_TRUE(dip_navigation.WaitForRequestStart());

    auto* speculative_rfh = web_contents()
                                ->GetPrimaryFrameTree()
                                .root()
                                ->render_manager()
                                ->speculative_frame_host();
    if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
      // When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
      // same-site main-frame navigations, the navigation will result in a new
      // RFH, so it will create a pending RFH.
      EXPECT_TRUE(speculative_rfh);
    } else {
      EXPECT_FALSE(speculative_rfh);
    }

    ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());

    // Even if the origin of the documents is the same, because their
    // DocumentIsolationPolicies do not match, the navigation is classified as a
    // cross-site browser-initiated request and the browser triggers a
    // speculative BrowsingInstance swap.
    EXPECT_FALSE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
        initial_site_instance.get()));
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());
  }

  // DIP into non-DIP.
  {
    SCOPED_TRACE("DIP to non-DIP");

    // Start on a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Navigate to a non DIP page.
    TestNavigationManager non_dip_navigation(web_contents(), non_dip_page);
    shell()->LoadURL(non_dip_page);
    EXPECT_TRUE(non_dip_navigation.WaitForRequestStart());

    auto* speculative_rfh = web_contents()
                                ->GetPrimaryFrameTree()
                                .root()
                                ->render_manager()
                                ->speculative_frame_host();
    if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
      // When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
      // same-site main-frame navigations, the navigation will result in a new
      // RFH, so it will create a pending RFH.
      EXPECT_TRUE(speculative_rfh);
    } else {
      EXPECT_FALSE(speculative_rfh);
    }

    ASSERT_TRUE(non_dip_navigation.WaitForNavigationFinished());

    // Even if the origin of the documents is the same, because their
    // DocumentIsolationPolicies do not match, the navigation is classified as a
    // cross-site browser-initiated request and the browser triggers a
    // speculative BrowsingInstance swap.
    EXPECT_FALSE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
        initial_site_instance.get()));
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              DipNone());
  }

  // DIP into DIP.
  {
    SCOPED_TRACE("DIP to DIP");

    // Start on a DIP page.
    EXPECT_TRUE(NavigateToURL(shell(), dip_page));
    scoped_refptr<SiteInstance> initial_site_instance(
        current_frame_host()->GetSiteInstance());

    // Navigate to a DIP page.
    TestNavigationManager dip_navigation(web_contents(), dip_page);
    shell()->LoadURL(dip_page);
    EXPECT_TRUE(dip_navigation.WaitForRequestStart());

    auto* speculative_rfh = web_contents()
                                ->GetPrimaryFrameTree()
                                .root()
                                ->render_manager()
                                ->speculative_frame_host();
    if (WillSameSiteNavigationChangeRenderFrameHosts(true, true)) {
      // When RenderDocument is enabled, a speculative RFH will always be
      // created. ProactivelySwapBrowsingInstance will not create one in this
      // case because we are navigating to the same URL as the existing
      // document.
      EXPECT_TRUE(speculative_rfh);
    } else {
      EXPECT_FALSE(speculative_rfh);
    }

    ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());

    EXPECT_TRUE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
        initial_site_instance.get()));
    EXPECT_EQ(current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .document_isolation_policy,
              GetDocumentIsolationPolicy());
  }
}

// A test to make sure that loading a page with DIP creates an origin-keyed
// AgentClusterKey in SiteInstance's SiteInfo.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, DipOriginKeyed) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");

  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(current_si->IsCrossOriginIsolated());
  EXPECT_TRUE(current_si->GetSiteInfo().agent_cluster_key().IsOriginKeyed());

  // While the AgentClusterKey is origin-keyed, this should not impact the OAC
  // status of the SiteInfo.
  if (SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault()) {
    EXPECT_EQ(AgentClusterKey::OACStatus::kOriginKeyedByDefault,
              current_si->GetSiteInfo().oac_status());
  } else {
    EXPECT_EQ(AgentClusterKey::OACStatus::kSiteKeyedByDefault,
              current_si->GetSiteInfo().oac_status());
  }
}

// A test to make sure that loading a page with DIP creates a SiteInfo with an
// AgentClusterKey that has the origin of the DIP document, not its SiteURL.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       DipAgentClusterKeyUsesOrigin) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.b.test");
  url::Origin origin = url::Origin::Create(isolated_page);

  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(current_si->IsCrossOriginIsolated());
  EXPECT_TRUE(current_si->GetSiteInfo().agent_cluster_key().IsOriginKeyed());
  EXPECT_EQ(origin, current_si->GetSiteInfo().agent_cluster_key().GetOrigin());
}

// Tests that main frame navigations are correctly assigned cross-origin
// isolated SiteInstances based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginIsolatedSiteInstance_MainFrame) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
  GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
  GURL non_isolated_page(https_server()->GetURL("a.test", "/title1.html"));

  // Navigation from/to cross-origin isolated pages.

  // Initial non cross-origin isolated page.
  {
    EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());
  }

  // Navigation to a cross-origin isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // The navigation triggers a speculative BrowsingInstance swap because it is
    // browser-initiated and end up being cross-site due to the DIP mismatch.
    EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
  }

  // Navigation to a non cross-origin isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // The navigation triggers a speculative BrowsingInstance swap because it is
    // browser-initiated and end up being cross-site due to the DIP mismatch.
    EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
  }

  // Back navigation from a a non cross-origin isolated page to a cross-origin
  // isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(web_contents()));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // The navigation triggers a speculative BrowsingInstance swap because it is
    // browser-initiated and end up being cross-site due to the DIP mismatch.
    EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
  }

  // Back navigation to the non cross-origin isolated initial page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(web_contents()));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // The navigation triggers a speculative BrowsingInstance swap because it is
    // browser-initiated and end up being cross-site due to the DIP mismatch.
    EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
  }

  // Cross origin navigation in between two cross-origin isolated pages.
  {
    EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURL(shell(), isolated_page_b));
    SiteInstanceImpl* site_instance_2 = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(site_instance_1->IsCrossOriginIsolated());
    EXPECT_TRUE(site_instance_2->IsCrossOriginIsolated());
    EXPECT_NE(site_instance_1->GetOrCreateProcessForTesting(),
              site_instance_2->GetProcess());

    // The navigation triggers a speculative BrowsingInstance swap because it is
    // browser-initiated and cross-site.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2));
  }
}

// Tests that renderer-initiated main frames navigations are assigned a
// cross-origin isolated SiteInstance based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(
    DocumentIsolationPolicyBrowserTest,
    CrossOriginIsolatedSiteInstance_MainFrameRendererInitiated) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
  GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
  GURL non_isolated_page(https_server()->GetURL("a.test", "/title1.html"));

  // Navigation from/to cross-origin isolated pages.

  // Initial non cross-origin isolated page.
  {
    EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());
  }

  // Navigation to a cross-origin isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // When BfCache is enabled, a pro-active BrowsingInstance swap happens.
    if (IsBackForwardCacheEnabled()) {
      EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
    } else {
      EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
    }
  }

  // Navigation to the same cross-origin isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(current_si->IsCrossOriginIsolated());
    EXPECT_EQ(current_si, previous_si);
  }

  // Navigation to a non cross-origin isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), non_isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // When BfCache is enabled, a pro-active BrowsingInstance swap happens.
    if (IsBackForwardCacheEnabled()) {
      EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
    } else {
      EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
    }
  }

  // Navigate back to a cross-origin isolated page.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    web_contents()->GetController().GoBack();
    ASSERT_TRUE(WaitForLoadStop(web_contents()));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(current_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // When BfCache is enabled, a pro-active BrowsingInstance swap happens.
    if (IsBackForwardCacheEnabled()) {
      EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
    } else {
      EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
    }
  }

  // Cross origin navigation in between two cross-origin isolated pages.
  {
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page_b));
    SiteInstanceImpl* site_instance_2 = current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(site_instance_1->IsCrossOriginIsolated());
    EXPECT_TRUE(site_instance_2->IsCrossOriginIsolated());
    EXPECT_NE(site_instance_1->GetOrCreateProcessForTesting(),
              site_instance_2->GetProcess());

    // When BfCache is enabled, a pro-active BrowsingInstance swap happens.
    if (IsBackForwardCacheEnabled()) {
      EXPECT_FALSE(
          site_instance_2->IsRelatedSiteInstance(site_instance_1.get()));
    } else {
      EXPECT_TRUE(
          site_instance_2->IsRelatedSiteInstance(site_instance_1.get()));
    }
  }
}

// Tests that iframe navigations are assigned a cross-origin isolated
// SiteInstance based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginIsolatedSiteInstance_IFrame) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
  GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
  GURL non_isolated_page(https_server()->GetURL("a.test", "/title1.html"));

  // Initial cross-origin isolated page.
  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(main_si->IsCrossOriginIsolated());

  // Same origin cross-origin isolated iframe.
  TestNavigationManager coi_iframe_navigation(web_contents(), isolated_page);

  EXPECT_TRUE(
      ExecJs(web_contents(),
             JsReplace("const iframe = document.createElement('iframe'); "
                       "iframe.src = $1; "
                       "document.body.appendChild(iframe);",
                       isolated_page)));

  ASSERT_TRUE(coi_iframe_navigation.WaitForNavigationFinished());
  EXPECT_TRUE(coi_iframe_navigation.was_successful());
  RenderFrameHostImpl* coi_iframe_rfh =
      current_frame_host()->child_at(0)->current_frame_host();
  SiteInstanceImpl* coi_iframe_si = coi_iframe_rfh->GetSiteInstance();
  EXPECT_EQ(coi_iframe_si, main_si);

  // Same origin non cross-origin isolated iframe.
  TestNavigationManager non_coi_iframe_navigation(web_contents(),
                                                  non_isolated_page);

  EXPECT_TRUE(
      ExecJs(web_contents(),
             JsReplace("const iframe = document.createElement('iframe'); "
                       "iframe.src = $1; "
                       "document.body.appendChild(iframe);",
                       non_isolated_page)));

  ASSERT_TRUE(non_coi_iframe_navigation.WaitForNavigationFinished());
  EXPECT_TRUE(non_coi_iframe_navigation.was_successful());
  RenderFrameHostImpl* non_coi_iframe_rfh =
      current_frame_host()->child_at(1)->current_frame_host();
  SiteInstanceImpl* non_coi_iframe_si = non_coi_iframe_rfh->GetSiteInstance();
  EXPECT_FALSE(non_coi_iframe_si->IsCrossOriginIsolated());
  EXPECT_TRUE(non_coi_iframe_si->IsRelatedSiteInstance(main_si));
  EXPECT_NE(non_coi_iframe_si, main_si);
  EXPECT_NE(non_coi_iframe_si->GetProcess(), main_si->GetProcess());
  EXPECT_NE(non_coi_iframe_si, coi_iframe_si);
  EXPECT_NE(non_coi_iframe_si->GetProcess(), coi_iframe_si->GetProcess());

  // Cross origin iframe.
  TestNavigationManager cross_origin_iframe_navigation(web_contents(),
                                                       isolated_page_b);

  EXPECT_TRUE(
      ExecJs(web_contents(),
             JsReplace("const iframe = document.createElement('iframe'); "
                       "iframe.src = $1; "
                       "document.body.appendChild(iframe);",
                       isolated_page_b)));

  ASSERT_TRUE(cross_origin_iframe_navigation.WaitForNavigationFinished());
  EXPECT_TRUE(cross_origin_iframe_navigation.was_successful());
  RenderFrameHostImpl* iframe_rfh =
      current_frame_host()->child_at(2)->current_frame_host();
  SiteInstanceImpl* cross_origin_iframe_si = iframe_rfh->GetSiteInstance();
  EXPECT_TRUE(cross_origin_iframe_si->IsCrossOriginIsolated());
  EXPECT_TRUE(cross_origin_iframe_si->IsRelatedSiteInstance(main_si));
  EXPECT_NE(cross_origin_iframe_si, main_si);
  EXPECT_NE(cross_origin_iframe_si->GetProcess(), main_si->GetProcess());
  EXPECT_NE(cross_origin_iframe_si, coi_iframe_si);
  EXPECT_NE(cross_origin_iframe_si->GetProcess(), coi_iframe_si->GetProcess());
  EXPECT_NE(cross_origin_iframe_si, non_coi_iframe_si);
  EXPECT_NE(cross_origin_iframe_si->GetProcess(),
            non_coi_iframe_si->GetProcess());

  // Navigate to a non cross-origin isolated page with a cross-origin isolated
  // iframe.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), non_isolated_page));
    main_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(main_si->IsCrossOriginIsolated());
    EXPECT_NE(main_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());

    // When BfCache is enabled, a pro-active BrowsingInstance swap happens.
    if (IsBackForwardCacheEnabled()) {
      EXPECT_FALSE(main_si->IsRelatedSiteInstance(previous_si.get()));
    } else {
      EXPECT_TRUE(main_si->IsRelatedSiteInstance(previous_si.get()));
    }

    TestNavigationManager same_origin_iframe_navigation(web_contents(),
                                                        isolated_page);

    EXPECT_TRUE(
        ExecJs(web_contents(),
               JsReplace("const iframe = document.createElement('iframe'); "
                         "iframe.src = $1; "
                         "document.body.appendChild(iframe);",
                         isolated_page)));

    ASSERT_TRUE(same_origin_iframe_navigation.WaitForNavigationFinished());
    EXPECT_TRUE(same_origin_iframe_navigation.was_successful());
    iframe_rfh = current_frame_host()->child_at(0)->current_frame_host();
    SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
    EXPECT_TRUE(iframe_si->IsCrossOriginIsolated());
    EXPECT_TRUE(iframe_si->IsRelatedSiteInstance(main_si));
    EXPECT_NE(iframe_si, main_si);
    EXPECT_NE(iframe_si->GetProcess(), main_si->GetProcess());
  }
}

// Tests that navigations in popups are correctly assigned a cross-origin
// isolated SiteInstance based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginIsolatedSiteInstance_Popup) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
  GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
  GURL non_isolated_page(
      embedded_test_server()->GetURL("a.test", "/title1.html"));

  // Initial cross-origin isolated page.
  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(main_si->IsCrossOriginIsolated());

  // Open a non isolated popup.
  {
    RenderFrameHostImpl* popup_rfh =
        static_cast<WebContentsImpl*>(
            OpenPopup(current_frame_host(), non_isolated_page, "")
                ->web_contents())
            ->GetPrimaryMainFrame();

    EXPECT_FALSE(popup_rfh->GetSiteInstance()->IsCrossOriginIsolated());
    EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsRelatedSiteInstance(
        current_frame_host()->GetSiteInstance()));
  }

  // Open an isolated popup.
  {
    RenderFrameHostImpl* popup_rfh =
        static_cast<WebContentsImpl*>(
            OpenPopup(current_frame_host(), isolated_page, "")->web_contents())
            ->GetPrimaryMainFrame();

    EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsCrossOriginIsolated());
    EXPECT_EQ(popup_rfh->GetSiteInstance(),
              current_frame_host()->GetSiteInstance());
  }

  // Open an isolated popup, but cross-origin.
  {
    RenderFrameHostImpl* popup_rfh =
        static_cast<WebContentsImpl*>(
            OpenPopup(current_frame_host(), isolated_page_b, "")
                ->web_contents())
            ->GetPrimaryMainFrame();

    EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsCrossOriginIsolated());
    EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsRelatedSiteInstance(
        current_frame_host()->GetSiteInstance()));
    EXPECT_NE(popup_rfh->GetSiteInstance()->GetProcess(),
              current_frame_host()->GetSiteInstance()->GetProcess());
  }
}

// Tests that navigations involving error pages are correctly assigned a
// cross-origin isolated SiteInstance based on their DocumentIsolationPolicy
// status.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginIsolatedSiteInstance_ErrorPage) {
  GURL isolated_page = GetDocumentIsolationPolicyURL(
      "a.test", "Cross-Origin-Embedder-Policy: require-corp");
  GURL non_coep_page(https_server()->GetURL("b.test",
                                            "/set-header?"
                                            "Access-Control-Allow-Origin: *"));

  GURL invalid_url(
      https_server()->GetURL("a.test", "/this_page_does_not_exist.html"));

  GURL error_url(https_server()->GetURL("a.test", "/page404.html"));
  GURL non_isolated_page(
      https_server()->GetURL("a.test",
                             "/set-header?"
                             "Cross-Origin-Embedder-Policy: require-corp"));

  // Initial cross-origin isolated page.
  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(main_si->IsCrossOriginIsolated());

  // Iframe.
  {
    TestNavigationManager iframe_navigation(web_contents(), invalid_url);

    EXPECT_TRUE(
        ExecJs(web_contents(),
               JsReplace("const iframe = document.createElement('iframe'); "
                         "iframe.src = $1; "
                         "document.body.appendChild(iframe);",
                         invalid_url)));

    ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
    EXPECT_FALSE(iframe_navigation.was_successful());
    RenderFrameHostImpl* iframe_rfh =
        current_frame_host()->child_at(0)->current_frame_host();
    SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
    // The load of the document with 404 status code is blocked by COEP.
    // An error page is expected in lieu of that document.
    EXPECT_EQ(GURL(kUnreachableWebDataURL),
              EvalJs(iframe_rfh, "document.location.href;"));
    EXPECT_TRUE(IsExpectedSubframeErrorTransition(main_si, iframe_si));
    EXPECT_FALSE(iframe_si->IsCrossOriginIsolated());
    EXPECT_NE(iframe_si, main_si);
  }

  // Iframe with a body added to the HTTP 404.
  {
    TestNavigationManager iframe_navigation(web_contents(), error_url);

    EXPECT_TRUE(
        ExecJs(web_contents(),
               JsReplace("const iframe = document.createElement('iframe'); "
                         "iframe.src = $1; "
                         "document.body.appendChild(iframe);",
                         error_url)));

    ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
    EXPECT_FALSE(iframe_navigation.was_successful());
    RenderFrameHostImpl* iframe_rfh =
        current_frame_host()->child_at(0)->current_frame_host();
    SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
    EXPECT_TRUE(IsExpectedSubframeErrorTransition(main_si, iframe_si));

    // The load of the document with 404 status code and custom body is blocked
    // by COEP. An error page is expected in lieu of that document.
    EXPECT_EQ(GURL(kUnreachableWebDataURL),
              EvalJs(iframe_rfh, "document.location.href;"));
    EXPECT_FALSE(iframe_si->IsCrossOriginIsolated());
    EXPECT_NE(iframe_si, main_si);
  }

  // Iframe blocked by coep.
  {
    TestNavigationManager iframe_navigation(web_contents(), non_coep_page);

    EXPECT_TRUE(
        ExecJs(web_contents(),
               JsReplace("const iframe = document.createElement('iframe'); "
                         "iframe.src = $1; "
                         "document.body.appendChild(iframe);",
                         non_coep_page)));

    ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
    EXPECT_FALSE(iframe_navigation.was_successful());
    RenderFrameHostImpl* iframe_rfh =
        current_frame_host()->child_at(0)->current_frame_host();
    SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
    EXPECT_TRUE(IsExpectedSubframeErrorTransition(main_si, iframe_si));
    EXPECT_FALSE(iframe_si->IsCrossOriginIsolated());
  }

  // Top frame.
  {
    scoped_refptr<SiteInstanceImpl> previous_si =
        current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(NavigateToURL(shell(), invalid_url));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    if (IsBackForwardCacheEnabled()) {
      EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
    } else {
      EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
    }
    EXPECT_NE(current_si->GetProcess(),
              previous_si->GetOrCreateProcessForTesting());
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());
  }

  // DIP iframe inside non-DIP page.
  {
    EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
    SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
    EXPECT_FALSE(current_si->IsCrossOriginIsolated());

    // First, add an iframe with DocumentIsolationPolicy.
    TestNavigationManager iframe_navigation(web_contents(), isolated_page);
    EXPECT_TRUE(
        ExecJs(web_contents(),
               JsReplace("const iframe = document.createElement('iframe'); "
                         "iframe.id = 'iframe';"
                         "iframe.src = $1; "
                         "document.body.appendChild(iframe);",
                         isolated_page)));
    ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
    EXPECT_TRUE(iframe_navigation.was_successful());
    RenderFrameHostImpl* iframe_rfh =
        current_frame_host()->child_at(0)->current_frame_host();
    scoped_refptr<SiteInstanceImpl> iframe_si = iframe_rfh->GetSiteInstance();
    EXPECT_TRUE(iframe_si->IsCrossOriginIsolated());
    EXPECT_NE(current_si, iframe_si);
    EXPECT_NE(current_si->GetProcess(), iframe_si->GetProcess());

    // Now navigate the iframe to an error page.
    TestNavigationManager error_navigation(web_contents(), invalid_url);
    EXPECT_TRUE(ExecJs(
        web_contents(),
        JsReplace("document.getElementById('iframe').src = $1;", invalid_url)));
    ASSERT_TRUE(error_navigation.WaitForNavigationFinished());
    EXPECT_FALSE(error_navigation.was_successful());
    iframe_rfh = current_frame_host()->child_at(0)->current_frame_host();
    scoped_refptr<SiteInstanceImpl> error_si = iframe_rfh->GetSiteInstance();
    EXPECT_FALSE(error_si->IsCrossOriginIsolated());
    EXPECT_NE(error_si, iframe_si);
    EXPECT_NE(error_si->GetProcess(), iframe_si->GetProcess());
    EXPECT_EQ(error_si, current_si);
    EXPECT_EQ(error_si->GetProcess(), current_si->GetProcess());
  }
}

// Tests that a reload navigation that redirects to a page with a
// Document-Isolation-Policy header is placed in a cross-origin isolated
// SiteInstance, even if the original page did not have DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       ReloadRedirectsToDipPage) {
  GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
  GURL redirect_page(https_server()->GetURL(
      "a.test", "/redirect-on-second-navigation?" + dip_page.spec()));

  // Navigate to the redirect page. On the first navigation, this is a simple
  // empty page with no headers.
  EXPECT_TRUE(NavigateToURL(shell(), redirect_page));
  scoped_refptr<SiteInstanceImpl> main_si =
      current_frame_host()->GetSiteInstance();
  EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), redirect_page);

  // Reload. This time we should be redirected to a DIP:
  // isolate-and-require-corp page.
  ReloadBlockUntilNavigationsComplete(shell(), 1);
  EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), dip_page);

  // We should have swapped SiteInstance.
  EXPECT_NE(main_si, current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(
      main_si->IsRelatedSiteInstance(current_frame_host()->GetSiteInstance()));
  EXPECT_NE(main_si->GetOrCreateProcessForTesting(),
            current_frame_host()->GetSiteInstance()->GetProcess());
}

// Tests that a reload navigation where the page starts sending
// DocumentIsolationPolicy header on the reload (while the initial load did not
// have them). Tha navigation should end up in a cross-origin isolated
// SiteInstance.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       ReloadPageWithUpdatedDipHeader) {
  GURL changing_dip_page(
      https_server()->GetURL("a.test", "/serve-dip-on-second-navigation"));

  // Navigate to the page. On the first navigation, this is a simple empty page
  // with no headers.
  EXPECT_TRUE(NavigateToURL(shell(), changing_dip_page));
  scoped_refptr<SiteInstanceImpl> main_si =
      current_frame_host()->GetSiteInstance();

  // Reload. This time the page should be served with DIP:
  // isolate-and-require-corp.
  ReloadBlockUntilNavigationsComplete(shell(), 1);

  // We should have swapped SiteInstance.
  EXPECT_NE(main_si, current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(
      main_si->IsRelatedSiteInstance(current_frame_host()->GetSiteInstance()));
  EXPECT_NE(main_si->GetOrCreateProcessForTesting(),
            current_frame_host()->GetSiteInstance()->GetProcess());
}

// Checks that a cross-origin but same site iframe is placed in a different
// SiteInstance from its parent when both have DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginSameSiteIframe) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
  GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");

  // Initial cross-origin isolated page.
  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(main_si->IsCrossOriginIsolated());

  TestNavigationManager cross_origin_iframe_navigation(web_contents(),
                                                       isolated_page_b);

  // Add a cross-origin but same-site iframe.
  EXPECT_TRUE(
      ExecJs(web_contents(),
             JsReplace("const iframe = document.createElement('iframe'); "
                       "iframe.src = $1; "
                       "document.body.appendChild(iframe);",
                       isolated_page_b)));

  ASSERT_TRUE(cross_origin_iframe_navigation.WaitForNavigationFinished());
  EXPECT_TRUE(cross_origin_iframe_navigation.was_successful());
  RenderFrameHostImpl* iframe_rfh =
      current_frame_host()->child_at(0)->current_frame_host();
  SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
  EXPECT_NE(iframe_si, main_si);
  EXPECT_TRUE(iframe_si->IsCrossOriginIsolated());
  EXPECT_TRUE(iframe_si->IsRelatedSiteInstance(main_si));
  EXPECT_NE(iframe_si->GetProcess(), main_si->GetProcess());

  // Open an isolated popup from the cross-origin but same-site iframe. It
  // should end up in the same SiteInstance as the main frame, since they are
  // same-origin with the same Document-Isolation-Policy.
  {
    RenderFrameHostImpl* popup_rfh =
        static_cast<WebContentsImpl*>(
            OpenPopup(iframe_rfh, isolated_page, "", "", true)->web_contents())
            ->GetPrimaryMainFrame();

    EXPECT_EQ(main_si, popup_rfh->GetSiteInstance());
  }
}

// Checks that the WebExposedIsolationLevel of a RenderFrameHost is properly
// computed when cross-origin isolation is enabled through
// DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       WebExposedIsolationLevel) {
  GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
  GURL isolated_page_b = GetDocumentIsolationPolicyURL("b.test");

  // Not isolated:
  EXPECT_TRUE(NavigateToURL(shell(), https_server()->GetURL("/empty.html")));
  EXPECT_EQ(WebExposedIsolationLevel::kNotIsolated,
            current_frame_host()->GetWebExposedIsolationLevel());

  // Cross-Origin Isolated:
  EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
  EXPECT_EQ(WebExposedIsolationLevel::kIsolated,
            current_frame_host()->GetWebExposedIsolationLevel());

  // Cross-origin isolated iframe without permission delegation. The iframe
  // should be cross-origin isolated, as the permission only applies to
  // cross-origin isolation inherited from the parent (and enabled by COOP &
  // COEP).
  std::string create_iframe = R"(
    new Promise(resolve => {
      const iframe = document.createElement('iframe');
      iframe.src = $1;
      iframe.allow = "cross-origin-isolated 'none'";
      iframe.addEventListener('load', () => resolve(true));
      document.body.appendChild(iframe);
    });
  )";
  EXPECT_TRUE(ExecJs(shell(), JsReplace(create_iframe, isolated_page_b)));
  RenderFrameHostImpl* iframe_rfh =
      current_frame_host()->child_at(0)->current_frame_host();
  EXPECT_EQ(WebExposedIsolationLevel::kIsolated,
            iframe_rfh->GetWebExposedIsolationLevel());
}

// Checks that a document with document isolation policy has its
// crossOriginIsolated property set to true and can instantiate
// SharedArrayBuffers.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, SAB) {
  GURL url = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  EXPECT_EQ(true, EvalJs(current_frame_host(), "self.crossOriginIsolated"));
  EXPECT_EQ(true,
            EvalJs(current_frame_host(), "'SharedArrayBuffer' in globalThis"));
}

// Checks that a document with document isolation policy can transfer a
// SharedArrayBuffer to a crossOriginIsolated same-origin iframe.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       SAB_TransferToIframe) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL url = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     "g_iframe = document.createElement('iframe');"
                     "g_iframe.src = location.href;"
                     "document.body.appendChild(g_iframe);"));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();

  EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
  EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));

  EXPECT_TRUE(ExecJs(sub_document, R"(
    g_sab_size = new Promise(resolve => {
      addEventListener("message", event => resolve(event.data.byteLength));
    });
  )",
                     EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));

  EXPECT_TRUE(ExecJs(main_document, R"(
    const sab = new SharedArrayBuffer(1234);
    g_iframe.contentWindow.postMessage(sab, "*");
  )"));

  EXPECT_EQ(1234, EvalJs(sub_document, "g_sab_size"));
}

// Checks that a document with document isolation policy can transfer a
// SharedArrayBuffer to a crossOriginIsolated same-origin about:blank iframe.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       SAB_TransferToAboutBlankIframe) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL url = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     "g_iframe = document.createElement('iframe');"
                     "g_iframe.src = 'about:blank';"
                     "document.body.appendChild(g_iframe);"));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();

  EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
  EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));
  EXPECT_EQ(true, EvalJs(sub_document, "'SharedArrayBuffer' in globalThis"));
}

// Checks that an about:blank iframe created by a cross-origin isolated document
// (through Document-Isolation-Policy) is set as crossOriginIsolated
// synchronously.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       AboutBlankIsSetCOISynchronously) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL url = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  EXPECT_EQ(true, EvalJs(current_frame_host(),
                         "const iframe = document.createElement('iframe');"
                         "document.body.appendChild(iframe);"
                         "iframe.contentWindow.crossOriginIsolated;"));
}

// Transfer a SharedArrayBuffer in between two documents with a parent/child
// relationship. The child has not set Document-Isolation-Policy, and is not
// cross-origin isolated. It cannot receive the object.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       SAB_TransferToNoCrossOriginIsolatedIframe) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL main_url = GetDocumentIsolationPolicyURL("a.test");
  GURL iframe_url = https_server()->GetURL("a.test", "/title1.html");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     JsReplace("g_iframe = document.createElement('iframe');"
                               "g_iframe.src = $1;"
                               "document.body.appendChild(g_iframe);",
                               iframe_url)));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();

  EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
  EXPECT_EQ(false, EvalJs(sub_document, "self.crossOriginIsolated"));

  // The parent and its child frame are in different processes, so it's not
  // possible to transfer a SharedArrayBuffer between the two of them.
  EXPECT_NE(main_document->GetSiteInstance()->GetProcess(),
            sub_document->GetSiteInstance()->GetProcess());
}

// Transfer a SharedArrayBuffer in between two documents with a
// parent/child relationship. The child does not have Document-Isolation-Policy.
// This non-cross-origin-isolated document cannot transfer a SharedArrayBuffer
// toward the cross-origin-isolated one.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       SAB_TransferFromNoCrossOriginIsolatedIframe) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL main_url = GetDocumentIsolationPolicyURL("a.test");
  GURL iframe_url = https_server()->GetURL("a.test", "/title1.html");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     JsReplace("g_iframe = document.createElement('iframe');"
                               "g_iframe.src = $1;"
                               "document.body.appendChild(g_iframe);",
                               iframe_url)));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();

  EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
  EXPECT_EQ(false, EvalJs(sub_document, "self.crossOriginIsolated"));

  EXPECT_TRUE(ExecJs(main_document, R"(
    g_sab_size = new Promise(resolve => {
      addEventListener("message", event => resolve(event.data.byteLength));
    });
  )",
                     EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));

  EXPECT_EQ(false, EvalJs(sub_document, "'SharedArrayBuffer' in globalThis"));

  // Unlike crossOriginIsolation enabled by COOP and COEP, the SAB cannot be
  // created in the non-crossOriginIsolated iframe.
  // See https://crbug.com/1144838 for discussions about this behavior in COOP
  // and COEP.
  EXPECT_FALSE(ExecJs(sub_document, R"(
    // Create a WebAssembly Memory to try to bypass the SAB constructor
    // restriction.
    const sab = new (new WebAssembly.Memory(
        { shared:true, initial:1, maximum:1 }).buffer.constructor)(1234);
    parent.postMessage(sab, "*");
  )"));
}

// Check that a same-origin iframe can become cross-origin isolated using
// DocumentIsolationPolicy regardless of its parent's crossOriginIsolated
// status.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginIsolatedIframe_SameOrigin) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL main_url = https_server()->GetURL("a.test", "/title1.html");
  GURL iframe_url = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     JsReplace("g_iframe = document.createElement('iframe');"
                               "g_iframe.src = $1;"
                               "document.body.appendChild(g_iframe);",
                               iframe_url)));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();

  EXPECT_EQ(false, EvalJs(main_document, "self.crossOriginIsolated"));
  EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));

  EXPECT_NE(main_document->GetSiteInstance()->GetProcess(),
            sub_document->GetSiteInstance()->GetProcess());
}

// Check that a cross-origin iframe can become cross-origin isolated using
// DocumentIsolationPolicy regardless of its parent's crossOriginIsolated
// status.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       CrossOriginIsolatedIframe_CrossOrigin) {
  CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
  GURL main_url = https_server()->GetURL("a.test", "/title1.html");
  GURL iframe_url = GetDocumentIsolationPolicyURL("b.test");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     JsReplace("g_iframe = document.createElement('iframe');"
                               "g_iframe.src = $1;"
                               "document.body.appendChild(g_iframe);",
                               iframe_url)));
  WaitForLoadStop(web_contents());

  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();

  EXPECT_EQ(false, EvalJs(main_document, "self.crossOriginIsolated"));
  EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));

  EXPECT_NE(main_document->GetSiteInstance()->GetProcess(),
            sub_document->GetSiteInstance()->GetProcess());
}

// Regression test for crbug.com/394350439.
// Doing a cross-document navigation to a local scheme that inherits DIP should
// not cause a crash.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       DIPNavigationToLocalScheme) {
  // Navigate to a page with DocumentIsolationPolicy.
  GURL main_url = GetDocumentIsolationPolicyURL("a.test");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Now navigate the top-level frame to about:blank. It should be cross-origin
  // isolated because it inherits the DocumentIsolationPolicy of its navigation
  // initiator (the current document).
  EXPECT_TRUE(ExecJs(current_frame_host(), "location = 'about:blank';"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  EXPECT_EQ(url::kAboutBlankURL, current_frame_host()->GetLastCommittedURL());
  EXPECT_EQ(current_frame_host()
                ->policy_container_host()
                ->policies()
                .document_isolation_policy,
            GetDocumentIsolationPolicy());
}

// TODO(crbug.com/349104385): Add a test checking that the
// Document-Isolation-Policy header is ignored on redirect responses.

// Regression test for crbug.com/393480086.
// Blocking a navigation at BeginNavigation stage in a child frame of a document
// with DocumentIsolationPolicy should not cause a crash.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
                       IframeLoadCancelledInBeginNavigation) {
  GURL main_url = GetDocumentIsolationPolicyURL(
      "a.test", "Content-Security-Policy: frame-src 'none';");
  GURL iframe_url = https_server()->GetURL("a.test", "/title1.html");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     JsReplace("g_iframe = document.createElement('iframe');"
                               "g_iframe.src = $1;"
                               "document.body.appendChild(g_iframe);",
                               iframe_url)));
  WaitForLoadStop(web_contents());

  // TODO(crbug.com/395036622): The subframe error page should commit in the
  // isolated error page process.
  RenderFrameHostImpl* main_document = current_frame_host();
  RenderFrameHostImpl* sub_document =
      current_frame_host()->child_at(0)->current_frame_host();
  EXPECT_EQ(main_document->GetSiteInstance()->GetProcess(),
            sub_document->GetSiteInstance()->GetProcess());
  EXPECT_FALSE(sub_document->GetSiteInstance()->GetSiteInfo().is_error_page());
}

static auto kTestParams =
    testing::Combine(testing::ValuesIn(RenderDocumentFeatureLevelValues()),
                     testing::Bool(),
                     testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
                         DocumentIsolationPolicyBrowserTest,
                         kTestParams,
                         DocumentIsolationPolicyBrowserTest::DescribeParams);
INSTANTIATE_TEST_SUITE_P(All,
                         DocumentIsolationPolicyWithoutFeatureBrowserTest,
                         kTestParams,
                         DocumentIsolationPolicyBrowserTest::DescribeParams);
INSTANTIATE_TEST_SUITE_P(All,
                         DocumentIsolationPolicyWithoutSiteIsolationBrowserTest,
                         kTestParams,
                         DocumentIsolationPolicyBrowserTest::DescribeParams);

}  // namespace content