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

#include "content/browser/renderer_host/navigation_policy_container_builder.h"

#include <iosfwd>
#include <utility>

#include "base/files/file_path.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/navigation_simulator.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "services/network/public/mojom/referrer_policy.mojom-shared.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/url_constants.h"

namespace content {
namespace {

using ::testing::ByRef;
using ::testing::Eq;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::SizeIs;

network::mojom::ContentSecurityPolicyPtr MakeTestCSP() {
  auto csp = network::mojom::ContentSecurityPolicy::New();
  csp->header = network::mojom::ContentSecurityPolicyHeader::New();
  csp->header->header_value = "some-directive some-value";
  return csp;
}

// Returns non-default policies for use in tests.
PolicyContainerPolicies MakeTestPolicies() {
  std::vector<network::mojom::ContentSecurityPolicyPtr> csp_list;
  csp_list.push_back(MakeTestCSP());
  return PolicyContainerPolicies(
      network::mojom::ReferrerPolicy::kAlways,
      network::mojom::IPAddressSpace::kPublic,
      /*is_web_secure_context=*/true, network::ConnectionAllowlists(),
      std::move(csp_list), network::CrossOriginOpenerPolicy(),
      network::CrossOriginEmbedderPolicy(), network::DocumentIsolationPolicy(),
      network::IntegrityPolicy(), network::IntegrityPolicy(),
      network::mojom::WebSandboxFlags::kNone,
      /*is_credentialless=*/false,
      /*can_navigate_top_without_user_gesture=*/true,
      /*cross_origin_isolation_enabled_by_dip=*/false,
      /*coi_key=*/std::nullopt);
}

// Shorthand.
scoped_refptr<PolicyContainerHost> NewHost(PolicyContainerPolicies policies) {
  return base::MakeRefCounted<PolicyContainerHost>(std::move(policies));
}

GURL AboutBlankUrl() {
  return GURL(url::kAboutBlankURL);
}

GURL AboutSrcdocUrl() {
  return GURL(url::kAboutSrcdocURL);
}

// RenderViewHostImplTestHarness allows interacting with RenderFrameHosts in the
// form of TestRenderFrameHosts. This allows us to easily set policies on frames
// for testing. It also instantiates a BrowserTaskEnvironment so that tests are
// executed "on the UI thread".
//
// This test fixture is moderately expensive to set up (~100ms overhead per
// test), but still an order of magnitude faster than browser tests.
class NavigationPolicyContainerBuilderTest
    : public RenderViewHostImplTestHarness {
 protected:
  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();
    contents()->GetPrimaryMainFrame()->InitializeRenderFrameIfNeeded();
  }
};

// Verifies that the initial delivered policies are default-constructed.
TEST_F(NavigationPolicyContainerBuilderTest, DefaultDeliveredPolicies) {
  EXPECT_EQ(NavigationPolicyContainerBuilder(nullptr, nullptr)
                .DeliveredPoliciesForTesting(),
            PolicyContainerPolicies());
}

// Verifies that SetIPAddressSpace sets the address space in the builder's
// delivered policies.
TEST_F(NavigationPolicyContainerBuilderTest, SetIPAddressSpace) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kPublic);

  PolicyContainerPolicies expected_policies;
  expected_policies.ip_address_space = network::mojom::IPAddressSpace::kPublic;

  EXPECT_EQ(builder.DeliveredPoliciesForTesting(), expected_policies);
}

// Verifies that SetIsOriginPotentiallyTrustworthy sets the secure context bit
// in the builder's delivered policies.
TEST_F(NavigationPolicyContainerBuilderTest,
       SetIsOriginPotentiallyTrustworthy) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.SetIsOriginPotentiallyTrustworthy(true);

  PolicyContainerPolicies expected_policies;
  expected_policies.is_web_secure_context = true;

  EXPECT_EQ(builder.DeliveredPoliciesForTesting(), expected_policies);

  builder.SetIsOriginPotentiallyTrustworthy(false);

  expected_policies.is_web_secure_context = false;
  EXPECT_EQ(builder.DeliveredPoliciesForTesting(), expected_policies);
}

