910e62b5创建于 1月15日历史提交
// Copyright 2018 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/browsing_data/clear_site_data_handler.h"

#include <memory>
#include <optional>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "content/public/browser/storage_partition_config.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "net/base/load_flags.h"
#include "net/cookies/cookie_partition_key.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"

using ::testing::_;

namespace content {

using ConsoleMessagesDelegate = ClearSiteDataHandler::ConsoleMessagesDelegate;
using Message = ClearSiteDataHandler::ConsoleMessagesDelegate::Message;

namespace {

const char kClearCookiesHeader[] = "\"cookies\"";
const char kClearPrefetchCacheHeader[] = "\"prefetchCache\"";
const char kClearPrerenderCacheHeader[] = "\"prerenderCache\"";
const char kClearPrefetchAndPrerenderCacheHeader[] =
    "\"prefetchCache\", \"prerenderCache\"";

const StoragePartitionConfig kTestStoragePartitionConfig;

// A slightly modified ClearSiteDataHandler for testing with dummy clearing
// functionality.
class TestHandler : public ClearSiteDataHandler {
 public:
  TestHandler(base::WeakPtr<BrowserContext> browser_context,
              base::WeakPtr<WebContents> web_contents,
              const StoragePartitionConfig& storage_partition_config,
              const GURL& url,
              const std::string& header_value,
              int load_flags,
              const std::optional<net::CookiePartitionKey> cookie_partition_key,
              const std::optional<blink::StorageKey> storage_key,
              bool partitioned_state_allowed_only,
              base::OnceClosure callback,
              std::unique_ptr<ConsoleMessagesDelegate> delegate)
      : ClearSiteDataHandler(browser_context,
                             web_contents,
                             storage_partition_config,
                             url,
                             header_value,
                             load_flags,
                             cookie_partition_key,
                             storage_key,
                             partitioned_state_allowed_only,
                             std::move(callback),
                             std::move(delegate)) {}
  ~TestHandler() override = default;

  // |HandleHeaderAndOutputConsoleMessages()| is protected and not visible in
  // test cases.
  bool DoHandleHeader() { return HandleHeaderAndOutputConsoleMessages(); }

  MOCK_METHOD8(
      ClearSiteData,
      void(const StoragePartitionConfig& storage_partition_config,
           const url::Origin& origin,
           const ClearSiteDataTypeSet clear_site_data_types,
           const std::set<std::string>& storage_buckets_to_remove,
           bool avoid_closing_connections,
           const std::optional<net::CookiePartitionKey> cookie_partition_key,
           const std::optional<blink::StorageKey> storage_key,
           bool partitioned_state_allowed_only));

 protected:
  void ExecuteClearingTask(
      const url::Origin& origin,
      const ClearSiteDataTypeSet clear_site_data_types,
      const std::set<std::string>& storage_buckets_to_remove,
      base::OnceClosure callback) override {
    ClearSiteData(StoragePartitionConfigForTesting(), origin,
                  clear_site_data_types, storage_buckets_to_remove, false,
                  CookiePartitionKeyForTesting(), StorageKeyForTesting(),
                  PartitionedStateOnlyForTesting());

    // NOTE: ResourceThrottle expects Resume() to be called asynchronously.
    // For the purposes of this test, synchronous call works correctly, and
    // is preferable for simplicity, so that we don't have to synchronize
    // between triggering Clear-Site-Data and verifying test expectations.
    std::move(callback).Run();
  }
};

// A ConsoleDelegate that copies message to a vector |message_buffer| owned by
// the caller instead of outputs to the console.
// We need this override because otherwise messages are outputted as soon as
// request finished, and we don't have a chance to check them.
class VectorConsoleMessagesDelegate : public ConsoleMessagesDelegate {
 public:
  VectorConsoleMessagesDelegate(std::vector<Message>* message_buffer)
      : message_buffer_(message_buffer) {}
  ~VectorConsoleMessagesDelegate() override = default;

  void OutputMessages(base::WeakPtr<WebContents> web_contents) override {
    *message_buffer_ = GetMessagesForTesting();
  }

