// Copyright 2023 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/mixed_content_checker.h"

#include <memory>
#include <optional>
#include <ostream>
#include <tuple>
#include <vector>

#include "base/test/scoped_feature_list.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/fake_local_frame.h"
#include "content/public/test/test_utils.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/source_location.mojom-forward.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
#include "url/gurl.h"

namespace content {
namespace {

using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
using ::testing::Optional;
using ::testing::UnorderedElementsAre;

// Intercepts the mojo calls of `MixedContentFound()` and
// `ReportBlinkFeatureUsage()` from `rfh` to a LocalFrame in renderer.
class LocalFrameInterceptor : public FakeLocalFrame {
 public:
  explicit LocalFrameInterceptor(RenderFrameHost* rfh)
      : rfh_(static_cast<TestRenderFrameHost*>(rfh)) {
    rfh_->ResetLocalFrame();
    Init(rfh_->GetRemoteAssociatedInterfaces());
  }

  // FakeLocalFrame overrides:
  struct MixedContentResult {
    GURL main_resource_url;
    GURL mixed_content_url;
    bool was_allowed;
    bool had_redirect;
  };
  void MixedContentFound(
      const GURL& main_resource_url,
      const GURL& mixed_content_url,
      blink::mojom::RequestContextType request_context,
      bool was_allowed,
      const GURL& url_before_redirects,
      bool had_redirect,
      network::mojom::SourceLocationPtr source_location) final {
    mixed_content_result_ = MixedContentResult{
        main_resource_url, mixed_content_url, was_allowed, had_redirect};
  }
  void ReportBlinkFeatureUsage(
      const std::vector<blink::mojom::WebFeature>& web_features) final {
    reported_web_features_ = web_features;
  }

  const std::optional<MixedContentResult>& mixed_content_result() const {
    return mixed_content_result_;
  }
  const std::vector<blink::mojom::WebFeature>& reported_web_features() const {
    return reported_web_features_;
  }

  void FlushLocalFrameMessages() { rfh_->FlushLocalFrameMessages(); }

 private:
  raw_ptr<TestRenderFrameHost> rfh_;
  std::vector<blink::mojom::WebFeature> reported_web_features_;
  std::optional<MixedContentResult> mixed_content_result_;
};

// Needed by GTest to display errors.
std::ostream& operator<<(std::ostream& out,
                         const LocalFrameInterceptor::MixedContentResult& m) {
  return out << "\nMixedContentResult {\n"
             << "  main_resource_url: " << m.main_resource_url << "\n"
             << "  mixed_content_url: " << m.mixed_content_url << "\n"
             << "  was_allowed: " << m.was_allowed << "\n"
             << "  had_redirect: " << m.had_redirect << "\n"
             << "}";
}

}  // namespace

// Tests that `content::MixedContentChecker` correctly detects or ignores many
// cases where there is or there is not mixed content, respectively.
// Note: Browser side version of
// `blink::MixedContentCheckerTest::IsMixedContent`.
// Must be kept in sync manually!
// LINT.IfChange
TEST(MixedContentCheckerTest, IsMixedContent) {
  struct TestCase {
    const char* origin;
    const char* target;
    const bool expected_mixed_content;
  } cases[] = {
      {"http://example.com/foo", "http://example.com/foo", false},
      {"http://example.com/foo", "https://example.com/foo", false},
      {"http://example.com/foo", "data:text/html,<p>Hi!</p>", false},
      {"http://example.com/foo", "about:blank", false},
      {"https://example.com/foo", "https://example.com/foo", false},
      {"https://example.com/foo", "wss://example.com/foo", false},
      {"https://example.com/foo", "data:text/html,<p>Hi!</p>", false},
      {"https://example.com/foo", "blob:https://example.com/foo", false},
      {"https://example.com/foo", "filesystem:https://example.com/foo", false},
      {"https://example.com/foo", "http://127.0.0.1/", false},
      {"https://example.com/foo", "http://[::1]/", false},
      {"https://example.com/foo", "http://a.localhost/", false},
      {"https://example.com/foo", "http://localhost/", false},

      {"https://example.com/foo", "http://example.com/foo", true},
      {"https://example.com/foo", "http://google.com/foo", true},
      {"https://example.com/foo", "ws://example.com/foo", true},
      {"https://example.com/foo", "ws://google.com/foo", true},
      {"https://example.com/foo", "http://192.168.1.1/", true},
      {"https://example.com/foo", "blob:http://example.com/foo", true},
      {"https://example.com/foo", "blob:null/foo", true},
      {"https://example.com/foo", "filesystem:http://example.com/foo", true},
      {"https://example.com/foo", "filesystem:null/foo", true},
  };

  for (const auto& test : cases) {
    SCOPED_TRACE(::testing::Message()
                 << "Origin: " << test.origin << ", Target: " << test.target
                 << ", Expectation: " << test.expected_mixed_content);
    GURL origin_url(test.origin);
    GURL target_url(test.target);
    EXPECT_EQ(
        test.expected_mixed_content,
        MixedContentChecker::IsMixedContentForTesting(origin_url, target_url));
  }
}
// LINT.ThenChange(third_party/blink/renderer/core/loader/mixed_content_checker_test.cc)

class MixedContentCheckerShouldBlockTestBase
    : public RenderViewHostImplTestHarness,
      public testing::WithParamInterface<bool> {
 protected:
  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();

    NavigateAndCommit(GURL("about:blank"));
  }

