// 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 "content/browser/site_per_process_browsertest.h"

#include "content/browser/renderer_host/navigation_entry_restore_context_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.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/test_frame_navigation_observer.h"
#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/page_state/page_state_serialization.h"
#include "third_party/blink/public/common/switches.h"

namespace content {

// Test class that runs with kNewBaseUrlInheritanceBehavior enabled.
class BaseUrlInheritanceBehaviorIframeTest : public ContentBrowserTest {
 public:
  BaseUrlInheritanceBehaviorIframeTest() {
    feature_list_.InitAndEnableFeature(
        blink::features::kNewBaseUrlInheritanceBehavior);
  }

  void SetUpOnMainThread() override {
    // Support multiple sites on the test server.
    host_resolver()->AddRule("*", "127.0.0.1");
  }
  void StartEmbeddedServer() {
    SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};  // class NewBaseUrlInheritanceBehaviorIframeTest

// Test class that runs with the legacy base url behavior.
class BaseUrlLegacyBehaviorIframeTest : public ContentBrowserTest {
 public:
  BaseUrlLegacyBehaviorIframeTest() {
    feature_list_.InitAndDisableFeature(
        blink::features::kNewBaseUrlInheritanceBehavior);
  }

  void SetUpOnMainThread() override {
    // Support multiple sites on the test server.
    host_resolver()->AddRule("*", "127.0.0.1");
  }
  void StartEmbeddedServer() {
    SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};  // class BaseUrlLegacyBehaviorIframeTest

// A class for tests that should run both with and without the new BaseURL
// inheritance behavior.
class BaseUrlInheritanceIframeTest
    : public ContentBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  BaseUrlInheritanceIframeTest() {
    if (GetParam()) {  // Test new base url behavior.
      feature_list_.InitWithFeatureState(
          blink::features::kNewBaseUrlInheritanceBehavior, true);
    } else {
      // Need to force off kIsolateSandboxedIframes if it's enabled in order to
      // test the legacy base url behavior.
      feature_list_.InitWithFeatureStates(
          {{blink::features::kNewBaseUrlInheritanceBehavior, false},
           {blink::features::kIsolateSandboxedIframes, false}});
    }
  }

  void SetUpOnMainThread() override {
    // Support multiple sites on the test server.
    host_resolver()->AddRule("*", "127.0.0.1");
  }
  void StartEmbeddedServer() {
    SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};  // class BaseUrlInheritanceIframeTest

// A test to make sure that restoring a session history entry that was saved
// with an about:blank subframe never results in an initiator_base_url of
// an empty string. std::nullopt is expected instead of an empty GURL with
// legacy base url behavior, or the non-empty initiator base url in the
// new base url inheritance mode. This test runs in both modes.
IN_PROC_BROWSER_TEST_P(BaseUrlInheritanceIframeTest,
                       BaseURLFromSessionHistoryIsNulloptNotEmptyString) {
  StartEmbeddedServer();
  GURL main_url(
      embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  // Navigate child to about:blank.
  {
    TestNavigationObserver iframe_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(child, "location.href = 'about:blank';"));
    iframe_observer.Wait();
  }
  GURL child_frame_url = child->current_frame_host()->GetLastCommittedURL();

  // Save the page state.
  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
      shell()->web_contents()->GetController());
  NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
  blink::PageState page_state = entry->GetPageState();

  // Decode the page state so we can inspect what base url value it contains.
  blink::ExplodedPageState exploded_page_state;
  ASSERT_TRUE(
      blink::DecodePageState(page_state.ToEncodedData(), &exploded_page_state));
  EXPECT_EQ(1U, exploded_page_state.top.children.size());
  if (GetParam()) {
    // Make sure the about:blank child has the correct initiator_base_url.
    GURL initiator_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(exploded_page_state.top.children[0]
                    .initiator_base_url_string.has_value());
    EXPECT_EQ(
        base::UTF8ToUTF16(initiator_url.spec()),
        exploded_page_state.top.children[0].initiator_base_url_string.value());
  } else {
    // Make sure the about:blank child has nullopt, and not an empty string, for
    // the initiator_base_url.
    EXPECT_EQ(std::nullopt,
              exploded_page_state.top.children[0].initiator_base_url_string);
  }
}

// A test to make sure that restoring a session history entry that was saved
// while the new behavior was enabled doesn't hit any CHECKs if it's restored
// while using the legacy behavior.
IN_PROC_BROWSER_TEST_F(BaseUrlLegacyBehaviorIframeTest,
                       RestoreNonEmptyBaseURLFromSessionHistory) {
  StartEmbeddedServer();
  GURL main_url(
      embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  // Navigate child to about:blank.
  {
    TestNavigationObserver iframe_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(child, "location.href = 'about:blank';"));
    iframe_observer.Wait();
  }
  GURL child_frame_url = child->current_frame_host()->GetLastCommittedURL();

  // Save the page state.
  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
      shell()->web_contents()->GetController());
  NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
  blink::PageState page_state = entry->GetPageState();

  // Simulate the case that the PageState was stored from a session with the new
  // base URL inheritance behavior enabled, by defining the
  // initiator_base_url_string. This approach is necessary because it is
  // difficult to change the feature state at runtime during the test.
  {
    blink::ExplodedPageState exploded_page_state;
    ASSERT_TRUE(blink::DecodePageState(page_state.ToEncodedData(),
                                       &exploded_page_state));
    EXPECT_EQ(1U, exploded_page_state.top.children.size());
    // Add a non-null base url which shouldn't be there if the feature is turned
    // off.
    exploded_page_state.top.children[0].initiator_base_url_string =
        base::UTF8ToUTF16(main_url.spec());
    std::string encoded_data;
    blink::EncodePageState(exploded_page_state, &encoded_data);
    page_state = blink::PageState::CreateFromEncodedData(encoded_data);
  }

  // Restore the altered entry in a new tab and verify the frame loads without
  // hitting any CHECKs.
  Shell* new_shell = Shell::CreateNewWindow(
      controller.GetBrowserContext(), GURL::EmptyGURL(), nullptr, gfx::Size());
  FrameTreeNode* new_root =
      static_cast<WebContentsImpl*>(new_shell->web_contents())
          ->GetPrimaryFrameTree()
          .root();
  NavigationControllerImpl& new_controller =
      static_cast<NavigationControllerImpl&>(
          new_shell->web_contents()->GetController());
  // Create the restored entry.
  std::unique_ptr<NavigationEntryImpl> restored_entry = entry->Clone();
  NavigationEntryRestoreContextImpl context;
  restored_entry->SetPageState(page_state, &context);
  EXPECT_EQ(main_url, restored_entry->root_node()->frame_entry->url());
  ASSERT_EQ(1U, restored_entry->root_node()->children.size());
  EXPECT_EQ(child_frame_url,
            restored_entry->root_node()->children[0]->frame_entry->url());

  std::vector<std::unique_ptr<NavigationEntry>> entries;
  entries.push_back(std::move(restored_entry));
  new_controller.Restore(entries.size() - 1, RestoreType::kRestored, &entries);
  ASSERT_EQ(0u, entries.size());
  {
    TestNavigationObserver restore_observer(new_shell->web_contents());
    new_controller.LoadIfNecessary();
    restore_observer.Wait();
  }
  ASSERT_EQ(1U, new_root->child_count());
  EXPECT_EQ(main_url, new_root->current_url());
  EXPECT_EQ(GURL("about:blank"), new_root->child_at(0)->current_url());
}

// Test class to allow testing srcdoc functionality both with and without
// `kIsolateSandboxedIframes` enabled. The tests verify the correct operation of
// plumbing of both srcdoc attribute values, as well as the srcdoc frame's
// parent's base url values, to the srcdoc's frame's renderer.
class SrcdocIsolatedSandboxedIframeTest
    : public ContentBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  SrcdocIsolatedSandboxedIframeTest() {
    feature_list_.InitWithFeatureState(
        blink::features::kIsolateSandboxedIframes, GetParam());
  }

  void SetUpOnMainThread() override {
    // Support multiple sites on the test server.
    host_resolver()->AddRule("*", "127.0.0.1");
  }
  void StartEmbeddedServer() {
    SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};  // class SrcdocIsolatedSandboxedIframeTest

// Test class to verify that the enterprise policy
// NewBaseUrlInheritanceBehaviorAllowed can be used to control whether the
// NewBaseUrlInheritanceBehavior and IsolateSandboxedIframes features can be
// used.
class BaseUrlInheritanceBehaviorEnterprisePolicyTest
    : public SrcdocIsolatedSandboxedIframeTest {
 public:
  BaseUrlInheritanceBehaviorEnterprisePolicyTest() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SrcdocIsolatedSandboxedIframeTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(
        blink::switches::kDisableNewBaseUrlInheritanceBehavior);
  }
};  // class BaseUrlInheritanceBehaviorEnterprisePolicyTest