 private:
  raw_ptr<std::vector<Message>> message_buffer_;
};

// A ConsoleDelegate that outputs messages to a string |output_buffer| owned
// by the caller instead of to the console (losing the level information).
class StringConsoleMessagesDelegate : public ConsoleMessagesDelegate {
 public:
  StringConsoleMessagesDelegate(std::string* output_buffer) {
    SetOutputFormattedMessageFunctionForTesting(base::BindRepeating(
        &StringConsoleMessagesDelegate::OutputFormattedMessage,
        base::Unretained(output_buffer)));
  }

  ~StringConsoleMessagesDelegate() override {}

 private:
  static void OutputFormattedMessage(std::string* output_buffer,
                                     WebContents* web_contents,
                                     blink::mojom::ConsoleMessageLevel level,
                                     const std::string& formatted_text) {
    *output_buffer += formatted_text + "\n";
  }
};

}  // namespace

class ClearSiteDataHandlerTest : public testing::Test,
                                 public testing::WithParamInterface<bool> {
 public:
  ClearSiteDataHandlerTest()
      : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP) {}

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

  bool IsStorageBucketSupportEnabled() { return GetParam(); }

 private:
  BrowserTaskEnvironment task_environment_;
};

INSTANTIATE_TEST_SUITE_P(
    ParseHeaderAndExecuteClearingTaskWithFeaturesEnabledTestSuite,
    ClearSiteDataHandlerTest,
    testing::Bool());