// Verifies that SetCrossOriginOpenerPolicy sets the cross-origin-opener-policy
// in the builder's final policies.
TEST_F(NavigationPolicyContainerBuilderTest, SetCrossOriginOpenerPolicy) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  network::CrossOriginOpenerPolicy coop;
  coop.value = network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin;
  coop.report_only_value =
      network::mojom::CrossOriginOpenerPolicyValue::kSameOriginAllowPopups;
  coop.reporting_endpoint = "A";
  coop.report_only_reporting_endpoint = "B";

  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/true);

  builder.SetCrossOriginOpenerPolicy(coop);

  PolicyContainerPolicies expected_policies;
  expected_policies.cross_origin_opener_policy = coop;

  EXPECT_EQ(builder.FinalPolicies(), expected_policies);
}

// Verifies that SetDocumentIsolationPolicy sets the document-isolation-policy
// in the builder's delivered policies.
TEST_F(NavigationPolicyContainerBuilderTest, SetDocumentIsolationPolicy) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  network::DocumentIsolationPolicy dip;
  dip.value =
      network::mojom::DocumentIsolationPolicyValue::kIsolateAndRequireCorp;
  dip.report_only_value =
      network::mojom::DocumentIsolationPolicyValue::kIsolateAndCredentialless;
  dip.reporting_endpoint = "A";
  dip.report_only_reporting_endpoint = "B";

  builder.SetDocumentIsolationPolicy(dip);

  PolicyContainerPolicies expected_policies;
  expected_policies.document_isolation_policy = dip;

  EXPECT_EQ(builder.DeliveredPoliciesForTesting(), expected_policies);
}

// Verifies that the default final policies of a builder are
// default-constructed, and are equal to the policies of the builder's policy
// container host.
TEST_F(NavigationPolicyContainerBuilderTest, DefaultFinalPolicies) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  MockNavigationHandle navigation_handle(GURL(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  PolicyContainerPolicies expected_policies;
  EXPECT_EQ(builder.FinalPolicies(), expected_policies);

  scoped_refptr<PolicyContainerHost> cloned_host =
      builder.GetPolicyContainerHost();
  ASSERT_THAT(cloned_host, NotNull());
  EXPECT_EQ(cloned_host->policies(), expected_policies);

  scoped_refptr<PolicyContainerHost> host =
      std::move(builder).TakePolicyContainerHost();
  ASSERT_THAT(host, NotNull());
  EXPECT_EQ(host->policies(), expected_policies);
  ASSERT_THAT(cloned_host, NotNull());
}

// Verifies that when the URL of the document to commit does not have a local
// scheme, then the final policies are copied from the delivered policies.
TEST_F(NavigationPolicyContainerBuilderTest, FinalPoliciesNormalUrl) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kPublic);
  builder.AddContentSecurityPolicy(MakeTestCSP());
  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

// Verifies the final policies when the URL of the document to commit is
// `about:blank` but there is no initiator.
TEST_F(NavigationPolicyContainerBuilderTest,
       FinalPoliciesAboutBlankWithoutInitiator) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kPublic);
  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  MockNavigationHandle navigation_handle(AboutBlankUrl(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

TEST_F(NavigationPolicyContainerBuilderTest, MHTMLSandboxFlags) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kLoopback);
  MockNavigationHandle navigation_handle(GURL("file:///my/page.mhtml"),
                                         nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          /*is_inside_mhtml=*/true,
                          network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies().sandbox_flags,
            // MHTML archives receive all sandbox flags except these:
            ~(network::mojom::WebSandboxFlags::kPopups |
              network::mojom::WebSandboxFlags::
                  kPropagatesToAuxiliaryBrowsingContexts));
}