// Out-of-process-sandboxed-iframe (OOPSIF) tests.
//
// Test classes for isolating sandboxed iframes and documents in a different
// process from the rest of their site.
// See https://crbug.com/510122.
class SitePerProcessIsolatedSandboxedIframeTest
    : public SitePerProcessBrowserTest {
 public:
  SitePerProcessIsolatedSandboxedIframeTest() {
    feature_list_.InitAndEnableFeature(
        blink::features::kIsolateSandboxedIframes);
  }

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

class SitePerProcessNotIsolatedSandboxedIframeTest
    : public SitePerProcessBrowserTest {
 public:
  SitePerProcessNotIsolatedSandboxedIframeTest() {
    feature_list_.InitAndDisableFeature(
        blink::features::kIsolateSandboxedIframes);
  }

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

// A test class to allow testing isolated sandboxed iframes using the per-origin
// process model.
class SitePerProcessPerOriginIsolatedSandboxedIframeTest
    : public SitePerProcessBrowserTest {
 public:
  SitePerProcessPerOriginIsolatedSandboxedIframeTest() {
    feature_list_.InitWithFeaturesAndParameters(
        {{blink::features::kIsolateSandboxedIframes,
          {{"grouping", "per-origin"}}}},
        {/* disabled_features */});
  }

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

class SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest
    : public SitePerProcessIsolatedSandboxedIframeTest {
 public:
  SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest() {
    feature_list_.InitAndEnableFeature(
        blink::features::kIsolateSandboxedIframes);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SitePerProcessIsolatedSandboxedIframeTest::SetUpCommandLine(command_line);
    // Because this test derives from SitePerProcessBrowserTestBase which
    // calls IsolateAllSitesForTesting, we need to manually remove the
    // kSitePerProcess switch to simulate the environment where not all sites
    // automatically get isolation.
    command_line->RemoveSwitch(switches::kSitePerProcess);
  }

  void SetUpOnMainThread() override {
    SitePerProcessIsolatedSandboxedIframeTest::SetUpOnMainThread();

    // Override BrowserClient to disable strict site isolation.
    browser_client_ =
        std::make_unique<PartialSiteIsolationContentBrowserClient>();
    // The custom ContentBrowserClient below typically ensures that this test
    // runs without strict site isolation, but it's still possible to
    // inadvertently override this when running with --site-per-process on the
    // command line. This might happen on try bots, so these tests take this
    // into account to prevent failures, but this is not an intended
    // configuration for these tests, since isolating sandboxed iframes in
    // these tests depends on use of default SiteInstances.
    if (AreAllSitesIsolatedForTesting()) {
      LOG(WARNING) << "This test should be run without --site-per-process, "
                   << "as it's designed to exercise code paths when strict "
                   << "site isolation is turned off.";
    }
  }

  void TearDownOnMainThread() override {
    SitePerProcessIsolatedSandboxedIframeTest::TearDownOnMainThread();
    browser_client_.reset();
  }

  // A custom ContentBrowserClient to turn off strict site isolation, since
  // isolated sandboxed iframes behave differently in environments like Android
  // where it is not (generally) used. Note that kSitePerProcess is a
  // higher-layer feature, so we can't just disable it here.
  class PartialSiteIsolationContentBrowserClient
      : public ContentBrowserTestContentBrowserClient {
   public:
    bool ShouldEnableStrictSiteIsolation() override { return false; }
    bool DoesSiteRequireDedicatedProcess(
        BrowserContext* browser_context,
        const GURL& effective_site_url) override {
      return effective_site_url == GURL("http://isolated.com");
    }
  };

 private:
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<PartialSiteIsolationContentBrowserClient> browser_client_;
};

// A test class to allow testing isolated sandboxed iframes using the
// per-document grouping model.
class SitePerProcessPerDocumentIsolatedSandboxedIframeTest
    : public SitePerProcessBrowserTest {
 public:
  SitePerProcessPerDocumentIsolatedSandboxedIframeTest() {
    feature_list_.InitWithFeaturesAndParameters(
        {{blink::features::kIsolateSandboxedIframes,
          {{"grouping", "per-document"}}}},
        {/* disabled_features */});
  }

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

// The following test should not crash. In this test the
// kIsolateSandboxedIframes flag is forced off, so we don't need to verify
// the process isolation details, as is done in
// SitePerProcessIsolatedSandboxedIframeTest.SrcdocCspSandboxIsIsolated below.
// https://crbug.com/1319430
IN_PROC_BROWSER_TEST_P(SitePerProcessNotIsolatedSandboxedIframeTest,
                       SrcdocSandboxFlagsCheck) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed srcdoc child frame, with csp sandbox.
  EXPECT_TRUE(ExecJs(shell(),
                     "var frame = document.createElement('iframe'); "
                     "frame.csp = 'sandbox'; "
                     "frame.srcdoc = 'foo'; "
                     "document.body.appendChild(frame);"));
  ASSERT_TRUE(WaitForLoadStop(web_contents()));
}

// Test that a srcdoc iframe that receives its sandbox flags from the CSP
// attribute also gets process isolation. This test starts the same as
// SitePerProcessNotIsolatedSandboxedIframeTest.SrcdocSandboxFlagsCheck, but in
// this test the kIsolateSandboxedIframes flag is on, so we also verify that
// the process isolation has indeed occurred.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       SrcdocCspSandboxIsIsolated) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed srcdoc child frame, with csp sandbox.
  EXPECT_TRUE(ExecJs(shell(),
                     "var frame = document.createElement('iframe'); "
                     "frame.csp = 'sandbox'; "
                     "frame.srcdoc = 'foo'; "
                     "document.body.appendChild(frame);"));
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->current_frame_host()->active_sandbox_flags());
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
}

// A test to verify that an iframe that is sandboxed using the 'csp' attribute
// instead of the 'sandbox' attribute gets process isolation when the
// kIsolatedSandboxedIframes flag is enabled.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       CspIsolatedSandbox) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create csp-sandboxed child frame, same-origin.
  {
    EXPECT_TRUE(ExecJs(shell(),
                       "var frame = document.createElement('iframe'); "
                       "frame.csp = 'sandbox'; "
                       "frame.srcdoc = '<b>Hello!</b>'; "
                       "document.body.appendChild(frame);"));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->current_frame_host()->active_sandbox_flags());
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
}

// A test to verify that an iframe with a fully-restrictive sandbox is rendered
// in a separate process from its parent frame even if they have the same
// origin.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       IsolatedSandbox) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
}