TEST_P(ClearSiteDataHandlerTest, ParseHeaderAndExecuteClearingTask) {
  std::vector<base::test::FeatureRef> features_to_enable;
  std::vector<base::test::FeatureRef> features_to_disable;
  if (IsStorageBucketSupportEnabled()) {
    features_to_enable.push_back(blink::features::kStorageBuckets);
  } else {
    features_to_disable.push_back(blink::features::kStorageBuckets);
  }
  base::test::ScopedFeatureList features;
  features.InitWithFeatures(features_to_enable, features_to_disable);

  struct TestCase {
    const char* header;
    bool cookies;
    bool storage;
    bool cache;
    bool client_hints;
    std::set<std::string> storage_buckets_to_remove;
  };

  std::set<std::string> storage_buckets_test_case_expectation = {"drafts",
                                                                 "inbox"};

  std::vector<TestCase> test_cases = {
      // One data type.
      {"\"cookies\"", true, false, false, false},
      {"\"storage\"", false, true, false, false},
      {"\"cache\"", false, false, true, false},
      {"\"clientHints\"", false, false, false, true},

      // Two data types.
      {"\"cookies\", \"storage\"", true, true, false, false},
      {"\"cookies\", \"cache\"", true, false, true, false},
      {"\"storage\", \"cache\"", false, true, true, false},
      {"\"cookies\", \"clientHints\"", true, false, false, true},
      {"\"storage\", \"clientHints\"", false, true, false, true},
      {"\"cache\", \"clientHints\"", false, false, true, true},

      // Three data types.
      {"\"cookies\", \"storage\", \"cache\"", true, true, true, false},
      {"\"clientHints\", \"storage\", \"cache\"", false, true, true, true},
      {"\"cookies\", \"clientHints\", \"cache\"", true, false, true, true},
      {"\"cookies\", \"storage\", \"clientHints\"", true, true, false, true},

      // Four data types.
      {"\"cookies\", \"storage\", \"cache\", \"clientHints\"", true, true, true,
       true},

      // Wildcard.
      {"\"*\"", true, true, true, true},
      {"\"*\", \"storage\"", true, true, true, true},
      {"\"cookies\", \"*\", \"storage\"", true, true, true, true},
      {"\"*\", \"cookies\", \"*\"", true, true, true, true},
      {"\"*\", \"clientHints\"", true, true, true, true},

      // Different formatting.
      {"\"cookies\"", true, false, false, false},

      // Duplicates.
      {"\"cookies\", \"cookies\"", true, false, false, false},

      // Other JSON-formatted items in the list.
      {"\"storage\", { \"other_params\": {} }", false, true, false, false},

      // Unknown types are ignored, but we still proceed with the deletion for
      // those that we recognize.
      {"\"cache\", \"foo\"", false, false, true, false},

      // Storage Buckets
      {"\"storage\", \"storage:drafts\"", false, true, false, false},
      {"\"*\", \"storage:drafts\", \"storage:inbox\"", true, true, true, true,
       std::set<std::string>()},
      {"\"cookies\", \"storage:drafts", true, false, false,
       false},  // Invalid header, should end with '"'
      {"\"cookies\", \"storage:invalid_name$#$\"", true, false, false,
       false},  // Invalid bucket name

      {"\"cookies\", \"storage:drafts\", \"storage:inbox\"", true, false, false,
       false,
       IsStorageBucketSupportEnabled() ? storage_buckets_test_case_expectation
                                       : std::set<std::string>()},
  };

  if (!base::FeatureList::IsEnabled(blink::features::kStorageBuckets)) {
    storage_buckets_test_case_expectation.clear();
  }

  for (const TestCase& test_case : test_cases) {
    SCOPED_TRACE(test_case.header);

    // Test that ParseHeader works correctly.
    ClearSiteDataTypeSet clear_site_data_types;
    std::set<std::string> storage_buckets_to_remove = {};

    GURL url("https://example.com");
    ConsoleMessagesDelegate console_delegate;

    base::HistogramTester histogram_tester;
    bool success = ClearSiteDataHandler::ParseHeaderForTesting(
        test_case.header, &clear_site_data_types, &storage_buckets_to_remove,
        &console_delegate, url);
    if (!test_case.cookies && !test_case.storage && !test_case.cache &&
        !test_case.client_hints &&
        test_case.storage_buckets_to_remove.empty()) {
      EXPECT_FALSE(success);
      continue;
    }
    EXPECT_TRUE(success);

    EXPECT_EQ(test_case.cookies,
              clear_site_data_types.Has(ClearSiteDataType::kCookies));
    EXPECT_EQ(test_case.storage,
              clear_site_data_types.Has(ClearSiteDataType::kStorage));
    EXPECT_EQ(test_case.cache,
              clear_site_data_types.Has(ClearSiteDataType::kCache));
    EXPECT_EQ(test_case.client_hints,
              clear_site_data_types.Has(ClearSiteDataType::kClientHints));
    EXPECT_EQ(test_case.storage_buckets_to_remove, storage_buckets_to_remove);

    // Count the number of bits in a mask that are 1.
    auto count_ones_in_mask = [](int mask) {
      int count = 0;
      for (size_t i = 0; i < sizeof(mask) * 8; ++i) {
        count += (mask >> i) & 1;
      }
      return count;
    };
    histogram_tester.ExpectTotalCount("Storage.ClearSiteDataHeader.Parameters",
                                      1);
    int sample =
        histogram_tester.GetTotalSum("Storage.ClearSiteDataHeader.Parameters");
    // There should be one bit set to one for each data type seen.
    EXPECT_EQ(count_ones_in_mask(sample),
              static_cast<int>(test_case.cookies) +
                  static_cast<int>(test_case.storage) +
                  static_cast<int>(test_case.cache) +
                  static_cast<int>(!storage_buckets_to_remove.empty()) +
                  static_cast<int>(test_case.client_hints));

    // Test that a call with the above parameters actually reaches
    // ExecuteClearingTask().
    TestHandler handler(
        nullptr, nullptr, kTestStoragePartitionConfig, url, test_case.header,
        net::LOAD_NORMAL, /*cookie_partition_key=*/std::nullopt,
        /*storage_key=*/std::nullopt, /*partitioned_state_allowed_only=*/false,
        base::DoNothing(), std::make_unique<ConsoleMessagesDelegate>());

    EXPECT_CALL(handler, ClearSiteData(
                             _, url::Origin::Create(url), clear_site_data_types,
                             test_case.storage_buckets_to_remove, _, _, _, _));
    bool defer = handler.DoHandleHeader();
    EXPECT_TRUE(defer);

    testing::Mock::VerifyAndClearExpectations(&handler);
  }
}