// When kMHTML_Improvements is enabled, in mhtml, scripts are allowed.
TEST_F(NavigationPolicyContainerBuilderTest,
       MHTMLSandboxFlagsWithMHTMLImprovementsLocalFile) {
  base::test::ScopedFeatureList features(blink::features::kMHTML_Improvements);
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kLoopback);
  MockNavigationHandle navigation_handle(GURL("file:///my/page.mhtml"),
                                         nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          /*is_inside_mhtml=*/true,
                          network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies().sandbox_flags,
            // When kMHTML_Improvements is enabled, MHTML archives receive all
            // sandbox flags except these:
            ~(network::mojom::WebSandboxFlags::kPopups |
              network::mojom::WebSandboxFlags::
                  kPropagatesToAuxiliaryBrowsingContexts |
              network::mojom::WebSandboxFlags::kScripts));
}

// Verifies the final policies when the URL of the document to commit is
// `about:blank` but there is no initiator, and we have some additional CSPs.
TEST_F(NavigationPolicyContainerBuilderTest,
       FinalPoliciesAboutBlankWithoutInitiatorAdditionalCSP) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kPublic);
  builder.AddContentSecurityPolicy(MakeTestCSP());
  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  MockNavigationHandle navigation_handle(AboutBlankUrl(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

// This test verifies the default final policies on error pages.
TEST_F(NavigationPolicyContainerBuilderTest, DefaultFinalPoliciesForErrorPage) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  builder.ComputePoliciesForError();

  // Error pages commit with default policies, mostly ignoring the delivered
  // policies and the document's URL.
  EXPECT_EQ(builder.FinalPolicies(), PolicyContainerPolicies());
}

// This test verifies that error pages commit in the same IP address space as
// the underlying page would have, had it not failed to load.
TEST_F(NavigationPolicyContainerBuilderTest, ErrorPageIPAddressSpace) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kPublic);
  builder.ComputePoliciesForError();

  PolicyContainerPolicies expected_policies;
  expected_policies.ip_address_space = network::mojom::IPAddressSpace::kPublic;
  EXPECT_EQ(builder.FinalPolicies(), expected_policies);
}

// Variation of: NavigationPolicyContainerBuilderTest.ErrorPageIPAddressSpace
// The decision to commit an error happens after receiving the response.
TEST_F(NavigationPolicyContainerBuilderTest,
       ErrorPageIPAddressSpaceAfterResponse) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  builder.SetIPAddressSpace(network::mojom::IPAddressSpace::kLocal);
  PolicyContainerPolicies expected_policies;
  expected_policies.ip_address_space = network::mojom::IPAddressSpace::kLocal;

  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  EXPECT_EQ(builder.FinalPolicies(), expected_policies);

  builder.ComputePoliciesForError();
  EXPECT_EQ(builder.FinalPolicies(), expected_policies);
}

// CSP delivered by the HTTP response are ignored for error document.
TEST_F(NavigationPolicyContainerBuilderTest,
       DeliveredCSPIgnoredForErrorDocument) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  builder.AddContentSecurityPolicy(
      network::mojom::ContentSecurityPolicy::New());

  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  EXPECT_THAT(builder.FinalPolicies().content_security_policies, SizeIs(1));

  builder.ComputePoliciesForError();
  EXPECT_THAT(builder.FinalPolicies().content_security_policies, SizeIs(0));
}

// Verifies that ParentPolicies returns nullptr in the absence of a parent.
TEST_F(NavigationPolicyContainerBuilderTest, ParentPoliciesWithoutParent) {
  EXPECT_THAT(
      NavigationPolicyContainerBuilder(nullptr, nullptr).ParentPolicies(),
      IsNull());
}

// Verifies that ParentPolicies returns a pointer to a copy of the parent's
// policies.
TEST_F(NavigationPolicyContainerBuilderTest, ParentPoliciesWithParent) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  EXPECT_THAT(builder.ParentPolicies(), Pointee(Eq(ByRef(parent_policies))));
}