// Test that a sandboxed srcdoc iframe loads properly when its parent's url is
// different from its site_url. The child should get its own SiteInstance with a
// site_url based on the full origin of the parent's original url.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                       SrcdocSandboxedFrameWithNonSiteParent) {
  GURL main_url(embedded_test_server()->GetURL("sub.a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed srcdoc child frame.
  {
    std::string js_str =
        "const frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frametree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);  // sub.a.com

  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);

  auto* parent_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  EXPECT_NE(parent_site_instance, child_site_instance);
  EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_FALSE(parent_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_EQ(embedded_test_server()->GetURL("sub.a.com", "/"),
            child_site_instance->GetSiteInfo().site_url());
  EXPECT_EQ(GURL("http://a.com/"),
            parent_site_instance->GetSiteInfo().site_url());
}

namespace {

GURL GetFrameBaseUrl(RenderFrameHostImpl* rfhi) {
  return GURL(EvalJs(rfhi, "document.baseURI").ExtractString());
}

GURL GetFrameBaseUrl(Shell* shell) {
  return GURL(EvalJs(shell, "document.baseURI").ExtractString());
}

}  // namespace

IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                       SrcdocSandboxedFrameInsideAboutBlank) {
  // Open main page on a.foo.com. It will be put in a site instance with site
  // url foo.com
  GURL main_url(embedded_test_server()->GetURL("a.foo.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();

  // Create an about:blank frame.
  {
    std::string js_str =
        "const frame = document.createElement('iframe'); "
        "frame.src = 'about:blank'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);

  // Create a sandboxed srcdoc frame inside the about:blank child.
  {
    std::string js_str =
        "const frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(child, js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grand_child = child->child_at(0);
  auto* grand_child_site_instance =
      grand_child->current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(grand_child_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_EQ(embedded_test_server()->GetURL("a.foo.com", "/"),
            grand_child_site_instance->GetSiteInfo().site_url());
  EXPECT_EQ(main_url, GetFrameBaseUrl(grand_child->current_frame_host()));
  EXPECT_EQ(main_url, grand_child->current_frame_host()->GetInheritedBaseUrl());
}

// Similar to SrcdocSandboxedFrameWithNonSiteParent, but this time the srcdoc
// is opened from b.foo.com which is loaded in the SiteInstance that was
// created for a.foo.com, so the SiteInstance cannot be used to specify the
// origin the srcdoc should use, namely b.foo.com.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                       SrcdocSandboxedFrameWithNonSiteParent2) {
  GURL main_url(embedded_test_server()->GetURL("a.foo.com", "/title1.html"));
  GURL sibling_url(embedded_test_server()->GetURL("b.foo.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();

  // Open a new window that will share the SiteInstance of the main window.
  Shell* new_shell = OpenPopup(root, sibling_url, "");
  FrameTreeNode* sibling =
      static_cast<WebContentsImpl*>(new_shell->web_contents())
          ->GetPrimaryFrameTree()
          .root();
  EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
            sibling->current_frame_host()->GetSiteInstance());

  {
    std::string js_str =
        "const frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(new_shell, js_str));
    ASSERT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  }
  ASSERT_EQ(1U, sibling->child_count());
  FrameTreeNode* child = sibling->child_at(0);  // b.foo.com

  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);

  auto* sibling_site_instance =
      sibling->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  EXPECT_NE(sibling_site_instance, child_site_instance);
  EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_FALSE(sibling_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_EQ(embedded_test_server()->GetURL("b.foo.com", "/"),
            child_site_instance->GetSiteInfo().site_url());
  EXPECT_EQ(GURL("http://foo.com/"),
            sibling_site_instance->GetSiteInfo().site_url());
}

// Test that sandboxed iframes that are same-site with their parent but
// cross-origin from each other are put in different processes from each other,
// when the 'per-origin' isolation grouping is active for
// kIsolateSandboxedIframes. (In 'per-site' isolation mode they would be in the
// same process.)
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                       CrossOriginIsolatedSandboxedIframes) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The children need to have the same origin as the parent, but be cross
  // origin from each other.
  GURL same_origin_child_url(main_url);
  GURL cross_origin_child_url(
      embedded_test_server()->GetURL("sub.a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frames, both same-origin and cross-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame1 = document.createElement('iframe'); "
        "frame1.sandbox = ''; "
        "frame1.src = '%s'; "
        "document.body.appendChild(frame1); "
        "var frame2 = document.createElement('iframe'); "
        "frame2.sandbox = ''; "
        "frame2.src = '%s'; "
        "document.body.appendChild(frame2);",
        same_origin_child_url.spec().c_str(),
        cross_origin_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(2U, root->child_count());

  FrameTreeNode* child1 = root->child_at(0);  // a.com
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child1->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child1->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child1->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());

  FrameTreeNode* child2 = root->child_at(1);  // sub.a.com
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child2->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child2->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child2->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  // This is the key result for this test: the sandboxed iframes for 'a.com' and
  // 'sub.a.com' should be in different SiteInstances.
  auto* child1_site_instance1 = child1->current_frame_host()->GetSiteInstance();
  auto* child2_site_instance1 = child2->current_frame_host()->GetSiteInstance();
  EXPECT_NE(child1_site_instance1, child2_site_instance1);
  EXPECT_NE(child1_site_instance1->GetProcess(),
            child2_site_instance1->GetProcess());
}

// Test that, while using 'per-origin' isolation grouping, navigating a
// sandboxed iframe from 'a.foo.com' to 'b.foo.com' results in the sandbox using
// two different SiteInstances.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                       CrossOriginNavigationSwitchesSiteInstances) {
  GURL main_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
  GURL cross_origin_child_url(
      embedded_test_server()->GetURL("a.foo.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed cross-origin child frame.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        cross_origin_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());

  FrameTreeNode* child = root->child_at(0);  // a.foo.com
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  scoped_refptr<SiteInstanceImpl> site_instance_root =
      root->current_frame_host()->GetSiteInstance();
  scoped_refptr<SiteInstanceImpl> site_instance1 =
      child->current_frame_host()->GetSiteInstance();
  EXPECT_NE(site_instance_root, site_instance1);
  EXPECT_TRUE(site_instance1->GetSiteInfo().is_sandboxed());
  EXPECT_FALSE(site_instance_root->GetSiteInfo().is_sandboxed());

  // Navigate sandboxed frame cross-origin to b.foo.com.
  EXPECT_TRUE(NavigateIframeToURL(
      shell()->web_contents(), "test_frame",
      GURL(embedded_test_server()->GetURL("b.foo.com", "/title1.html"))));

  scoped_refptr<SiteInstanceImpl> site_instance2 =
      child->current_frame_host()->GetSiteInstance();
  EXPECT_NE(site_instance_root, site_instance2);
  EXPECT_NE(site_instance1, site_instance2);
  EXPECT_NE(site_instance1->GetProcess(), site_instance2->GetProcess());
}

// Test that navigating cross-origin from a non-sandboxed iframe to a CSP
// sandboxed iframe results in switching to a new SiteInstance in a different
// process.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                       CrossOriginNavigationToCSPSwitchesSiteInstances) {
  GURL main_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
  GURL cross_origin_child_url(
      embedded_test_server()->GetURL("a.foo.com", "/title1.html"));
  GURL cross_origin_csp_child_url(
      embedded_test_server()->GetURL("b.foo.com",
                                     "/set-header?"
                                     "Content-Security-Policy: sandbox "));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create non-sandboxed cross-origin child frame.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        cross_origin_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());

  FrameTreeNode* child = root->child_at(0);  // a.foo.com
  scoped_refptr<SiteInstanceImpl> site_instance_root =
      root->current_frame_host()->GetSiteInstance();
  scoped_refptr<SiteInstanceImpl> site_instance1 =
      child->current_frame_host()->GetSiteInstance();
  EXPECT_EQ(site_instance_root, site_instance1);
  EXPECT_FALSE(site_instance1->GetSiteInfo().is_sandboxed());

  // Navigate child frame cross-origin to CSP-isolated b.foo.com.
  EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame",
                                  cross_origin_csp_child_url));

  // The child frame should now have a different SiteInstance and process than
  // it did before the navigation.
  scoped_refptr<SiteInstanceImpl> site_instance2 =
      child->current_frame_host()->GetSiteInstance();
  EXPECT_NE(site_instance1, site_instance2);
  EXPECT_NE(site_instance1->GetProcess(), site_instance2->GetProcess());
  EXPECT_TRUE(site_instance2->GetSiteInfo().is_sandboxed());
}

// Check that two same-site sandboxed iframes in unrelated windows share the
// same process due to subframe process reuse.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       SandboxProcessReuse) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  std::string js_str = base::StringPrintf(
      "var frame = document.createElement('iframe'); "
      "frame.sandbox = ''; "
      "frame.src = '%s'; "
      "document.body.appendChild(frame);",
      child_url.spec().c_str());
  EXPECT_TRUE(ExecJs(shell(), js_str));
  ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());

  // Set up an unrelated window with the same frame hierarchy.
  Shell* new_shell = CreateBrowser();
  EXPECT_TRUE(NavigateToURL(new_shell, main_url));
  FrameTreeNode* new_root =
      static_cast<WebContentsImpl*>(new_shell->web_contents())
          ->GetPrimaryFrameTree()
          .root();
  EXPECT_TRUE(ExecJs(new_shell, js_str));
  ASSERT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  FrameTreeNode* new_child = new_root->child_at(0);
  EXPECT_TRUE(new_child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(new_root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());

  // Check that the two sandboxed subframes end up in separate
  // BrowsingInstances but in the same process.
  EXPECT_FALSE(
      new_child->current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
          child->current_frame_host()->GetSiteInstance()));
  EXPECT_EQ(new_child->current_frame_host()->GetProcess(),
            child->current_frame_host()->GetProcess());
}

// A test to verify that when an iframe has two sibling subframes, each with a
// fully-restrictive sandbox, that each of the three gets its own process
// even though they are all same-origin.
// Note: using "sandbox = ''" in this and the following tests creates fully
// restricted sandboxes, which will include the kOrigin case we are interested
// in.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       IsolatedSandboxSiblingSubframes) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame1 = document.createElement('iframe'); "
        "frame1.sandbox = ''; "
        "frame1.src = '%s'; "
        "document.body.appendChild(frame1); "
        "var frame2 = document.createElement('iframe'); "
        "frame2.sandbox = ''; "
        "frame2.src = '%s'; "
        "document.body.appendChild(frame2);",
        child_url.spec().c_str(), child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(2U, root->child_count());
  FrameTreeNode* child1 = root->child_at(0);
  FrameTreeNode* child2 = root->child_at(1);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child1->effective_frame_policy().sandbox_flags);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child2->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child1->current_frame_host()->GetSiteInstance());
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child2->current_frame_host()->GetSiteInstance());
  // Because the siblings are same-site to each other (in fact, same origin) we
  // expect them to share a process when sandboxed.
  EXPECT_EQ(child1->current_frame_host()->GetSiteInstance(),
            child2->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child1->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_TRUE(child2->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
}

IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       IsolatedSandboxSrcdocSubframe) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, with srcdoc content.
  std::string child_inner_text("srcdoc sandboxed subframe");
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.srcdoc = '%s'; "
        "document.body.appendChild(frame);",
        child_inner_text.c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  // Verify that the child has only the 'allow-scripts' permission set.
  EXPECT_EQ(child->effective_frame_policy().sandbox_flags,
            network::mojom::WebSandboxFlags::kAll &
                ~network::mojom::WebSandboxFlags::kScripts &
                ~network::mojom::WebSandboxFlags::kAutomaticFeatures);
  EXPECT_EQ(std::string(url::kAboutSrcdocURL),
            child->current_frame_host()->GetLastCommittedURL());
  EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
  // Verify that the child's precursor origin matches 'a.com'. Note: we create
  // the expected value using `main_url` so that the test server port will be
  // correctly matched.
  EXPECT_EQ(url::SchemeHostPort(main_url),
            child->current_origin().GetTupleOrPrecursorTupleIfOpaque());
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
  {
    std::string js_str("document.body.innerText;");
    EXPECT_EQ(child_inner_text, EvalJs(child->current_frame_host(), js_str));
  }
}