  bool for_redirect() const { return GetParam(); }
};

class MixedContentCheckerShouldBlockNavigationTestBase
    : public MixedContentCheckerShouldBlockTestBase {
 protected:
  // Starts a navigation from `source_url` to `target_url`. `from_subframe`
  // tells if the navigation is initiated from the main frame or sub frame of
  // the page of `source_url`.
  // Returns a tuple of:
  //   - a navigation simulator indicating a started navigation request.
  //   - a LocalFrame inspector that collects messages from the navigation
  //     initiator frame.
  std::tuple<std::unique_ptr<NavigationSimulatorImpl>,
             std::unique_ptr<LocalFrameInterceptor>>
  StartNavigation(
      const std::string& source_url,
      const std::string& target_url,
      bool from_subframe = false,
      blink::mojom::InsecureRequestPolicy main_frame_insecure_request_policy =
          blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
      blink::mojom::MixedContentContextType mixed_content_context_type =
          blink::mojom::MixedContentContextType::kBlockable) {
    // Loads the page of `source_url` first.
    NavigateAndCommit(GURL(source_url));

    TestRenderFrameHost* rfh = main_test_rfh();
    rfh->DidEnforceInsecureRequestPolicy(main_frame_insecure_request_policy);
    if (from_subframe) {
      // Navigation request is from a subframe of the page of `source_url`.
      TestRenderFrameHost* subframe = rfh->AppendChild("subframe");
      NavigationSimulator::NavigateAndCommitFromDocument(
          GURL(source_url + "/subframe"), subframe);
      rfh = subframe;
    }
    // `interceptor` must be constructed before initiating the navigation, such
    // that the remote of `rfh` pointing to a LocalFrame can be replaced.
    auto interceptor = std::make_unique<LocalFrameInterceptor>(rfh);

    std::unique_ptr<NavigationSimulatorImpl> navigation =
        NavigationSimulatorImpl::CreateRendererInitiated(GURL(target_url), rfh);
    navigation->SetReferrer(blink::mojom::Referrer::New(
        rfh->GetLastCommittedURL(),
        network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
    navigation->set_request_context_type(
        blink::mojom::RequestContextType::INTERNAL);
    navigation->set_mixed_content_context_type(mixed_content_context_type);
    // This line will trigger `MixedContentNavigationThrottle`, which underlying
    // also runs the test target `MixedContentChecker::ShouldBlockNavigation()`.
    navigation->Start();
    return std::make_tuple(std::move(navigation), std::move(interceptor));
  }
};

using MixedContentCheckerShouldBlockNavigationTest =
    MixedContentCheckerShouldBlockNavigationTestBase;

INSTANTIATE_TEST_SUITE_P(
    All,
    MixedContentCheckerShouldBlockNavigationTest,
    ::testing::Values(false, true),
    [](const testing::TestParamInfo<
        MixedContentCheckerShouldBlockNavigationTest::ParamType>& info) {
      return info.param ? "ForRedirect" : "ForNonRedirect";
    });

// Main frame navigations cannot be mixed content, no matter the source page is
// secure or not.
TEST_P(MixedContentCheckerShouldBlockNavigationTest,
       ShouldNotBlockNavigationFromInsecureMainFrame) {
  const auto [nav, inspector] =
      StartNavigation("http://source.com", "http://target.com");
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(inspector->mixed_content_result(), Eq(std::nullopt));
  EXPECT_THAT(inspector->reported_web_features(), IsEmpty());
}

// Main frame navigations cannot be mixed content, no matter the source page is
// secure or not.
TEST_P(MixedContentCheckerShouldBlockNavigationTest,
       ShouldNotBlockNavigationFromSecureMainFrame) {
  const auto [nav, inspector] =
      StartNavigation("https://source.com", "http://target.com");
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(inspector->mixed_content_result(), Eq(std::nullopt));
  EXPECT_THAT(inspector->reported_web_features(), IsEmpty());
}

// Navigates from insecure content is not mixed content.
TEST_P(MixedContentCheckerShouldBlockNavigationTest,
       ShouldNotBlockNavigationFromInsecureSubFrame) {
  const bool from_subframe = true;
  const auto [nav, inspector] =
      StartNavigation("http://source.com", "http://target.com", from_subframe);
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(inspector->mixed_content_result(), Eq(std::nullopt));
  EXPECT_THAT(inspector->reported_web_features(), IsEmpty());
}

// When mixed content is found in a navigation initiated from a subframe that
// is hosted in a different process from the mixed content frame, the main
// resource URL reported to the subframe's renderer should only contain the
// origin of the mixed content frame, not its full URL. The mixed content frame
// is the main frame in this scenario.
TEST_P(MixedContentCheckerShouldBlockNavigationTest,
       ReportsAncestorOriginToCrossProcessRenderer) {
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is required for this test.";
  }

  const GURL main_frame_url("https://source.com/private/path?token=value#frag");
  NavigateAndCommit(main_frame_url);
  TestRenderFrameHost* main_rfh = main_test_rfh();
  main_rfh->DidEnforceInsecureRequestPolicy(
      blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone);

  TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
      NavigationSimulator::NavigateAndCommitFromDocument(
          GURL("https://other.com/subframe"),
          main_rfh->AppendChild("subframe")));
  ASSERT_NE(subframe->GetProcess(), main_rfh->GetProcess());
  auto interceptor = std::make_unique<LocalFrameInterceptor>(subframe);

  std::unique_ptr<NavigationSimulatorImpl> navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(
          GURL("http://target.com"), subframe);
  navigation->SetReferrer(blink::mojom::Referrer::New(
      subframe->GetLastCommittedURL(),
      network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
  navigation->set_request_context_type(
      blink::mojom::RequestContextType::INTERNAL);
  navigation->set_mixed_content_context_type(
      blink::mojom::MixedContentContextType::kBlockable);
  navigation->Start();

  auto checker = MixedContentChecker();
  EXPECT_TRUE(checker.ShouldBlockNavigation(*navigation->GetNavigationHandle(),
                                            for_redirect()));
  interceptor->FlushLocalFrameMessages();
  EXPECT_THAT(
      interceptor->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com/"), GURL("http://target.com"),
                         /*was_allowed=*/false, for_redirect())));
}