TEST_F(ClearSiteDataHandlerTest, InvalidHeader) {
  struct TestCase {
    const char* header;
    const char* console_message;
  } test_cases[] = {{"", "No recognized types specified.\n"},
                    {"\"unclosed",
                     "Unrecognized type: \"unclosed.\n"
                     "No recognized types specified.\n"},
                    {"\"passwords\"",
                     "Unrecognized type: \"passwords\".\n"
                     "No recognized types specified.\n"},
                    // The wildcard datatype is not yet shipped.
                    {"[ \"*\" ]",
                     "Unrecognized type: [ \"*\" ].\n"
                     "No recognized types specified.\n"},
                    {"[ \"list\" ]",
                     "Unrecognized type: [ \"list\" ].\n"
                     "No recognized types specified.\n"},
                    {"{ \"cookies\": [ \"a\" ] }",
                     "Unrecognized type: { \"cookies\": [ \"a\" ] }.\n"
                     "No recognized types specified.\n"},
                    {"\"кукис\", \"сторидж\", \"кэш\"",
                     "Must only contain ASCII characters.\n"}};

  for (const TestCase& test_case : test_cases) {
    SCOPED_TRACE(test_case.header);

    ClearSiteDataTypeSet clear_site_data_types;
    std::set<std::string> actual_storage_buckets_to_remove;

    ConsoleMessagesDelegate console_delegate;

    EXPECT_FALSE(ClearSiteDataHandler::ParseHeaderForTesting(
        test_case.header, &clear_site_data_types,
        &actual_storage_buckets_to_remove, &console_delegate, GURL()));

    std::string multiline_message;
    for (const auto& message : console_delegate.GetMessagesForTesting()) {
      EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kWarning, message.level);
      multiline_message += message.text + "\n";
    }

    EXPECT_EQ(test_case.console_message, multiline_message);
  }
}

TEST_F(ClearSiteDataHandlerTest, ClearCookieSuccess) {
  std::vector<Message> message_buffer;
  TestHandler handler(
      nullptr, nullptr, kTestStoragePartitionConfig,
      GURL("https://example.com"), kClearCookiesHeader, net::LOAD_NORMAL,
      /*cookie_partition_key=*/std::nullopt, /*storage_key=*/std::nullopt,
      /*partitioned_state_allowed_only=*/false, base::DoNothing(),
      std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));

  EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _, _));
  bool defer = handler.DoHandleHeader();
  EXPECT_TRUE(defer);
  EXPECT_EQ(1u, message_buffer.size());
  EXPECT_EQ(
      "Cleared data types: \"cookies\". "
      "Clearing channel IDs and HTTP authentication cache is currently "
      "not supported, as it breaks active network connections.",
      message_buffer.front().text);
  EXPECT_EQ(message_buffer.front().level,
            blink::mojom::ConsoleMessageLevel::kInfo);
  testing::Mock::VerifyAndClearExpectations(&handler);
}

TEST_F(ClearSiteDataHandlerTest, LoadDoNotSaveCookies) {
  std::vector<Message> message_buffer;
  TestHandler handler(
      nullptr, nullptr, kTestStoragePartitionConfig,
      GURL("https://example.com"), kClearCookiesHeader,
      net::LOAD_DO_NOT_SAVE_COOKIES, /*cookie_partition_key=*/std::nullopt,
      /*storage_key=*/std::nullopt, /*partitioned_state_allowed_only=*/false,
      base::DoNothing(),
      std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));

  EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _, _)).Times(0);
  bool defer = handler.DoHandleHeader();
  EXPECT_FALSE(defer);
  EXPECT_EQ(1u, message_buffer.size());
  EXPECT_EQ(
      "The request's credentials mode prohibits modifying cookies "
      "and other local data.",
      message_buffer.front().text);
  EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
            message_buffer.front().level);
  testing::Mock::VerifyAndClearExpectations(&handler);
}

TEST_F(ClearSiteDataHandlerTest, InvalidOrigin) {
  struct TestCase {
    const char* origin;
    bool expect_success;
    std::string error_message;  // Tested only if |expect_success| = false.
  } kTestCases[] = {
      // The handler only works on secure origins.
      {"https://secure-origin.com", true, ""},
      {"filesystem:https://secure-origin.com/temporary/", true, ""},

      // That includes localhost.
      {"http://localhost", true, ""},

      // Not on insecure origins.
      {"http://insecure-origin.com", false,
       "Not supported for insecure origins."},
      {"filesystem:http://insecure-origin.com/temporary/", false,
       "Not supported for insecure origins."},

      // Not on unique origins.
      {"data:unique-origin;", false, "Not supported for unique origins."},
  };

  for (const TestCase& test_case : kTestCases) {
    std::vector<Message> message_buffer;
    TestHandler handler(
        nullptr, nullptr, kTestStoragePartitionConfig, GURL(test_case.origin),
        kClearCookiesHeader, net::LOAD_NORMAL,
        /*cookie_partition_key=*/std::nullopt, /*storage_key=*/std::nullopt,
        /*partitioned_state_allowed_only=*/false, base::DoNothing(),
        std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));

    EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _, _))
        .Times(test_case.expect_success ? 1 : 0);

    bool defer = handler.DoHandleHeader();

    EXPECT_EQ(defer, test_case.expect_success);
    EXPECT_EQ(message_buffer.size(), 1u);
    EXPECT_EQ(test_case.expect_success
                  ? blink::mojom::ConsoleMessageLevel::kInfo
                  : blink::mojom::ConsoleMessageLevel::kError,
              message_buffer.front().level);
    if (!test_case.expect_success) {
      EXPECT_EQ(test_case.error_message, message_buffer.front().text);
    }
    testing::Mock::VerifyAndClearExpectations(&handler);
  }
}