// A test to make sure that about:blank in a sandboxed iframe doesn't get
// process isolation. If it did, it would be impossible for the parent to inject
// any content, and it would be stuck as empty content.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       NotIsolatedSandboxAboutBlankSubframe) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, with about:blank content.
  {
    std::string js_str(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'child_frame'; "
        "frame.sandbox = ''; "
        "frame.src = 'about:blank'; "
        "document.body.appendChild(frame);");
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  // Verify that the child has no permissions set.
  EXPECT_EQ(child->effective_frame_policy().sandbox_flags,
            network::mojom::WebSandboxFlags::kAll);
  EXPECT_EQ(GURL(url::kAboutBlankURL),
            child->current_frame_host()->GetLastCommittedURL());
  EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
  // Verify that the child's precursor origin matches 'a.com'. Note: we create
  // the expected value using `main_url` so that the test server port will be
  // correctly matched.
  EXPECT_EQ(url::SchemeHostPort(main_url),
            child->current_origin().GetTupleOrPrecursorTupleIfOpaque());
  // The child needs to be in the parent's SiteInstance.
  EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());

  // Navigate to a page that should get process isolation.
  GURL isolated_child_url(
      embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateFrameToURL(child, isolated_child_url));
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());

  // Navigate back to about:blank, and verify it's put back into the parent's
  // SiteInstance.
  scoped_refptr<SiteInstanceImpl> child_previous_site_instance =
      child->current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child_frame",
                                  GURL("about:blank")));
  EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_NE(child_previous_site_instance,
            child->current_frame_host()->GetSiteInstance());
  EXPECT_FALSE(child->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
}

// Test to make sure that javascript: urls don't execute in a sandboxed iframe.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       SandboxedIframeWithJSUrl) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame with a javascript: URL.
  std::string js_url_str("javascript:\"foo\"");
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.sandbox = 'allow-scripts'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        js_url_str.c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Verify parent and child frames share a SiteInstance. A sandboxed iframe
  // with a javascript: url shouldn't get its own process.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());

  // Verify that the javascript: url did not execute. This is expected
  // regardless of IsolatedSandboxedIframes since sandboxed iframes get opaque
  // origins, and javascript: urls don't execute in opaque origins.
  EXPECT_TRUE(
      EvalJs(child->current_frame_host(), "document.body.innerHTML == ''")
          .ExtractBool());
}

// Test to make sure that an iframe with a data:url is process isolated.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       SandboxedIframeWithDataURLIsIsolated) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame with a data URL.
  std::string data_url_str("data:text/html,dataurl");
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        data_url_str.c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Verify parent and child frames don't share a SiteInstance
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());
}

// Test to make sure that an iframe with a data:url is appropriately sandboxed.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       SandboxedIframeWithDataURL) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create non-sandboxed child frame with a data URL.
  std::string data_url_str("data:text/html,dataurl");
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        data_url_str.c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Verify parent and child frames share a SiteInstance
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());

  // Now make subframe sandboxed.
  {
    std::string js_str(
        "var frame = document.getElementById('test_frame'); "
        "frame.sandbox = ''; ");
    EXPECT_TRUE(ExecJs(shell(), js_str));
  }
  NavigateFrameToURL(child,
                     embedded_test_server()->GetURL("b.com", "/title1.html"));
  // Child should now be in a different SiteInstance.
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());

  // Go back and ensure the data: URL committed in the same SiteInstance as the
  // original navigation.
  EXPECT_TRUE(web_contents()->GetController().CanGoBack());
  {
    TestFrameNavigationObserver frame_observer(child);
    web_contents()->GetController().GoBack();
    frame_observer.WaitForCommit();
  }
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_EQ(GURL(data_url_str),
            child->current_frame_host()->GetLastCommittedURL());
}

// Test to make sure that a sandboxed child iframe with a data url and a
// sandboxed parent end up in the same SiteInstance.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       SandboxedParentWithSandboxedChildWithDataURL) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  std::string parent_url_str = main_url.spec();
  std::string data_url_str("data:text/html,dataurl");
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Allow "parent" to have the allow-scripts permissions so it can create
  // a child.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        parent_url_str.c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());

  // Give the grandchild the allow-scripts permissions so it matches the child.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        data_url_str.c_str());
    EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grandchild = child->child_at(0);
  EXPECT_EQ(child->current_frame_host()->GetSiteInstance(),
            grandchild->current_frame_host()->GetSiteInstance());
  EXPECT_EQ(GURL(data_url_str),
            grandchild->current_frame_host()->GetLastCommittedURL());
}

// Test to make sure that a sandboxed iframe with a (not-explicitly) sandboxed
// subframe ends up in the same SiteInstance/process as its subframe.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       IsolatedSandboxWithNonSandboxedSubframe) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(
      embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check child vs. parent.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());

  // Check grandchild vs. child.
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grand_child = child->child_at(0);
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            grand_child->effective_frame_policy().sandbox_flags);
  EXPECT_EQ(child->current_frame_host()->GetSiteInstance(),
            grand_child->current_frame_host()->GetSiteInstance());
}

// A test to verify that an iframe with a fully-restrictive sandbox is rendered
// in the same process as its parent frame when the parent frame is in a
// default SiteInstance.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    NotIsolatedSandbox) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  auto* parent_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  EXPECT_FALSE(parent_site_instance->RequiresDedicatedProcess());
  EXPECT_EQ(parent_site_instance, child_site_instance);
  EXPECT_FALSE(child_site_instance->GetSiteInfo().is_sandboxed());
}

// Similar to the NotIsolatedSandbox test, but using a site that requires a
// dedicated process, and thus resulting in a separate process for the sandboxed
// iframe.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    IsolatedSandbox) {
  // Specify an isolated.com site to get the main frame into a dedicated
  // process.
  GURL main_url(embedded_test_server()->GetURL("isolated.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  auto* parent_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  EXPECT_TRUE(parent_site_instance->RequiresDedicatedProcess());
  EXPECT_NE(parent_site_instance, child_site_instance);
  EXPECT_NE(parent_site_instance->GetProcess(),
            child_site_instance->GetProcess());
  EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed());
}

// In this test, a main frame requests sandbox isolation for a site that would
// not normally be given a dedicated process. This causes the sandbox isolation
// request to fail.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    CSPSandboxedMainFrame) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/set-header?Content-Security-Policy: sandbox allow-scripts"));
  GURL child_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  auto* parent_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  EXPECT_FALSE(parent_site_instance->RequiresDedicatedProcess());
  EXPECT_FALSE(parent_site_instance->GetSiteInfo().is_sandboxed());
  // TODO(wjmaclean): It seems weird that the
  // effective_frame_policy().sandbox_flags don't get set in this case. Maybe
  // worth investigating this at some point. https://crbug.com/1346723
  EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
            root->effective_frame_policy().sandbox_flags);
  // Since the parent is sandboxed, the child is same process to it.
  EXPECT_EQ(parent_site_instance, child_site_instance);
}

// Same as CSPSandboxedMainframe, but this time the site is isolatable on its
// own, so it gets the sandbox attribute via the CSP header.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    CSPSandboxedMainframeIsolated) {
  GURL main_url(embedded_test_server()->GetURL(
      "isolated.com",
      "/set-header?Content-Security-Policy: sandbox allow-scripts"));
  GURL child_url(
      embedded_test_server()->GetURL("isolated.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  auto* parent_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(parent_site_instance->RequiresDedicatedProcess());
  EXPECT_TRUE(parent_site_instance->GetSiteInfo().is_sandboxed());
  // TODO(wjmaclean): It seems weird that the
  // effective_frame_policy().sandbox_flags don't get set in this case. Maybe
  // worth investigating this at some point. https://crbug.com/1346723
  EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
            root->effective_frame_policy().sandbox_flags);
  // Since the parent is sandboxed, the child is same process to it.
  // Note: this assumes that we are running per-site isolation mode for isolated
  // sandboxed iframes.
  EXPECT_EQ(parent_site_instance, child_site_instance);
}