// When mixed content is found in a navigation initiated from a subframe that
// is hosted in the same process as the mixed content frame, the full URL of
// the mixed content frame is reported to the renderer (which already has
// access to it). The mixed content frame is the main frame in this scenario.
TEST_P(MixedContentCheckerShouldBlockNavigationTest,
       ReportsAncestorFullUrlToSameProcessRenderer) {
  const GURL main_frame_url("https://source.com/private/path?token=value#frag");
  NavigateAndCommit(main_frame_url);
  TestRenderFrameHost* main_rfh = main_test_rfh();
  main_rfh->DidEnforceInsecureRequestPolicy(
      blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone);

  TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
      NavigationSimulator::NavigateAndCommitFromDocument(
          GURL("https://source.com/subframe"),
          main_rfh->AppendChild("subframe")));
  ASSERT_EQ(subframe->GetProcess(), main_rfh->GetProcess());
  auto interceptor = std::make_unique<LocalFrameInterceptor>(subframe);

  std::unique_ptr<NavigationSimulatorImpl> navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(
          GURL("http://target.com"), subframe);
  navigation->SetReferrer(blink::mojom::Referrer::New(
      subframe->GetLastCommittedURL(),
      network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
  navigation->set_request_context_type(
      blink::mojom::RequestContextType::INTERNAL);
  navigation->set_mixed_content_context_type(
      blink::mojom::MixedContentContextType::kBlockable);
  navigation->Start();

  auto checker = MixedContentChecker();
  EXPECT_TRUE(checker.ShouldBlockNavigation(*navigation->GetNavigationHandle(),
                                            for_redirect()));
  interceptor->FlushLocalFrameMessages();
  EXPECT_THAT(interceptor->mixed_content_result(),
              Optional(FieldsAre(main_frame_url, GURL("http://target.com"),
                                 /*was_allowed=*/false, for_redirect())));
}

// Tests to cover MixedContentContextType = kBlockable.
class MixedContentCheckerShouldBlockNavigationWithBlockableContextTest
    : public MixedContentCheckerShouldBlockNavigationTestBase {
 protected:
  blink::mojom::MixedContentContextType mixed_content_context_type() const {
    return blink::mojom::MixedContentContextType::kBlockable;
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    MixedContentCheckerShouldBlockNavigationWithBlockableContextTest,
    ::testing::Values(false, true),
    [](const testing::TestParamInfo<
        MixedContentCheckerShouldBlockNavigationWithBlockableContextTest::
            ParamType>& info) {
      return info.param ? "ForRedirect" : "ForNonRedirect";
    });

// ShouldBlockNavigation(subframe) => true
// - MixedContentContextType = kBlockable
// - main frame's InsecureRequestPolicy = kLeaveInsecureRequestsAlone
TEST_P(MixedContentCheckerShouldBlockNavigationWithBlockableContextTest,
       ShouldBlockMixedContentNavigationWithPolicyLeaveInsecureRequestAlone) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.com", from_subframe,
      main_frame_insecure_request_policy, mixed_content_context_type());
  auto checker = MixedContentChecker();

  EXPECT_TRUE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                            for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/false, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentBlockable));
}