// Verifies that when the the URL of the document to commit is `about:srcdoc`,
// the builder's final policies are copied from the parent.
TEST_F(NavigationPolicyContainerBuilderTest,
       FinalPoliciesAboutSrcdocWithParent) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);
  MockNavigationHandle navigation_handle(AboutSrcdocUrl(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), parent_policies);
}

// Verifies that when a document has a potentially-trustworthy origin and no
// parent, then it is a secure context.
TEST_F(NavigationPolicyContainerBuilderTest,
       IsWebSecureContextTrustworthyOriginNoParent) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  builder.SetIsOriginPotentiallyTrustworthy(true);

  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  EXPECT_TRUE(delivered_policies.is_web_secure_context);

  MockNavigationHandle navigation_handle(GURL(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

// Verifies that when a document has a non-potentially-trustworthy origin and no
// parent, then it is not a secure context.
TEST_F(NavigationPolicyContainerBuilderTest,
       IsWebSecureContextNonTrustworthyOriginNoParent) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);

  builder.SetIsOriginPotentiallyTrustworthy(false);

  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  EXPECT_FALSE(delivered_policies.is_web_secure_context);

  MockNavigationHandle navigation_handle(GURL(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

// Verifies that when a document has a potentially-trustworthy origin and a
// parent that is not a secure context, then it is not a secure context.
TEST_F(NavigationPolicyContainerBuilderTest,
       IsWebSecureContextTrustworthyOriginNonSecureParent) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();
  parent_policies.is_web_secure_context = false;

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(std::move(parent_policies)),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  builder.SetIsOriginPotentiallyTrustworthy(true);

  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_FALSE(builder.FinalPolicies().is_web_secure_context);
}

// Verifies that when a document has a non-potentially-trustworthy origin and a
// parent that is a secure context, then it is not a secure context.
TEST_F(NavigationPolicyContainerBuilderTest,
       IsWebSecureContextNonTrustworthyOriginSecureParent) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();
  parent_policies.is_web_secure_context = true;

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(std::move(parent_policies)),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  builder.SetIsOriginPotentiallyTrustworthy(false);

  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  EXPECT_FALSE(delivered_policies.is_web_secure_context);

  MockNavigationHandle navigation_handle(GURL("http://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

// Verifies that when a document has a potentially-trustworthy origin and a
// parent that is a secure context, then it is a secure context.
TEST_F(NavigationPolicyContainerBuilderTest,
       IsWebSecureContextTrustworthyOriginSecureParent) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();
  parent_policies.is_web_secure_context = true;

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(std::move(parent_policies)),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  builder.SetIsOriginPotentiallyTrustworthy(true);

  PolicyContainerPolicies delivered_policies =
      builder.DeliveredPoliciesForTesting().Clone();
  EXPECT_TRUE(delivered_policies.is_web_secure_context);

  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  EXPECT_EQ(builder.FinalPolicies(), delivered_policies);
}

// When `ComputePolicies()` is called with `is_secure_context_root=true`,
// the navigating frame's `is_web_secure_context` is decided solely by its
// own origin's trustworthiness, ignoring the parent's secure-context status.
// Used by callers at embedder-identified inheritance boundaries (e.g.,
// MIME-handler OOPIFs) where the frame acts as an independent security
// boundary that does not inherit an insecure state from its embedder.
TEST_F(NavigationPolicyContainerBuilderTest, IsSecureContextRootOverride) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();
  parent_policies.is_web_secure_context = false;

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  // The real parent's policies are still visible - the override only changes
  // how the final secure-context bit is computed, not what the builder sees
  // as parent.
  EXPECT_THAT(builder.ParentPolicies(), Pointee(Eq(ByRef(parent_policies))));

  builder.SetIsOriginPotentiallyTrustworthy(true);
  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/true);

  EXPECT_TRUE(builder.FinalPolicies().is_web_secure_context);
}

// Verifies the secure-context root flag cannot turn an untrustworthy origin
// (e.g. an http: URL) into a secure context. The flag only stops a frame
// from inheriting its parent's secure-context status; it never grants secure
// context on its own.
TEST_F(NavigationPolicyContainerBuilderTest,
       IsSecureContextRootDoesNotOverrideUntrustworthyOrigin) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();
  parent_policies.is_web_secure_context = true;

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  builder.SetIsOriginPotentiallyTrustworthy(false);
  MockNavigationHandle navigation_handle(GURL("http://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/true);

  EXPECT_FALSE(builder.FinalPolicies().is_web_secure_context);
}

// Verifies that when the the URL of the document to commit is `about:srcdoc`,
// the builder's final policies are copied from the parent, and additional
// delivered policies are merged.
TEST_F(NavigationPolicyContainerBuilderTest,
       FinalPoliciesAboutSrcdocWithParentAndAdditionalCSP) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);

  // Add some CSP.
  network::mojom::ContentSecurityPolicyPtr test_csp = MakeTestCSP();
  builder.AddContentSecurityPolicy(test_csp.Clone());
  MockNavigationHandle navigation_handle(AboutSrcdocUrl(), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);

  parent_policies.content_security_policies.push_back(std::move(test_csp));
  EXPECT_EQ(builder.FinalPolicies(), parent_policies);
}