// Test to verify which IsolationContext is used when a BrowsingInstance swap is
// performed during a navigation.
// Note: this test does not work as hoped, see comment before the final
// expectation of the test.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    MainFrameBrowsingInstanceSwap) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  scoped_refptr<SiteInstanceImpl> site_instance_a =
      web_contents()->GetSiteInstance();
  EXPECT_FALSE(site_instance_a->GetSiteInfo().is_sandboxed());

  // Force BrowsingInstance swap to a URL with a CSP sandbox header.
  GURL isolated_url(embedded_test_server()->GetURL(
      "b.com", "/set-header?Content-Security-Policy: sandbox"));
  SiteInstance::StartIsolatingSite(
      shell()->web_contents()->GetController().GetBrowserContext(),
      isolated_url,
      content::ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
  EXPECT_TRUE(NavigateToURL(shell(), isolated_url));
  scoped_refptr<SiteInstanceImpl> site_instance_b =
      web_contents()->GetSiteInstance();
  EXPECT_NE(site_instance_a, site_instance_b);
  EXPECT_NE(site_instance_a->GetIsolationContext().browsing_instance_id(),
            site_instance_b->GetIsolationContext().browsing_instance_id());
  // The SiteInstance is not considered sandboxed even in the new
  // BrowsingInstance. This is not the result we wanted, but without a massive
  // amount of work it's the best we can do. This happens because
  // NavigationRequest::GetUrlInfo() doesn't know (at the time
  // NavigationRequest::OnResponseStarted() calls
  // RenderFrameHostManager::GetFrameHostForNavigation()) that there will be
  // a BrowsingInstance swap, and it doesn't have access to the new
  // BrowsingInstance (IsolationContext) when deciding to add the `is_sandboxed`
  // attribute to UrlInfoInit.
  // This is an edge case we can live with since it only happens with the
  // main frame getting a CSP sandbox, and the main frame does get its own
  // process regardless in this case.
  EXPECT_FALSE(site_instance_b->GetSiteInfo().is_sandboxed());
}

IN_PROC_BROWSER_TEST_P(
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    MainFrameWithSandboxedOpener) {
  // Specify an isolated.com site to get the main frame into a dedicated
  // process.
  GURL main_url(embedded_test_server()->GetURL("isolated.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame, same-origin.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts allow-popups'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  auto* parent_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  network::mojom::WebSandboxFlags expected_flags =
      network::mojom::WebSandboxFlags::kAll &
      ~network::mojom::WebSandboxFlags::kScripts &
      ~network::mojom::WebSandboxFlags::kPopups &
      ~network::mojom::WebSandboxFlags::kAutomaticFeatures &
      ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols;
  EXPECT_EQ(expected_flags, child->effective_frame_policy().sandbox_flags);
  EXPECT_TRUE(parent_site_instance->RequiresDedicatedProcess());
  EXPECT_NE(parent_site_instance, child_site_instance);
  EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed());

  // Sandboxed child calls window.open.
  Shell* new_shell = OpenPopup(child, child_url, "");
  EXPECT_TRUE(new_shell);
  FrameTreeNode* new_root =
      static_cast<WebContentsImpl*>(new_shell->web_contents())
          ->GetPrimaryFrameTree()
          .root();
  auto* new_window_site_instance =
      new_root->current_frame_host()->GetSiteInstance();
  EXPECT_TRUE(new_window_site_instance->RequiresDedicatedProcess());
  EXPECT_TRUE(new_window_site_instance->GetSiteInfo().is_sandboxed());
  // Note: this assumes per-site mode for sandboxed iframe isolation. If we
  // settle on per-document mode, this will change to EXPECT_NE.
  EXPECT_EQ(child_site_instance, new_window_site_instance);
}

// Test that sandboxed iframes that are same-site with their parent but
// same-origin to each other are put in different processes from each other,
// when the 'per-document' isolation grouping is active for
// kIsolateSandboxedIframes. (In 'per-site' and 'per-origin' isolation groupings
// they would be in the same process.)
IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest,
                       SameOriginIsolatedSandboxedIframes) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The children need to be same origin to each other, and be (at least)
  // same-site to the parent.
  GURL same_origin_child_url(
      embedded_test_server()->GetURL("sub.a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frames.
  {
    std::string js_str = base::StringPrintf(
        "var frame1 = document.createElement('iframe'); "
        "frame1.sandbox = ''; "
        "frame1.src = '%s'; "
        "document.body.appendChild(frame1); "
        "var frame2 = document.createElement('iframe'); "
        "frame2.sandbox = ''; "
        "frame2.src = '%s'; "
        "document.body.appendChild(frame2);",
        same_origin_child_url.spec().c_str(),
        same_origin_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(2U, root->child_count());

  FrameTreeNode* child1 = root->child_at(0);  // sub.a.com
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child1->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child1->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child1->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  EXPECT_FALSE(root->current_frame_host()
                   ->GetSiteInstance()
                   ->GetSiteInfo()
                   .is_sandboxed());

  FrameTreeNode* child2 = root->child_at(1);  // sub.a.com
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child2->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
            child2->current_frame_host()->GetSiteInstance());
  EXPECT_TRUE(child2->current_frame_host()
                  ->GetSiteInstance()
                  ->GetSiteInfo()
                  .is_sandboxed());
  // This is the key result for this test: the sandboxed iframes for both child
  // frames should be in different SiteInstances, even though they are
  // same-origin.
  auto* child1_site_instance = child1->current_frame_host()->GetSiteInstance();
  auto* child2_site_instance = child2->current_frame_host()->GetSiteInstance();
  EXPECT_EQ(child1_site_instance->GetSiteInfo().site_url(),
            child2_site_instance->GetSiteInfo().site_url());
  EXPECT_NE(child1_site_instance->GetSiteInfo().unique_sandbox_id(),
            child2_site_instance->GetSiteInfo().unique_sandbox_id());
  EXPECT_NE(child1_site_instance, child2_site_instance);
  EXPECT_NE(child1_site_instance->GetProcess(),
            child2_site_instance->GetProcess());
}

// This test ensures that nested srcdoc iframes get correct base urls.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       NestedSrcdocIframes) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed srcdoc child frame.
  {
    std::string js_str =
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  // Make sure the parent's base url propagates properly to the child.
  GURL parent_base_url = GetFrameBaseUrl(root->current_frame_host());
  GURL child_base_url = GetFrameBaseUrl(child->current_frame_host());
  // Verify child inherited base url from parent as expected.
  EXPECT_EQ(parent_base_url, child_base_url);
  EXPECT_EQ(parent_base_url,
            child->current_frame_host()->GetInheritedBaseUrl());

  // Switch the base url of the root.
  GURL new_root_base_url("http://b.com/");
  {
    std::string js_str = base::StringPrintf(
        "var base_element = document.createElement('base'); "
        "base_element.href = '%s'; "
        "document.head.appendChild(base_element);",
        new_root_base_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    EXPECT_EQ(new_root_base_url, GetFrameBaseUrl(shell()));
  }

  // Create sandboxed srcdoc grandchild frame.
  {
    std::string js_str =
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grandchild = child->child_at(0);

  // Make sure the child's snapshotted base url propagates properly to the
  // grandchild. And make sure child's snapshotted base url hasn't changed with
  // the creation of the child.
  EXPECT_EQ(parent_base_url, GetFrameBaseUrl(child->current_frame_host()));
  EXPECT_EQ(parent_base_url,
            child->current_frame_host()->GetInheritedBaseUrl());
  EXPECT_EQ(parent_base_url, GetFrameBaseUrl(grandchild->current_frame_host()));
  EXPECT_EQ(parent_base_url,
            grandchild->current_frame_host()->GetInheritedBaseUrl());
}

// Test to verify that nested sandboxed iframes aren't put in the same
// SiteInstance.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest,
                       NestedIsolatedSandboxedIframes) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
  // The children need to be same origin to each other, and be (at least)
  // same-site to the parent.
  GURL same_origin_child_url(
      embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        same_origin_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);

  // Create sandboxed grand-child frame.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        same_origin_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grandchild = child->child_at(0);

  // Check frame tree.
  auto* child_site_instance = child->current_frame_host()->GetSiteInstance();
  auto* grandchild_site_instance =
      grandchild->current_frame_host()->GetSiteInstance();
  EXPECT_NE(child_site_instance, grandchild_site_instance);
  EXPECT_NE(child_site_instance->GetProcess(),
            grandchild_site_instance->GetProcess());
  EXPECT_NE(child_site_instance->GetSiteInfo().unique_sandbox_id(),
            grandchild_site_instance->GetSiteInfo().unique_sandbox_id());
}

// Verify same-document navigations in a sandboxed iframe stay in the same
// SiteInstance, and that the unique_sandbox_id changes for any
// non-same-document navigation.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest,
                       SandboxedIframeNavigations) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to be same-site to the parent.
  GURL same_site_child_url(
      embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.sandbox = ''; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        same_site_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());

  FrameTreeNode* child = root->child_at(0);
  scoped_refptr<SiteInstanceImpl> root_site_instance =
      root->current_frame_host()->GetSiteInstance();
  EXPECT_FALSE(root_site_instance->GetSiteInfo().is_sandboxed());

  scoped_refptr<SiteInstanceImpl> child_site_instance1 =
      child->current_frame_host()->GetSiteInstance();
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child->effective_frame_policy().sandbox_flags);
  EXPECT_NE(root_site_instance, child_site_instance1);
  EXPECT_TRUE(child_site_instance1->GetSiteInfo().is_sandboxed());

  // Navigate child same-site, same-origin, same-document.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.getElementById('test_frame'); "
        "frame.src = '%s#foo';",
        same_site_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  scoped_refptr<SiteInstanceImpl> child_site_instance1a =
      child->current_frame_host()->GetSiteInstance();
  // Since the sandboxed iframe is navigated same-document, we expect the
  // SiteInstance to remain the same.
  EXPECT_EQ(child_site_instance1, child_site_instance1a);

  // Navigate child same-site, same-origin, cross-document.
  GURL same_site_child_url2(
      embedded_test_server()->GetURL("a.com", "/title2.html"));
  ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame",
                                  same_site_child_url2));
  scoped_refptr<SiteInstanceImpl> child_site_instance2 =
      child->current_frame_host()->GetSiteInstance();
  // Since the sandboxed iframe is navigated same-site but to a different
  // document, we expect the SiteInstance to change.
  EXPECT_NE(child_site_instance1, child_site_instance2);
  EXPECT_NE(child_site_instance1->GetSiteInfo().unique_sandbox_id(),
            child_site_instance2->GetSiteInfo().unique_sandbox_id());

  // Navigate child cross-site.
  GURL cross_site_child_url(
      embedded_test_server()->GetURL("b.com", "/title1.html"));
  ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame",
                                  cross_site_child_url));
  scoped_refptr<SiteInstanceImpl> child_site_instance3 =
      child->current_frame_host()->GetSiteInstance();
  // Since the sandboxed iframe is navigated same-site, but cross-document
  // we expect the SiteInstance to change.
  EXPECT_NE(child_site_instance1, child_site_instance3);
  EXPECT_NE(child_site_instance1->GetSiteInfo().unique_sandbox_id(),
            child_site_instance3->GetSiteInfo().unique_sandbox_id());
}