// ShouldBlockNavigation(subframe) => true
// - MixedContentContextType = kBlockable
// - main frame's InsecureRequestPolicy = kBlockAllMixedContent
TEST_P(MixedContentCheckerShouldBlockNavigationWithBlockableContextTest,
       ShouldBlockMixedContentNavigationWithPolicyBlockAll) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent;
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.com", from_subframe,
      main_frame_insecure_request_policy, mixed_content_context_type());
  auto checker = MixedContentChecker();

  EXPECT_TRUE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                            for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/false, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentBlockable));
}

// Tests to cover MixedContentContextType = kOptionallyBlockable.
class MixedContentCheckerShouldBlockNavigationWithOptionallyBlockableContextTest
    : public MixedContentCheckerShouldBlockNavigationTestBase {
 protected:
  blink::mojom::MixedContentContextType mixed_content_context_type() const {
    return blink::mojom::MixedContentContextType::kOptionallyBlockable;
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    MixedContentCheckerShouldBlockNavigationWithOptionallyBlockableContextTest,
    ::testing::Values(false, true),
    [](const testing::TestParamInfo<
        MixedContentCheckerShouldBlockNavigationWithOptionallyBlockableContextTest::
            ParamType>& info) {
      return info.param ? "ForRedirect" : "ForNonRedirect";
    });

// ShouldBlockNavigation(subframe) => false
// - MixedContentContextType = kOptionallyBlockable
// - main frame's InsecureRequestPolicy = kLeaveInsecureRequestsAlone
TEST_P(
    MixedContentCheckerShouldBlockNavigationWithOptionallyBlockableContextTest,
    ShouldNotBlockMixedContentNavigationWithPolicyLeaveInsecureRequestAlone) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.com", from_subframe,
      main_frame_insecure_request_policy, mixed_content_context_type());
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/true, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentInternal));
}