// Calling ComputePolicies() twice triggers a DCHECK.
TEST_F(NavigationPolicyContainerBuilderTest, ComputePoliciesTwiceDCHECK) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  EXPECT_DCHECK_DEATH(builder.ComputePolicies(
      &navigation_handle, /*initiator_policies=*/nullptr, false,
      network::mojom::WebSandboxFlags::kNone,
      /*is_credentialless=*/false, /*is_secure_context_root=*/false));
}

// Calling ComputePolicies() followed by ComputePoliciesForError() is supported.
TEST_F(NavigationPolicyContainerBuilderTest, ComputePoliciesThenError) {
  NavigationPolicyContainerBuilder builder(nullptr, nullptr);
  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  builder.ComputePoliciesForError();
}

// After ComputePolicies() or ComputePoliciesForError(), the parent
// policies are still accessible.
TEST_F(NavigationPolicyContainerBuilderTest,
       AccessParentAfterComputingPolicies) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();
  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);
  EXPECT_THAT(builder.ParentPolicies(), Pointee(Eq(ByRef(parent_policies))));

  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  EXPECT_THAT(builder.ParentPolicies(), Pointee(Eq(ByRef(parent_policies))));

  builder.ComputePoliciesForError();
  EXPECT_THAT(builder.ParentPolicies(), Pointee(Eq(ByRef(parent_policies))));
}

// Verifies that the parent policies are preserved on
// ResetForCrossDocumentRestart.
TEST_F(NavigationPolicyContainerBuilderTest,
       ResetForCrossDocumentRestartParentPolicies) {
  PolicyContainerPolicies parent_policies = MakeTestPolicies();

  TestRenderFrameHost* parent = contents()->GetPrimaryMainFrame();
  parent->SetPolicyContainerHost(NewHost(parent_policies.Clone()),
                                 base::UnguessableToken::Create());

  NavigationPolicyContainerBuilder builder(parent, nullptr);
  MockNavigationHandle navigation_handle(GURL("https://foo.test"), nullptr);
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  EXPECT_EQ(builder.FinalPolicies(), PolicyContainerPolicies());

  builder.ResetForCrossDocumentRestart();
  EXPECT_THAT(builder.ParentPolicies(), Pointee(Eq(ByRef(parent_policies))));

  navigation_handle.set_url(AboutSrcdocUrl());
  builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
                          false, network::mojom::WebSandboxFlags::kNone,
                          /*is_credentialless=*/false,
                          /*is_secure_context_root=*/false);
  EXPECT_EQ(builder.FinalPolicies(), parent_policies);
}

}  // namespace
}  // namespace content