// Verify that a sandboxed iframe with an about:blank subframe shares its
// SiteInstance with that subframe. Further, if the about:blank subframe
// navigates cross-site, it gets a new SiteInstance.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest,
                       SandboxedAboutBlankSubframes) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to be same-site to the parent.
  GURL same_site_child_url(
      embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create sandboxed child frame.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.sandbox = 'allow-scripts'; "
        "frame.src = '%s'; "
        "document.body.appendChild(frame);",
        same_site_child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame-tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(1U, root->child_count());

  FrameTreeNode* child = root->child_at(0);

  // Add about:blank subframe to child. Verify that it stays in its parent's
  // SiteInstance.
  {
    std::string js_str =
        "var frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.src = 'about:blank'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grandchild = child->child_at(0);

  scoped_refptr<SiteInstanceImpl> child_site_instance =
      child->current_frame_host()->GetSiteInstance();
  scoped_refptr<SiteInstanceImpl> grandchild_site_instance1 =
      grandchild->current_frame_host()->GetSiteInstance();
  EXPECT_EQ(child_site_instance, grandchild_site_instance1);

  // Navigate the grandchild same-site but cross-document and verify it gets a
  // new sandboxing id (and therefore a new SiteInstance).
  GURL cross_document_child_url(
      embedded_test_server()->GetURL("a.com", "/title2.html"));
  ASSERT_TRUE(NavigateToURLFromRenderer(grandchild, cross_document_child_url));
  scoped_refptr<SiteInstanceImpl> grandchild_site_instance2 =
      grandchild->current_frame_host()->GetSiteInstance();
  EXPECT_NE(child_site_instance, grandchild_site_instance2);
  EXPECT_NE(child_site_instance->GetSiteInfo().unique_sandbox_id(),
            grandchild_site_instance2->GetSiteInfo().unique_sandbox_id());
}

// Test to verify that sibling srcdoc sandboxed iframes are placed in separate
// SiteInstances in the per-document grouping model.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest,
                       SiblingSrcdocIframesGetDifferentProcesses) {
  // Create any main frame.
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create two identical sibling srcdoc sandboxed iframes.
  // Create sandboxed child frame, with srcdoc content.
  {
    std::string js_str =
        "var frame1 = document.createElement('iframe'); "
        "frame1.sandbox = ''; "
        "frame1.srcdoc = 'srcdoc sandboxed subframe1'; "
        "var frame2 = document.createElement('iframe'); "
        "frame2.sandbox = ''; "
        "frame2.srcdoc = 'srcdoc sandboxed subframe2'; "
        "document.body.appendChild(frame1); "
        "document.body.appendChild(frame2);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Check frame tree.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_EQ(2U, root->child_count());

  FrameTreeNode* child1 = root->child_at(0);  // frame1
  FrameTreeNode* child2 = root->child_at(1);  // frame2
  auto* root_site_instance = root->current_frame_host()->GetSiteInstance();
  auto* child1_site_instance = child1->current_frame_host()->GetSiteInstance();
  auto* child2_site_instance = child2->current_frame_host()->GetSiteInstance();

  EXPECT_FALSE(root_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child1->effective_frame_policy().sandbox_flags);
  EXPECT_TRUE(child1_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
            child2->effective_frame_policy().sandbox_flags);
  EXPECT_TRUE(child2_site_instance->GetSiteInfo().is_sandboxed());
  EXPECT_NE(root_site_instance, child1_site_instance);
  EXPECT_NE(root_site_instance, child2_site_instance);
  // Verify siblings have different SiteInstances and processes.
  EXPECT_NE(child1_site_instance, child2_site_instance);
  EXPECT_NE(child1_site_instance->GetSiteInfo().unique_sandbox_id(),
            child2_site_instance->GetSiteInfo().unique_sandbox_id());
  EXPECT_NE(child1_site_instance->GetProcess(),
            child2_site_instance->GetProcess());
}

// Test that changes to an iframe's srcdoc attribute propagate through the
// browser and are stored/cleared on the RenderFrameHost as needed.
IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, SrcdocIframe) {
  StartEmbeddedServer();

  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create srcdoc iframe.
  {
    std::string js_str =
        "const frame = document.createElement('iframe'); "
        "frame.id = 'test_frame'; "
        "frame.srcdoc = 'srcdoc test content'; "
        "document.body.append(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  // Verify content on RenderFrameHost.
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(GURL(url::kAboutSrcdocURL), child->current_url());
  EXPECT_EQ("srcdoc test content", child->srcdoc_value());
  if (SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) {
    EXPECT_EQ(main_url, GetFrameBaseUrl(child->parent()));
  }
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));

  // Reset the srcdoc attribute, and verify the FrameTreeNode is updated
  // accordingly.
  {
    std::string js_str =
        "const frame = document.getElementById('test_frame'); "
        "frame.removeAttribute('srcdoc');";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
    // The next line serves two purposes. First, it confirms via JS that the
    // srcdoc attribute has indeed been removed. Secondly, and more importantly,
    // it synchronizes the mojo pipe where the two DidChangeSrcDoc calls occur;
    // the first call sets the srcdoc value to '' and the second call removes
    // the BaseUrl. Waiting for loadstop is insufficient to catch the second
    // call.
    EXPECT_EQ(
        false,
        EvalJs(shell(),
               "document.getElementById('test_frame').hasAttribute('srcdoc')"));
  }
  EXPECT_EQ(GURL(url::kAboutBlankURL), child->current_url());
  EXPECT_EQ("", child->srcdoc_value());
  // The base url is set on the parent, and not cleared with the child's srcdoc
  // information.
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Repeat the srcdoc attribute tests from above, but this time using
  // src='about:srcdoc' to make the frame srcdoc.

  {
    std::string js_str =
        "const frame = document.createElement('iframe'); "
        "frame.id = 'test_frame2'; "
        "frame.src = 'about:srcdoc'; "
        "document.body.append(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  ASSERT_EQ(2U, root->child_count());
  FrameTreeNode* child2 = root->child_at(1);
  EXPECT_EQ(GURL(url::kAboutSrcdocURL), child2->current_url());
  EXPECT_EQ("", child2->srcdoc_value());
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  EXPECT_EQ(main_url, GetFrameBaseUrl(child2->parent()));
  EXPECT_EQ(main_url, GetFrameBaseUrl(child2->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child2->current_frame_host()->GetInheritedBaseUrl());
  }

  // Reset the src attribute, and verify the FrameTreeNode is updated
  // accordingly.
  {
    std::string js_str =
        "const frame = document.getElementById('test_frame2'); "
        "frame.removeAttribute('src');";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
    // The next line serves two purposes. First, it confirms via JS that the
    // srcdoc attribute has indeed been removed. Secondly, and more importantly,
    // it synchronizes the mojo pipe where the two DidChangeSrcDoc calls occur;
    // the first call sets the srcdoc value to '' and the second call removes
    // the BaseUrl. Waiting for loadstop is insufficient to catch the second
    // call.
    EXPECT_EQ(
        false,
        EvalJs(shell(),
               "document.getElementById('test_frame').hasAttribute('src')"));
  }
  EXPECT_EQ(GURL(url::kAboutBlankURL), child2->current_url());
  EXPECT_EQ("", child2->srcdoc_value());
  EXPECT_EQ(GURL("about:blank"), child->current_url());
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }
}