// ShouldBlockNavigation(subframe) => true
// - MixedContentContextType = kOptionallyBlockable
// - main frame's InsecureRequestPolicy = kBlockAllMixedContent
TEST_P(
    MixedContentCheckerShouldBlockNavigationWithOptionallyBlockableContextTest,
    ShouldBlockMixedContentNavigationWithPolicyBlockAll) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent;
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.com", from_subframe,
      main_frame_insecure_request_policy, mixed_content_context_type());
  auto checker = MixedContentChecker();

  EXPECT_TRUE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                            for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/false, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentInternal));
}

// Tests to cover MixedContentContextType = kShouldBeBlockable.
class MixedContentCheckerShouldBlockNavigationWithShouldBeBlockableContextTest
    : public MixedContentCheckerShouldBlockNavigationTestBase {
 protected:
  blink::mojom::MixedContentContextType mixed_content_context_type() const {
    return blink::mojom::MixedContentContextType::kShouldBeBlockable;
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    MixedContentCheckerShouldBlockNavigationWithShouldBeBlockableContextTest,
    ::testing::Values(false, true),
    [](const testing::TestParamInfo<
        MixedContentCheckerShouldBlockNavigationWithShouldBeBlockableContextTest::
            ParamType>& info) {
      return info.param ? "ForRedirect" : "ForNonRedirect";
    });

// ShouldBlockNavigation(subframe) => false
// - MixedContentContextType = kShouldBeBlockable
// - main frame's InsecureRequestPolicy = kLeaveInsecureRequestsAlone
TEST_P(
    MixedContentCheckerShouldBlockNavigationWithShouldBeBlockableContextTest,
    ShouldNotBlockMixedContentNavigationWithPolicyLeaveInsecureRequestAlone) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.com", from_subframe,
      main_frame_insecure_request_policy, mixed_content_context_type());
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/true, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentInternal));
}

// ShouldBlockNavigation(subframe) => true
// - MixedContentContextType = kShouldBeBlockable
// - main frame's InsecureRequestPolicy = kBlockAllMixedContent
TEST_P(MixedContentCheckerShouldBlockNavigationWithShouldBeBlockableContextTest,
       ShouldBlockMixedContentNavigationWithPolicyBlockAll) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent;
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.com", from_subframe,
      main_frame_insecure_request_policy, mixed_content_context_type());
  auto checker = MixedContentChecker();

  EXPECT_TRUE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                            for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/false, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentInternal));
}