// Verifies that console outputs from various actions on different URLs
// are correctly pretty-printed to the console.
TEST_F(ClearSiteDataHandlerTest, FormattedConsoleOutput) {
  struct TestCase {
    const char* header;
    const char* url;
    const char* output;
  } kTestCases[] = {
      // Successful deletion outputs one line, and in case of cookies, also
      // a disclaimer about omitted data (https://crbug.com/798760).
      {"\"cookies\"", "https://origin1.com/foo",
       "Clear-Site-Data header on 'https://origin1.com/foo': "
       "Cleared data types: \"cookies\". "
       "Clearing channel IDs and HTTP authentication cache is currently "
       "not supported, as it breaks active network connections.\n"},

      // Another successful deletion.
      {"\"storage\"", "https://origin2.com/foo",
       "Clear-Site-Data header on 'https://origin2.com/foo': "
       "Cleared data types: \"storage\".\n"},

      // Redirect to the same URL. Unsuccessful deletion outputs two lines.
      {"\"foo\"", "https://origin2.com/foo",
       "Clear-Site-Data header on 'https://origin2.com/foo': "
       "Unrecognized type: \"foo\".\n"
       "Clear-Site-Data header on 'https://origin2.com/foo': "
       "No recognized types specified.\n"},

      // Redirect to another URL. Another unsuccessful deletion.
      {"\"some text\"", "https://origin3.com/bar",
       "Clear-Site-Data header on 'https://origin3.com/bar': "
       "Unrecognized type: \"some text\".\n"
       "Clear-Site-Data header on 'https://origin3.com/bar': "
       "No recognized types specified.\n"},

      // Yet another on the same URL.
      {"\"passwords\"", "https://origin3.com/bar",
       "Clear-Site-Data header on 'https://origin3.com/bar': "
       "Unrecognized type: \"passwords\".\n"
       "Clear-Site-Data header on 'https://origin3.com/bar': "
       "No recognized types specified.\n"},

      // Successful deletion on the same URL.
      {"\"cache\"", "https://origin3.com/bar",
       "Clear-Site-Data header on 'https://origin3.com/bar': "
       "Cleared data types: \"cache\".\n"},

      // Successful deletion for client hints.
      {"\"clientHints\"", "https://origin3.com/bar",
       "Clear-Site-Data header on 'https://origin3.com/bar': "
       "Cleared data types: \"clientHints\".\n"},

      // Successful deletion for *.
      {"\"*\"", "https://origin3.com/bar",
       "Clear-Site-Data header on 'https://origin3.com/bar': Cleared data "
       "types: \"cookies\", \"storage\", \"cache\", \"clientHints\". Clearing "
       "channel IDs and HTTP authentication cache is currently not supported, "
       "as it breaks active network connections.\n"},

      // Redirect to the original URL.
      // Successful deletion outputs one line.
      {"", "https://origin1.com/foo",
       "Clear-Site-Data header on 'https://origin1.com/foo': "
       "No recognized types specified.\n"},
  };

  // TODO(crbug.com/41409604): Delay output until next frame for navigations.
  bool kHandlerTypeIsNavigation[] = {false};

  for (bool navigation : kHandlerTypeIsNavigation) {
    SCOPED_TRACE(navigation ? "Navigation test." : "Subresource test.");

    std::string output_buffer;
    std::string last_seen_console_output;

    // |NetworkServiceClient| creates a new |ClearSiteDataHandler| for each
    // navigation, redirect, or subresource header responses.
    for (const auto& test : kTestCases) {
      TestHandler handler(
          nullptr, nullptr, kTestStoragePartitionConfig, GURL(test.url),
          test.header, net::LOAD_NORMAL, /*cookie_partition_key=*/std::nullopt,
          /*storage_key=*/std::nullopt,
          /*partitioned_state_allowed_only=*/false, base::DoNothing(),
          std::make_unique<StringConsoleMessagesDelegate>(&output_buffer));
      handler.DoHandleHeader();

      // For navigations, the console should be still empty. For subresource
      // requests, messages should be added progressively.
      if (navigation) {
        EXPECT_TRUE(output_buffer.empty());
      } else {
        EXPECT_EQ(last_seen_console_output + test.output, output_buffer);
      }

      last_seen_console_output = output_buffer;
    }

    // At the end, the console must contain all messages regardless of whether
    // it was a navigation or a subresource request.
    std::string expected_output;
    for (struct TestCase& test_case : kTestCases)
      expected_output += test_case.output;
    EXPECT_EQ(expected_output, output_buffer);
  }
}