// Test that when a frame changes its base url by manipulating its
// base-element, and then undoes those changes, that the browser is properly
// notified.
IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, FrameChangesBaseUrl) {
  StartEmbeddedServer();

  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  FrameTreeNode* root_ftn =
      static_cast<WebContentsImpl*>(shell()->web_contents())
          ->GetPrimaryFrameTree()
          .root();

  // Initially we don't expect the baseurl value to have been sent from the
  // renderer.
  EXPECT_EQ(main_url, GetFrameBaseUrl(root_ftn->current_frame_host()));
  EXPECT_EQ(GURL(), root_ftn->current_frame_host()->GetInheritedBaseUrl());

  // The page modifies its base element to set a non-standard value the browser
  // knows nothing about, so the renderer sends it to the browser.
  {
    std::string js_str =
        "const base_element = document.createElement('base'); "
        "base_element.id = 'base_element'; "
        "base_element.href = 'http://foo.com'; "
        "document.head.append(base_element);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    // The following JS is useful, but also forces synchronization on the mojo
    // pipe that sends the srcdoc base url data.
    EXPECT_EQ(
        true,
        EvalJs(shell(),
               "document.getElementById('base_element').hasAttribute('href')"));
  }
  GURL foo_url("http://foo.com");
  EXPECT_EQ(foo_url, GetFrameBaseUrl(root_ftn->current_frame_host()));
  EXPECT_EQ(GURL(), root_ftn->current_frame_host()->GetInheritedBaseUrl());

  // The page removes its base element, restoring the standard baseurl value.
  // The previous value sent to the browser should be reset.
  {
    EXPECT_TRUE(ExecJs(shell(), "document.querySelector('base').remove();"));
    // The following JS is useful, but also forces synchronization on the mojo
    // pipe that sends the srcdoc base url data.
    EXPECT_EQ(true,
              EvalJs(shell(),
                     "document.getElementById('base_element') == undefined"));
  }
  EXPECT_EQ(main_url, GetFrameBaseUrl(root_ftn->current_frame_host()));
  EXPECT_EQ(GURL(), root_ftn->current_frame_host()->GetInheritedBaseUrl());
}

// A test to make sure that a sandboxed srcdoc iframe correctly updates its
// base url with the <base> element, and restores the snapshotted base url from
// the parent if it removes its <base> element.
IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest,
                       SandboxedSrcdocIframeAddsRemovesBaseUrl) {
  StartEmbeddedServer();

  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Create srcdoc iframe with base url from a.com.
  {
    std::string js_str =
        "var frame = document.createElement('iframe'); "
        "frame.sandbox = 'allow-scripts'; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  auto* child = root->child_at(0);
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  EXPECT_EQ(main_url, GetFrameBaseUrl(root->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Srcdoc frame changes its base url.
  GURL b_url("http://b.com/");
  {
    std::string js_str = base::StringPrintf(
        "var base_element = document.createElement('base'); "
        "base_element.href = '%s'; "
        "document.head.appendChild(base_element);",
        b_url.spec().c_str());
    EXPECT_TRUE(ExecJs(child, js_str));
    EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host()));
  }
  EXPECT_EQ(main_url, GetFrameBaseUrl(root->current_frame_host()));
  EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Root frame adds base element.
  GURL c_url("http://c.com/");
  {
    std::string js_str = base::StringPrintf(
        "var base_element = document.createElement('base'); "
        "base_element.href = '%s'; "
        "document.head.appendChild(base_element);",
        c_url.spec().c_str());
    EXPECT_TRUE(ExecJs(root, js_str));
    EXPECT_EQ(c_url, GetFrameBaseUrl(root->current_frame_host()));
  }
  EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host()));
  EXPECT_EQ(c_url, GetFrameBaseUrl(root->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // The srcdoc removes its base element.
  {
    EXPECT_TRUE(ExecJs(child, "document.querySelector('base').remove();"));
    if (SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) {
      EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
    }
    if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
      EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
      EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
    } else {
      // TODO(wjmaclean): we know this expectation is wrong, and is fixed when
      // IsNewBaseUrlInheritanceBehaviorEnabled() is true.
      // https://crbug.com/1356658
      EXPECT_EQ(c_url, GetFrameBaseUrl(child->current_frame_host()));
    }
  }
  EXPECT_EQ(c_url, GetFrameBaseUrl(root->current_frame_host()));
}

// Test that when a sandboxed srcdoc iframe's parent changes its base url, the
// srcdoc continues to use the original base url until it reloads.
IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest,
                       SrcdocParentChangesBaseUrl) {
  StartEmbeddedServer();

  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  GURL b_url("http://b.com/");
  {
    std::string js_str = base::StringPrintf(
        "var base_element = document.createElement('base'); "
        "base_element.href = '%s'; "
        "document.head.appendChild(base_element);",
        b_url.spec().c_str());
    EXPECT_TRUE(ExecJs(root, js_str));
    EXPECT_EQ(b_url, GetFrameBaseUrl(root->current_frame_host()));
  }

  // Create srcdoc iframe inheriting a base url of b.com.
  {
    std::string js_str =
        "var frame = document.createElement('iframe'); "
        "frame.id = 'child-srcdoc'; "
        "frame.sandbox = 'allow-scripts'; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  auto* child = root->child_at(0);
  EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host()));
  EXPECT_EQ(b_url, GetFrameBaseUrl(root->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(b_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Remove base element from root.
  EXPECT_TRUE(ExecJs(root, "document.querySelector('base').remove();"));
  EXPECT_EQ(main_url, GetFrameBaseUrl(root->current_frame_host()));
  EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(b_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Reload child. Since the child is initiating the reload, it should reload
  // with the same base url it had before the reload.
  {
    EXPECT_TRUE(ExecJs(child, "location.reload();"));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host()));
    EXPECT_EQ(b_url, child->current_frame_host()->GetInheritedBaseUrl());
  } else {
    EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  }

  // Have the parent initiate the reload. This time the parent's original url
  // should be sent to the child as its base url.
  {
    EXPECT_TRUE(ExecJs(shell(),
                       "var frame = document.getElementById('child-srcdoc'); "
                       "frame.srcdoc = frame.srcdoc;"));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }
}

// A test to verify that the base url stored in RFHI for an about:srcdoc frame
// is cleared when the frame navigates to a non-srcdoc/blank url.
IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest,
                       InheritedBaseUrlClearedOnNavigation) {
  StartEmbeddedServer();
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL child_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  {
    std::string js_str =
        "var frame = document.createElement('iframe'); "
        "frame.id = 'child-srcdoc'; "
        "frame.srcdoc = 'foo'; "
        "document.body.appendChild(frame);";
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  auto* child = root->child_at(0);
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Remove the srcdoc attribute from the child frame. This should trigger a
  // navigation to about:blank.
  {
    EXPECT_TRUE(ExecJs(shell(),
                       "var frame = document.getElementById('child-srcdoc'); "
                       "frame.removeAttribute('srcdoc'); "));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }
  EXPECT_EQ(GURL("about:blank"),
            child->current_frame_host()->GetLastCommittedURL());
  EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host()));
  if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) {
    EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  }

  // Navigate the subframe to `child_url`. This should remove the inherited base
  // URL.
  {
    std::string js_str = base::StringPrintf(
        "var frame = document.getElementById('child-srcdoc'); "
        "frame.src = '%s';",
        child_url.spec().c_str());
    EXPECT_TRUE(ExecJs(shell(), js_str));
    ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  }

  EXPECT_EQ(child_url, child->current_frame_host()->GetLastCommittedURL());
  EXPECT_EQ(child_url, GetFrameBaseUrl(child->current_frame_host()));
  EXPECT_EQ(GURL(), child->current_frame_host()->GetInheritedBaseUrl());
}