class MixedContentCheckerShouldBlockFetchKeepAliveTestBase
    : public MixedContentCheckerShouldBlockTestBase {
 protected:
  // Prepares a frame that loads `source_url`.
  // `from_subframe` tells if the frame is a main frame or sub frame of the page
  // of `source_url`.
  // Returns a tuple of:
  //   - a RenderFrameHostImpl representing the prepared frame.
  //   - a LocalFrame inspector that collects messages from the prepared frame.
  std::tuple<RenderFrameHostImpl*, std::unique_ptr<LocalFrameInterceptor>>
  PrepareFrame(
      const std::string& source_url,
      bool from_subframe = false,
      blink::mojom::InsecureRequestPolicy main_frame_insecure_request_policy =
          blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone) {
    // Loads the page of `source_url` first.
    NavigateAndCommit(GURL(source_url));

    TestRenderFrameHost* rfh = main_test_rfh();
    rfh->DidEnforceInsecureRequestPolicy(main_frame_insecure_request_policy);
    if (from_subframe) {
      // Request is from a subframe of the page of `source_url`.
      TestRenderFrameHost* subframe = rfh->AppendChild("subframe");
      rfh = static_cast<TestRenderFrameHost*>(
          NavigationSimulator::NavigateAndCommitFromDocument(
              GURL(source_url + "/subframe"), subframe));
    }
    auto interceptor = std::make_unique<LocalFrameInterceptor>(rfh);
    return std::make_tuple(rfh, std::move(interceptor));
  }

  // Expects no report to renderer no matter blocking happens or not.
  void ExpectNoReportToRenderer(LocalFrameInterceptor* inspector) {
    inspector->FlushLocalFrameMessages();
    EXPECT_THAT(inspector->mixed_content_result(), Eq(std::nullopt));
    EXPECT_THAT(inspector->reported_web_features(), IsEmpty());
  }
};

using MixedContentCheckerShouldBlockFetchKeepAliveTest =
    MixedContentCheckerShouldBlockFetchKeepAliveTestBase;

INSTANTIATE_TEST_SUITE_P(
    All,
    MixedContentCheckerShouldBlockFetchKeepAliveTest,
    ::testing::Values(false, true),
    [](const testing::TestParamInfo<
        MixedContentCheckerShouldBlockFetchKeepAliveTest::ParamType>& info) {
      return info.param ? "ForRedirect" : "ForNonRedirect";
    });

// Loading insecure url from insecure main frame should not be blocked.
TEST_P(MixedContentCheckerShouldBlockFetchKeepAliveTest,
       ShouldNotBlockInsecureFetchFromInsecureMainFrame) {
  const GURL url("http://target.com");
  const auto [rfh, inspector] = PrepareFrame("http://source.com");

  EXPECT_FALSE(
      MixedContentChecker::ShouldBlockFetchKeepAlive(rfh, url, for_redirect()));

  ExpectNoReportToRenderer(inspector.get());
}

// Loading insecure url from insecure subframe should not be blocked.
TEST_P(MixedContentCheckerShouldBlockFetchKeepAliveTest,
       ShouldNotBlockInsecureFetchFromInsecureSubFrame) {
  const bool from_subframe = true;
  const GURL url("http://target.com");
  const auto [rfh, inspector] =
      PrepareFrame("http://source.com", from_subframe);

  EXPECT_FALSE(
      MixedContentChecker::ShouldBlockFetchKeepAlive(rfh, url, for_redirect()));

  ExpectNoReportToRenderer(inspector.get());
}

// Loading insecure url from secure main/sub frame should be blocked, where the
// frame's InsecureRequestPolicy = kLeaveInsecureRequestsAlone.
TEST_P(
    MixedContentCheckerShouldBlockFetchKeepAliveTest,
    ShouldBlockInsecureFetchFromSecureFrameWithPolicyLeaveInsecureRequestAlone) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
  const GURL url("http://target.com");
  {
    const bool from_subframe = false;
    const auto [rfh, inspector] =
        PrepareFrame("https://source.com", from_subframe,
                     main_frame_insecure_request_policy);

    EXPECT_TRUE(MixedContentChecker::ShouldBlockFetchKeepAlive(rfh, url,
                                                               for_redirect()));

    ExpectNoReportToRenderer(inspector.get());
  }
  {
    const bool from_subframe = true;
    const auto [rfh, inspector] =
        PrepareFrame("https://source.com", from_subframe,
                     main_frame_insecure_request_policy);

    EXPECT_TRUE(MixedContentChecker::ShouldBlockFetchKeepAlive(rfh, url,
                                                               for_redirect()));

    ExpectNoReportToRenderer(inspector.get());
  }
}