TEST_F(ClearSiteDataHandlerTest, CookiePartitionKey) {
  std::optional<net::CookiePartitionKey> cookie_partition_keys[] = {
      std::nullopt,
      net::CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")),
  };
  const GURL kTestURL("https://www.bar.com");

  for (const auto& cookie_partition_key : cookie_partition_keys) {
    std::string output_buffer;
    TestHandler handler(
        nullptr, nullptr, kTestStoragePartitionConfig, kTestURL, "\"cookies\"",
        net::LOAD_NORMAL, cookie_partition_key, /*storage_key=*/std::nullopt,
        /*partitioned_state_allowed_only=*/false, base::DoNothing(),
        std::make_unique<StringConsoleMessagesDelegate>(&output_buffer));
    EXPECT_CALL(handler,
                ClearSiteData(_, _, _, _, _, cookie_partition_key, _, _));
    EXPECT_TRUE(handler.DoHandleHeader());
  }
}

TEST_F(ClearSiteDataHandlerTest, StorageKey) {
  std::optional<blink::StorageKey> storage_keys[] = {
      std::nullopt,
      blink::StorageKey::CreateFromStringForTesting("https://example.com")};
  const GURL kTestURL("https://example.com");

  for (const auto& storage_key : storage_keys) {
    std::string output_buffer;
    TestHandler handler(
        nullptr, nullptr, kTestStoragePartitionConfig, kTestURL, "\"storage\"",
        net::LOAD_NORMAL, /*cookie_partition_key=*/std::nullopt, storage_key,
        /*partitioned_state_allowed_only=*/false, base::DoNothing(),
        std::make_unique<StringConsoleMessagesDelegate>(&output_buffer));
    EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, storage_key, _));
    EXPECT_TRUE(handler.DoHandleHeader());
  }
}

TEST_F(ClearSiteDataHandlerTest, ThirdPartyCookieBlockingEnabled) {
  bool test_cases[] = {true, false};
  const GURL kTestURL("https://example.com");

  for (const auto partitioned_state_allowed_only : test_cases) {
    SCOPED_TRACE(base::StringPrintf("partitioned_state_allowed_only: %d",
                                    partitioned_state_allowed_only));
    std::string output_buffer;
    TestHandler handler(
        nullptr, nullptr, kTestStoragePartitionConfig, kTestURL, "\"storage\"",
        net::LOAD_NORMAL, /*cookie_partition_key=*/std::nullopt,
        /*storage_key=*/std::nullopt, partitioned_state_allowed_only,
        base::DoNothing(),
        std::make_unique<StringConsoleMessagesDelegate>(&output_buffer));
    EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _,
                                       partitioned_state_allowed_only));
    EXPECT_TRUE(handler.DoHandleHeader());
  }
}