// This test verifies that using enterprise policy to disable
// NewBaseUrlInheritanceBehavior effectively disables both
// the new base url inheritance behavior, and isolation of sandboxed iframes by
// forcing both AreIsolatedSandboxedIframesEnabled() and
// IsNewBaseUrlInheritanceBehaviorEnabled() to return false.
IN_PROC_BROWSER_TEST_P(BaseUrlInheritanceBehaviorEnterprisePolicyTest,
                       VerifyEnterprisePolicyDisables) {
  EXPECT_FALSE(SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled());
  EXPECT_FALSE(blink::features::IsNewBaseUrlInheritanceBehaviorEnabled());

  // Verify that the about:blank window does not get the base url of its
  // initiator, which is the expected behavior when the
  // IsolateSandboxedIframes or NewBaseUrlInheritanceBehavior features are
  // overridden by the enterprise policy.
  StartEmbeddedServer();
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_NE(GURL("about:blank"), GetFrameBaseUrl(shell()));

  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  ShellAddedObserver new_shell_observer;
  EXPECT_TRUE(ExecJs(root, "popup = window.open('about:blank');"));
  Shell* popup = new_shell_observer.GetShell();
  EXPECT_EQ(GURL("about:blank"), GetFrameBaseUrl(popup));
}

// A test to verify the initial stages of the initiator base url plumbing work.
// The test verifies the value propagates as far as NavigationRequest and
// FrameNavigationEntry. The test is based on
// SitePerProcessIsolatedSandboxedIframeTest since that will automatically
// enable the NewBaseUrlInheritanceBehavior.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
                       VerifyBaseUrlPlumbing) {
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  // The child needs to have the same origin as the parent.
  GURL child_url(main_url);
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  FrameNavigationEntry* root_frame_entry =
      root->current_frame_host()->last_committed_frame_entry();
  ASSERT_TRUE(root_frame_entry);
  EXPECT_FALSE(root_frame_entry->initiator_base_url().has_value());

  // Create srcdoc iframe. Verify the baseurl is plumbed as far as the
  // FrameNavigationEntry.
  {
    std::string js_str =
        "const frm = document.createElement('iframe'); "
        "frm.srcdoc = 'foo'; "
        "document.body.appendChild(frm); ";
    EXPECT_TRUE(ExecJs(shell(), js_str));
  }
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());

  FrameNavigationEntry* child_frame_entry =
      child->current_frame_host()->last_committed_frame_entry();
  ASSERT_TRUE(child_frame_entry);
  ASSERT_TRUE(child_frame_entry->initiator_base_url().has_value());
  EXPECT_EQ(main_url, child_frame_entry->initiator_base_url().value());

  // Create about:blank iframe. Verify the baseurl is plumbed as far as the
  // FrameNavigationEntry.
  {
    std::string js_str =
        "const frm = document.createElement('iframe'); "
        "frm.src = 'about:blank'; "
        "document.body.appendChild(frm); ";
    EXPECT_TRUE(ExecJs(shell(), js_str));
  }
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  ASSERT_EQ(2U, root->child_count());
  child = root->child_at(1);
  EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl());
  child_frame_entry = child->current_frame_host()->last_committed_frame_entry();

  ASSERT_TRUE(child_frame_entry);
  ASSERT_TRUE(child_frame_entry->initiator_base_url().has_value());
  EXPECT_EQ(main_url, child_frame_entry->initiator_base_url().value());

  // Renderer-initiated navigation of the top-level frame to about:blank; there
  // should be an initiator base url.
  EXPECT_TRUE(ExecJs(shell(), "location = 'about:blank';"));
  ASSERT_TRUE(WaitForLoadStop(web_contents()));
  root_frame_entry = root->current_frame_host()->last_committed_frame_entry();
  ASSERT_TRUE(root_frame_entry);
  ASSERT_TRUE(root_frame_entry->initiator_base_url().has_value());
  EXPECT_EQ(main_url, root_frame_entry->initiator_base_url().value());

  // Browser-initiated navigation of the top-level frame to about:blank; there
  // should be no initiator base url.
  EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
  root_frame_entry = root->current_frame_host()->last_committed_frame_entry();
  ASSERT_TRUE(root_frame_entry);
  EXPECT_FALSE(root_frame_entry->initiator_base_url().has_value());
  EXPECT_EQ(GURL(), root->current_frame_host()->GetInheritedBaseUrl());
}

// This test verifies that a renderer process doesn't crash if a srcdoc calls
// document.write on a mainframe parent.
IN_PROC_BROWSER_TEST_F(BaseUrlInheritanceBehaviorIframeTest,
                       SrcdocWritesMainFrame) {
  StartEmbeddedServer();
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  // Create srcdoc child.
  EXPECT_TRUE(ExecJs(root,
                     "var frm = document.createElement('iframe'); "
                     "frm.srcdoc = 'foo'; "
                     "document.body.appendChild(frm);"));
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);

  // Have the srcdoc child call document.write on the mainframe-parent.
  std::string test_str("test-complete");
  // Since having the child write the parent's document will delete the child,
  // we use setTimeout to ensure ExecJS returns true, and then wait for the
  // child's RenderFrameHost to be deleted so we know that the write has
  // completed. Note: the child's subframe exiting does not mean that its
  // process, which it shares with the parent, has exited.
  RenderFrameDeletedObserver observer(child->current_frame_host());
  EXPECT_TRUE(ExecJs(
      child, JsReplace("setTimeout(() => { parent.document.write($1); }, 100);",
                       test_str)));
  observer.WaitUntilDeleted();

  // But fortunately `root` is still valid.
  EXPECT_EQ(test_str, EvalJs(root, "document.body.innerText").ExtractString());
  // If we get here without a crash, we've passed.
}

// A test to verify that a new about:blank mainframe inherits its base url
// from its initiator.
IN_PROC_BROWSER_TEST_F(BaseUrlInheritanceBehaviorIframeTest,
                       PopupsInheritBaseUrl) {
  StartEmbeddedServer();
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  ShellAddedObserver new_shell_observer;
  EXPECT_TRUE(ExecJs(root, "var w = window.open()"));
  Shell* new_shell = new_shell_observer.GetShell();
  WebContentsImpl* new_contents =
      static_cast<WebContentsImpl*>(new_shell->web_contents());
  EXPECT_TRUE(WaitForLoadStop(new_contents));
  ASSERT_NE(new_contents, shell()->web_contents());

  // The popup should get the same base URL as its initiator.
  FrameTreeNode* new_root = new_contents->GetPrimaryFrameTree().root();
  EXPECT_EQ(EvalJs(root, "document.baseURI").ExtractString(),
            EvalJs(new_root, "document.baseURI").ExtractString());
}

IN_PROC_BROWSER_TEST_F(BaseUrlInheritanceBehaviorIframeTest,
                       AboutBlankInheritsBaseUrlFromSiblingInitiator) {
  StartEmbeddedServer();
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  // Create siblings.
  EXPECT_TRUE(ExecJs(root,
                     "var frm = document.createElement('iframe'); "
                     "frm.src = 'about:blank'; "
                     "frm.id = 'frm1'; "
                     "document.body.appendChild(frm);"));
  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child1 = root->child_at(0);

  EXPECT_TRUE(ExecJs(root,
                     "var frm = document.createElement('iframe'); "
                     "frm.id = 'frm2'; "
                     "document.body.appendChild(frm);"));
  ASSERT_EQ(2U, root->child_count());
  FrameTreeNode* child2 = root->child_at(1);

  // First child navigates to about:blank on second child.
  EXPECT_TRUE(ExecJs(child1,
                     "var base = document.createElement('base'); "
                     "base.href = 'https://example.com'; "
                     "document.head.appendChild(base); "
                     "window.top.window[1].location.href = 'about:blank';"));
  // Make sure second child inherited base url from the first child.
  EXPECT_EQ(GURL("https://example.com"),
            GetFrameBaseUrl(child2->current_frame_host()));
}

INSTANTIATE_TEST_SUITE_P(All,
                         SitePerProcessIsolatedSandboxedIframeTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         SitePerProcessNotIsolatedSandboxedIframeTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         SitePerProcessPerOriginIsolatedSandboxedIframeTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
    All,
    SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest,
    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         SitePerProcessPerDocumentIsolatedSandboxedIframeTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         SrcdocIsolatedSandboxedIframeTest,
                         testing::Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param ? "isolated" : "non_isolated";
                         });
INSTANTIATE_TEST_SUITE_P(All,
                         BaseUrlInheritanceIframeTest,
                         testing::Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param
                                      ? "new_base_url_inheritance_behavior"
                                      : "legacy_base_url_inheritance_behavior";
                         });
INSTANTIATE_TEST_SUITE_P(All,
                         BaseUrlInheritanceBehaviorEnterprisePolicyTest,
                         testing::Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param ? "isolated" : "non_isolated";
                         });

}  // namespace content