// Loading insecure url from secure main/sub frame should be blocked, where the
// frame's InsecureRequestPolicy = kBlockAllMixedContent.
TEST_P(MixedContentCheckerShouldBlockFetchKeepAliveTest,
       ShouldBlockInsecureFetchFromSecureFrameWithPolicyBlockAllMixedContent) {
  const auto main_frame_insecure_request_policy =
      blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent;
  const GURL url("http://target.com");
  {
    const bool from_subframe = false;
    const auto [rfh, inspector] =
        PrepareFrame("https://source.com", from_subframe,
                     main_frame_insecure_request_policy);

    EXPECT_TRUE(MixedContentChecker::ShouldBlockFetchKeepAlive(rfh, url,
                                                               for_redirect()));

    ExpectNoReportToRenderer(inspector.get());
  }
  {
    const bool from_subframe = true;
    const auto [rfh, inspector] =
        PrepareFrame("https://source.com", from_subframe,
                     main_frame_insecure_request_policy);

    EXPECT_TRUE(MixedContentChecker::ShouldBlockFetchKeepAlive(rfh, url,
                                                               for_redirect()));

    ExpectNoReportToRenderer(inspector.get());
  }
}

using MixedContentCheckerShouldBlockLNATest =
    MixedContentCheckerShouldBlockNavigationTestBase;

INSTANTIATE_TEST_SUITE_P(
    All,
    MixedContentCheckerShouldBlockLNATest,
    ::testing::Values(false, true),
    [](const testing::TestParamInfo<
        MixedContentCheckerShouldBlockNavigationTest::ParamType>& info) {
      return info.param ? "ForRedirect" : "ForNonRedirect";
    });

// Local Network Access(LNA) request should not block before LNA checks.
TEST_P(MixedContentCheckerShouldBlockLNATest,
       ShouldNotBlockNavigationFromLocalHostname) {
  base::test::ScopedFeatureList feature_list(
      network::features::kLocalNetworkAccessChecks);
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://target.local", from_subframe);
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(inspector->mixed_content_result(),
              Optional(FieldsAre(GURL("https://source.com"),
                                 GURL("http://target.local"),
                                 /*was_allowed=*/true, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentBlockable));
}

TEST_P(MixedContentCheckerShouldBlockLNATest,
       ShouldNotBlockNavigationFromLocalIP) {
  base::test::ScopedFeatureList feature_list(
      network::features::kLocalNetworkAccessChecks);
  const bool from_subframe = true;
  const auto [nav, inspector] = StartNavigation(
      "https://source.com", "http://192.168.1.1", from_subframe);
  auto checker = MixedContentChecker();

  EXPECT_FALSE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                             for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://192.168.1.1"),
                         /*was_allowed=*/true, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentBlockable));
}

// Non-LNA requests should be blocked as normal.
TEST_P(MixedContentCheckerShouldBlockLNATest, ShouldBlockNonLNARequest) {
  base::test::ScopedFeatureList feature_list(
      network::features::kLocalNetworkAccessChecks);
  const bool from_subframe = true;
  const auto [nav, inspector] =
      StartNavigation("https://source.com", "http://target.com", from_subframe);
  auto checker = MixedContentChecker();

  EXPECT_TRUE(checker.ShouldBlockNavigation(*nav->GetNavigationHandle(),
                                            for_redirect()));
  inspector->FlushLocalFrameMessages();
  EXPECT_THAT(
      inspector->mixed_content_result(),
      Optional(FieldsAre(GURL("https://source.com"), GURL("http://target.com"),
                         /*was_allowed=*/false, for_redirect())));
  EXPECT_THAT(
      inspector->reported_web_features(),
      UnorderedElementsAre(blink::mojom::WebFeature::kMixedContentPresent,
                           blink::mojom::WebFeature::kMixedContentBlockable));
}

}  // namespace content