TEST_F(ClearSiteDataHandlerTest, CorrectStoragePartition) {
  const GURL kTestURL("https://example.com");
  TestBrowserContext test_browser_context;
  test_browser_context.set_is_off_the_record(false);

  std::vector<StoragePartitionConfig> test_cases{
      StoragePartitionConfig::Create(&test_browser_context, "test_domain_0",
                                     "test name 1", true),
      StoragePartitionConfig::Create(&test_browser_context, "test_domain_1",
                                     "test name 2", false),
      StoragePartitionConfig::Create(&test_browser_context, "test_domain_1",
                                     "test name 3", false),
      StoragePartitionConfig::Create(&test_browser_context, "test_domain_1", "",
                                     false),
  };

  for (const StoragePartitionConfig& storage_partition_config : test_cases) {
    std::string output_buffer;
    TestHandler handler(
        nullptr, nullptr, storage_partition_config, kTestURL, "\"storage\"",
        net::LOAD_NORMAL, /*cookie_partition_key=*/std::nullopt,
        /*storage_key=*/std::nullopt, /*partitioned_state_allowed_only=*/false,
        base::DoNothing(),
        std::make_unique<StringConsoleMessagesDelegate>(&output_buffer));
    EXPECT_CALL(handler,
                ClearSiteData(storage_partition_config, _, _, _, _, _, _, _));
    EXPECT_TRUE(handler.DoHandleHeader());
  }
}

TEST_F(ClearSiteDataHandlerTest, ClearPrefetchCacheSuccess) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(
      blink::features::kClearSiteDataPrefetchPrerenderCache);

  std::vector<Message> message_buffer;
  TestHandler handler(
      nullptr, nullptr, kTestStoragePartitionConfig,
      GURL("https://example.com"), kClearPrefetchCacheHeader, net::LOAD_NORMAL,
      /*cookie_partition_key=*/std::nullopt, /*storage_key=*/std::nullopt,
      /*partitioned_state_allowed_only=*/false, base::DoNothing(),
      std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));

  EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _, _));
  bool defer = handler.DoHandleHeader();
  EXPECT_TRUE(defer);
  EXPECT_EQ(1u, message_buffer.size());
  EXPECT_EQ("Cleared data types: \"prefetchCache\".",
            message_buffer.front().text);
  EXPECT_EQ(message_buffer.front().level,
            blink::mojom::ConsoleMessageLevel::kInfo);
  testing::Mock::VerifyAndClearExpectations(&handler);
}

TEST_F(ClearSiteDataHandlerTest, ClearPrerenderCacheSuccess) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(
      blink::features::kClearSiteDataPrefetchPrerenderCache);

  std::vector<Message> message_buffer;
  TestHandler handler(
      nullptr, nullptr, kTestStoragePartitionConfig,
      GURL("https://example.com"), kClearPrerenderCacheHeader, net::LOAD_NORMAL,
      /*cookie_partition_key=*/std::nullopt, /*storage_key=*/std::nullopt,
      /*partitioned_state_allowed_only=*/false, base::DoNothing(),
      std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));

  EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _, _));
  bool defer = handler.DoHandleHeader();
  EXPECT_TRUE(defer);
  EXPECT_EQ(1u, message_buffer.size());
  EXPECT_EQ("Cleared data types: \"prerenderCache\".",
            message_buffer.front().text);
  EXPECT_EQ(message_buffer.front().level,
            blink::mojom::ConsoleMessageLevel::kInfo);
  testing::Mock::VerifyAndClearExpectations(&handler);
}

TEST_F(ClearSiteDataHandlerTest, ClearPrefetchAndPrerenderCacheSuccess) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(
      blink::features::kClearSiteDataPrefetchPrerenderCache);

  std::vector<Message> message_buffer;
  TestHandler handler(
      nullptr, nullptr, kTestStoragePartitionConfig,
      GURL("https://example.com"), kClearPrefetchAndPrerenderCacheHeader,
      net::LOAD_NORMAL,
      /*cookie_partition_key=*/std::nullopt, /*storage_key=*/std::nullopt,
      /*partitioned_state_allowed_only=*/false, base::DoNothing(),
      std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));

  EXPECT_CALL(handler, ClearSiteData(_, _, _, _, _, _, _, _));
  bool defer = handler.DoHandleHeader();
  EXPECT_TRUE(defer);
  EXPECT_EQ(1u, message_buffer.size());
  EXPECT_EQ("Cleared data types: \"prefetchCache\", \"prerenderCache\".",
            message_buffer.front().text);
  EXPECT_EQ(message_buffer.front().level,
            blink::mojom::ConsoleMessageLevel::kInfo);
  testing::Mock::VerifyAndClearExpectations(&handler);
}
}  // namespace content