// Copyright 2013 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/storage_partition_impl.h"

#include <stddef.h>
#include <stdint.h>

#include <array>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/threading/sequence_local_storage_slot.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "components/services/storage/dom_storage/async_dom_storage_database.h"
#include "components/services/storage/dom_storage/db_status.h"
#include "components/services/storage/dom_storage/dom_storage_database.h"
#include "components/services/storage/dom_storage/features.h"
#include "components/services/storage/dom_storage/test_support/dom_storage_database_testing.h"
#include "components/services/storage/public/cpp/constants.h"
#include "components/services/storage/public/mojom/local_storage_control.mojom.h"
#include "components/services/storage/public/mojom/storage_service.mojom.h"
#include "components/services/storage/public/mojom/storage_usage_info.mojom.h"
#include "components/services/storage/storage_service_impl.h"
#include "content/browser/code_cache/generated_code_cache.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/declarative_performance_observer/declarative_performance_observer_store.h"
#include "content/browser/gpu/gpu_disk_cache_factory.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/security/cpsp/child_process_security_policy_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/generated_code_cache_settings.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/storage_usage_info.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "net/base/test_completion_callback.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_params.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/net_buildflags.h"
#include "services/network/cookie_manager.h"
#include "services/network/public/cpp/features.h"
#include "services/network/test/mock_device_bound_session_manager.h"
#include "storage/browser/quota/quota_client_type.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/test/mock_quota_client.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_special_storage_policy.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/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(IS_ANDROID)
#include "content/public/browser/android/java_interfaces.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#endif  // BUILDFLAG(IS_ANDROID)

using net::CanonicalCookie;
using CookieDeletionFilter = network::mojom::CookieDeletionFilter;
using CookieDeletionFilterPtr = network::mojom::CookieDeletionFilterPtr;
using ::testing::_;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::SaveArgPointee;
using ::testing::WithArg;

namespace content {
namespace {

const char kCacheKey[] = "key";
const char kCacheValue[] = "cached value";

const storage::QuotaClientType kClientFile =
    storage::QuotaClientType::kFileSystem;

const uint32_t kAllQuotaRemoveMask =
    StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS |
    StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
class RemoveCookieTester {
 public:
  explicit RemoveCookieTester(StoragePartition* storage_partition)
      : storage_partition_(storage_partition) {}

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

  // Returns true, if the given cookie exists in the cookie store.
  bool ContainsCookie(const url::Origin& origin,
                      std::optional<net::CookiePartitionKey>
                          cookie_partition_key = std::nullopt) {
    get_cookie_success_ = false;
    base::RunLoop loop;
    storage_partition_->GetCookieManagerForBrowserProcess()->GetCookieList(
        origin.GetURL(), net::CookieOptions::MakeAllInclusive(),
        net::CookiePartitionKeyCollection(cookie_partition_key),
        base::BindOnce(&RemoveCookieTester::GetCookieListCallback,
                       base::Unretained(this), loop.QuitClosure()));
    loop.Run();
    return get_cookie_success_;
  }

  void AddCookie(const url::Origin& origin,
                 std::optional<net::CookiePartitionKey> cookie_partition_key =
                     std::nullopt) {
    net::CookieInclusionStatus status;
    std::string cookie_str = "A=1";
    if (cookie_partition_key) {
      cookie_str += ";Partitioned;Secure;";
    }
    std::unique_ptr<net::CanonicalCookie> cc(
        net::CanonicalCookie::CreateForTesting(
            origin.GetURL(), cookie_str, base::Time::Now(),
            net::CookieSourceType::kOther,
            /*server_time=*/std::nullopt, cookie_partition_key, &status));
    base::RunLoop loop;
    storage_partition_->GetCookieManagerForBrowserProcess()->SetCanonicalCookie(
        *cc, origin.GetURL(), net::CookieOptions::MakeAllInclusive(),
        base::BindOnce(&RemoveCookieTester::SetCookieCallback,
                       base::Unretained(this), loop.QuitClosure()));
    loop.Run();
  }

 private:
  void GetCookieListCallback(
      base::OnceClosure quit_closure,
      const net::CookieAccessResultList& cookie_list,
      const net::CookieAccessResultList& excluded_cookies) {
    std::string cookie_line =
        net::CanonicalCookie::BuildCookieLine(cookie_list);
    if (cookie_line == "A=1") {
      get_cookie_success_ = true;
    } else if (cookie_line == "A=1; A=1") {
      EXPECT_NE(cookie_list[0].cookie.IsPartitioned(),
                cookie_list[1].cookie.IsPartitioned());
      get_cookie_success_ = true;
    } else {
      EXPECT_EQ("", cookie_line);
      get_cookie_success_ = false;
    }
    std::move(quit_closure).Run();
  }

  void SetCookieCallback(base::OnceClosure quit_closure,
                         net::CookieAccessResult result) {
    ASSERT_TRUE(result.status.IsInclude());
    std::move(quit_closure).Run();
  }

  bool get_cookie_success_;
  raw_ptr<StoragePartition> storage_partition_;
};


class RemoveLocalStorageTester {
 public:
  explicit RemoveLocalStorageTester(TestBrowserContext* browser_context)
      : browser_context_(browser_context) {}

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

  ~RemoveLocalStorageTester() = default;

  // Returns true, if the given origin URL exists.
  bool DOMStorageExistsForOrigin(const url::Origin& origin) {
    for (size_t i = 0; i < infos_.size(); ++i) {
      if (origin == infos_[i].storage_key.origin())
        return true;
    }
    return false;
  }

  void AddDOMStorageTestData(const url::Origin& origin1,
                             const url::Origin& origin2,
                             const url::Origin& origin3) {
    // NOTE: Tests which call this method depend on implementation details of
    // how exactly the Local Storage subsystem stores persistent data.
    // NOTE: it's very important to avoid creating the local storage
    // database/C++ objects before calling this method. That happens when the
    // storage partition is first created/accessed.

    base::RunLoop open_loop;
    auto database = storage::AsyncDomStorageDatabase::Open(
        storage::StorageType::kLocalStorage,
        // Technically this should be the partition path, but in this context
        // it's the same path, and calling `GetStoragePartition()` too early
        // will cause a race against DOMStorageContextWrapper/LocalStorageImpl
        // creation.
        browser_context_->GetPath(),
        /*memory_dump_id=*/std::nullopt,
        /*dir_to_destroy=*/base::FilePath(),
        base::BindLambdaForTesting(
            [&](storage::AsyncDomStorageDatabase::OpenOutcome outcome) {
              ASSERT_TRUE(outcome.open_status.ok());
              open_loop.Quit();
            }));
    open_loop.Run();

    PopulateDatabase(database.get(), origin1, origin2, origin3);
    database.reset();

    // This will trigger creating the `StoragePartitionImpl`, the
    // `DOMStorageContextWrapper`, etc. It shouldn't race `database->database()`
    // destruction because the `DomStorageDatabase` used internally will run on
    // the same sequenced task runner, so opening it will be enqueued behind
    // destruction activities.
    GetLocalStorageUsage();
    EXPECT_TRUE(DOMStorageExistsForOrigin(origin1));
    EXPECT_TRUE(DOMStorageExistsForOrigin(origin2));
    EXPECT_TRUE(DOMStorageExistsForOrigin(origin3));
  }

  static void PopulateLocalStorageMap(storage::AsyncDomStorageDatabase* db,
                                      const url::Origin& origin,
                                      base::Time last_accessed,
                                      base::Time last_modified) {
    // Create an ID for the map using `origin`.
    storage::DomStorageDatabase::MapLocator map_locator{
        blink::StorageKey::CreateFirstParty(origin)};

    // Write a key/value pair to the database for the map.
    storage::FakeCommitter committer(db, map_locator.Clone());
    ASSERT_NO_FATAL_FAILURE(
        committer.PutMapKeyValueSync(/*key=*/{'X'}, /*value=*/{}));

    // Write the map's usage metadata to the database.
    storage::DomStorageDatabase::Metadata usage;
    usage.map_metadata.push_back({
        .map_locator = map_locator.Clone(),
        .last_accessed = last_accessed,
        .last_modified = last_modified,
        .total_size = base::ByteSize{16},
    });
    ASSERT_NO_FATAL_FAILURE(storage::PutMetadataSync(*db, std::move(usage)));
  }

  static void PopulateDatabase(storage::AsyncDomStorageDatabase* db,
                               const url::Origin& origin1,
                               const url::Origin& origin2,
                               const url::Origin& origin3) {
    base::Time now = base::Time::Now();
    ASSERT_NO_FATAL_FAILURE(PopulateLocalStorageMap(
        db, origin1, /*last_accessed=*/now, /*last_modified=*/now));

    base::Time one_day_ago = now - base::Days(1);
    ASSERT_NO_FATAL_FAILURE(
        PopulateLocalStorageMap(db, origin2, /*last_accessed=*/one_day_ago,
                                /*last_modified=*/one_day_ago));

    base::Time sixty_days_ago = now - base::Days(60);
    ASSERT_NO_FATAL_FAILURE(
        PopulateLocalStorageMap(db, origin3, /*last_accessed=*/sixty_days_ago,
                                /*last_modified=*/sixty_days_ago));
  }

  // Clears LocalStorage according to parameters, and refreshes the local cache
  // of metadata.
  void ClearLocalStorage(
      content::StoragePartition* partition,
      const base::Time delete_begin,
      const base::Time delete_end,
      BrowsingDataFilterBuilder* filter_builder,
      StoragePartition::StorageKeyPolicyMatcherFunction storage_key_matcher) {
    base::RunLoop run_loop;
    partition->ClearData(StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
                         filter_builder, std::move(storage_key_matcher),
                         nullptr, false, delete_begin, delete_end,
                         run_loop.QuitClosure());
    run_loop.Run();
    GetLocalStorageUsage();
  }

  void GetLocalStorageUsage() {
    base::test::TestFuture<const std::vector<content::StorageUsageInfo>&>
        future;
    browser_context_->GetDefaultStoragePartition()
        ->GetDOMStorageContext()
        ->GetLocalStorageUsage(future.GetCallback());
    infos_ = future.Get();
  }

 private:
  raw_ptr<TestBrowserContext> browser_context_;

  std::vector<content::StorageUsageInfo> infos_;
};

class RemoveCodeCacheTester {
 public:
  enum Cache { kJs, kWebAssembly };

  explicit RemoveCodeCacheTester(RenderFrameHost& rfh)
      : render_frame_host_(static_cast<RenderFrameHostImpl&>(rfh)) {
    render_frame_host_->CreateCodeCacheHost(
        code_cache_host_.BindNewPipeAndPassReceiver());
  }
  RemoveCodeCacheTester(const RemoveCodeCacheTester&) = delete;
  RemoveCodeCacheTester& operator=(const RemoveCodeCacheTester&) = delete;

  // Returns the entry for `url` if it is found in the code cache for `cache`,
  // or nullopt if not found.
  std::optional<std::string> GetEntry(Cache cache, const GURL& url) {
    // A TestFuture that will get the entry for `url` if there is one, or
    // nullopt otherwise;
    base::test::TestFuture<std::optional<std::string>> future;

    code_cache_host_->FetchCachedCode(
        CacheToCodeCacheType(cache), url,
        base::BindOnce(
            [](base::OnceCallback<void(std::optional<std::string>)> callback,
               base::Time response_time, mojo_base::BigBuffer data) {
              if (!response_time.is_null()) {
                // Cache hit.
                std::move(callback).Run(
                    std::string(base::as_string_view(base::span(data))));
              } else {
                std::move(callback).Run(std::nullopt);  // Cache miss.
              }
            },
            future.GetCallback()));

    return future.Get();
  }

  // Adds `data` to `cache` for a resource generated from `url`.
  void AddEntry(Cache cache, const GURL& url, const std::string& data) {
    code_cache_host_->DidGenerateCacheableMetadata(
        CacheToCodeCacheType(cache), url, base::Time::Now(),
        mojo_base::BigBuffer(base::as_byte_span(data)));
  }

  // Sets the time-of-last-use in `cache` for the resource generated from `url`
  // to `time`. Not supported when UsePersistentCacheForCodeCache is enabled.
  void SetLastUseTime(Cache cache, const GURL& url, base::Time time) {
    if (blink::features::IsPersistentCacheForCodeCacheEnabled()) {
      // PersistentCache does not track time of last use.
      return;
    }
    base::test::TestFuture<void> future;
    GeneratedCodeCacheContext::RunOrPostTask(
        GetCodeCacheContext(), FROM_HERE,
        base::BindOnce(
            [](GeneratedCodeCacheContext* code_cache_context, Cache cache,
               const GURL& url, const GURL& origin_lock, base::Time time,
               base::OnceClosure closure) {
              GetCache(code_cache_context, cache, origin_lock)
                  ->SetLastUsedTimeForTest(url, origin_lock,
                                           net::NetworkIsolationKey(), time,
                                           std::move(closure));
            },
            base::Unretained(GetCodeCacheContext()), cache, url,
            GetOriginLock(), time,
            base::BindPostTaskToCurrentDefault(future.GetCallback())));
    (void)future.Wait();
  }

 private:
  // Returns the blink CodeCacheType corresponding to `cache`.
  static blink::mojom::CodeCacheType CacheToCodeCacheType(Cache cache) {
    switch (cache) {
      case kJs:
        return blink::mojom::CodeCacheType::kJavascript;
      case kWebAssembly:
        return blink::mojom::CodeCacheType::kWebAssembly;
    }
  }

  // Returns the render frame host's GeneratedCodeCacheContext.
  GeneratedCodeCacheContext* GetCodeCacheContext() {
    return render_frame_host_->GetBrowserContext()
        ->GetDefaultStoragePartition()
        ->GetGeneratedCodeCacheContext();
  }

  // Returns the GeneratedCodeCache to use in `code_cache_context` for `cache`
  // data when the render frame host is locked to `origin_lock`.
  static GeneratedCodeCache* GetCache(
      GeneratedCodeCacheContext* code_cache_context,
      Cache cache,
      const GURL& origin_lock) {
    CHECK(!blink::features::IsPersistentCacheForCodeCacheEnabled());
    const bool is_locked_to_webui =
        origin_lock.SchemeIs(kChromeUIScheme) ||
        origin_lock.SchemeIs(kChromeUIUntrustedScheme);
    switch (cache) {
      case kJs:
        return is_locked_to_webui
                   ? code_cache_context->generated_webui_js_code_cache()
                   : code_cache_context->generated_js_code_cache();
      case kWebAssembly:
        return is_locked_to_webui
                   ? nullptr
                   : code_cache_context->generated_wasm_code_cache();
    }
  }

  // Returns the origin to which the render frame host is locked.
  GURL GetOriginLock() {
    return ChildProcessSecurityPolicyImpl::GetInstance()
        ->GetProcessLock(render_frame_host_->GetProcess()->GetID())
        .GetProcessLockURL();
  }

  const raw_ref<RenderFrameHostImpl> render_frame_host_;
  mojo::Remote<blink::mojom::CodeCacheHost> code_cache_host_;
};

class MockDataRemovalObserver : public StoragePartition::DataRemovalObserver {
 public:
  explicit MockDataRemovalObserver(StoragePartition* partition) {
    observation_.Observe(partition);
  }

  MOCK_METHOD4(OnStorageKeyDataCleared,
               void(uint32_t,
                    content::StoragePartition::StorageKeyMatcherFunction,
                    base::Time,
                    base::Time));

 private:
  base::ScopedObservation<StoragePartition,
                          StoragePartition::DataRemovalObserver>
      observation_{this};
};

bool IsWebSafeSchemeForTest(const std::string& scheme) {
  return scheme == url::kHttpScheme;
}

bool DoesOriginMatchForUnprotectedWeb(
    const blink::StorageKey& storage_key,
    storage::SpecialStoragePolicy* special_storage_policy) {
  if (IsWebSafeSchemeForTest(storage_key.origin().scheme())) {
    return !special_storage_policy->IsStorageProtected(
        storage_key.origin().GetURL());
  }

  return false;
}

bool DoesOriginMatchForBothProtectedAndUnprotectedWeb(
    const blink::StorageKey& storage_key,
    storage::SpecialStoragePolicy* special_storage_policy) {
  return true;
}

bool DoesOriginMatchUnprotected(
    const url::Origin& desired_origin,
    const blink::StorageKey& storage_key,
    storage::SpecialStoragePolicy* special_storage_policy) {
  return storage_key.origin().scheme() != desired_origin.scheme();
}

void ClearQuotaData(content::StoragePartition* partition,
                    base::RunLoop* loop_to_quit) {
  partition->ClearData(kAllQuotaRemoveMask, blink::StorageKey(), base::Time(),
                       base::Time::Max(), loop_to_quit->QuitClosure());
}

void ClearQuotaDataWithOriginMatcher(
    content::StoragePartition* partition,
    StoragePartition::StorageKeyPolicyMatcherFunction storage_key_matcher,
    const base::Time delete_begin,
    base::RunLoop* loop_to_quit) {
  partition->ClearData(
      kAllQuotaRemoveMask,
      /*filter_builder=*/nullptr, std::move(storage_key_matcher), nullptr,
      false, delete_begin, base::Time::Max(), loop_to_quit->QuitClosure());
}

void ClearQuotaDataForOrigin(content::StoragePartition* partition,
                             const GURL& remove_origin,
                             const base::Time delete_begin,
                             base::RunLoop* loop_to_quit) {
  partition->ClearData(
      kAllQuotaRemoveMask,
      blink::StorageKey::CreateFirstParty(url::Origin::Create(remove_origin)),
      delete_begin, base::Time::Max(), loop_to_quit->QuitClosure());
}

void ClearQuotaDataTime(content::StoragePartition* partition,
                        const base::Time delete_begin,
                        base::RunLoop* loop_to_quit) {
  partition->ClearData(kAllQuotaRemoveMask, blink::StorageKey(), delete_begin,
                       base::Time::Max(), loop_to_quit->QuitClosure());
}

void ClearCookies(content::StoragePartition* partition,
                  const base::Time delete_begin,
                  const base::Time delete_end,
                  base::RunLoop* run_loop) {
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES,
                       blink::StorageKey(), delete_begin, delete_end,
                       run_loop->QuitClosure());
}

void ClearCookiesMatchingInfo(content::StoragePartition* partition,
                              CookieDeletionFilterPtr delete_filter,
                              base::RunLoop* run_loop) {
  base::Time delete_begin;
  if (delete_filter->created_after_time.has_value())
    delete_begin = delete_filter->created_after_time.value();
  base::Time delete_end;
  if (delete_filter->created_before_time.has_value())
    delete_end = delete_filter->created_before_time.value();
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES,
                       /*filter_builder=*/nullptr,
                       StoragePartition::StorageKeyPolicyMatcherFunction(),
                       std::move(delete_filter), false, delete_begin,
                       delete_end, run_loop->QuitClosure());
}


void ClearData(content::StoragePartition* partition, base::RunLoop* run_loop) {
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
                       blink::StorageKey(), base::Time(), base::Time::Max(),
                       run_loop->QuitClosure());
}

void ClearDataForOrigin(uint32_t remove_mask,
                        content::StoragePartition* partition,
                        const GURL& origin,
                        base::RunLoop* run_loop) {
  partition->ClearDataForOrigin(remove_mask, origin, run_loop->QuitClosure());
}

void ClearCodeCache(content::StoragePartition* partition,
                    base::Time begin_time,
                    base::Time end_time,
                    base::RepeatingCallback<bool(const GURL&)> url_predicate,
                    base::RunLoop* run_loop) {
  partition->ClearCodeCaches(begin_time, end_time, url_predicate,
                             run_loop->QuitClosure());
}

bool FilterURL(const GURL& filter_url, const GURL& url) {
  return url == filter_url;
}

bool FilterMatchesCookie(const CookieDeletionFilterPtr& filter,
                         const net::CanonicalCookie& cookie) {
  return network::DeletionFilterToInfo(filter.Clone())
      .Matches(cookie, net::CookieAccessParams{
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::UNKNOWN, false});
}

}  // namespace

class StoragePartitionImplTest : public testing::Test {
 public:
  explicit StoragePartitionImplTest(
      bool is_local_storage_sqlite_enabled = false) {
    std::vector<base::test::FeatureRef> enabled_features{
        blink::features::kDeclarativePerformanceObserver};
    std::vector<base::test::FeatureRef> disabled_features;
    if (is_local_storage_sqlite_enabled) {
      enabled_features.push_back(storage::kDomStorageSqlite);
      enabled_features.push_back(storage::kDomStorageSqliteInMemory);
    } else {
      disabled_features.push_back(storage::kDomStorageSqlite);
      disabled_features.push_back(storage::kDomStorageSqliteInMemory);
    }
    feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

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

  storage::MockQuotaManager* GetMockManager() {
    if (!quota_manager_.get()) {
      quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
          browser_context_.IsOffTheRecord(), browser_context_.GetPath(),
          GetIOThreadTaskRunner({}).get(),
          browser_context_.GetSpecialStoragePolicy());
      mojo::PendingRemote<storage::mojom::QuotaClient> quota_client;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<storage::MockQuotaClient>(
              quota_manager_->proxy(), storage::QuotaClientType::kFileSystem),
          quota_client.InitWithNewPipeAndPassReceiver());
      quota_manager_->proxy()->RegisterClient(
          std::move(quota_client), storage::QuotaClientType::kFileSystem);
    }
    return quota_manager_.get();
  }

  TestBrowserContext* browser_context() { return &browser_context_; }

  base::test::TaskEnvironment* task_environment() { return &task_environment_; }

 private:
  base::test::ScopedCommandLine command_line_;
  base::test::ScopedFeatureList feature_list_;
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  TestBrowserContext browser_context_;
  scoped_refptr<storage::MockQuotaManager> quota_manager_;
};

// Runs each local storage test twice: once with SQLite and once with LevelDB.
class LocalStoragePartitionImplTest
    : public testing::WithParamInterface</*is_sqlite_enabled=*/bool>,
      public StoragePartitionImplTest {
 public:
  LocalStoragePartitionImplTest()
      : StoragePartitionImplTest(
            /*is_local_storage_sqlite_enabled=*/GetParam()) {}
  ~LocalStoragePartitionImplTest() override = default;
};

INSTANTIATE_TEST_SUITE_P(
    /*no prefix*/,
    LocalStoragePartitionImplTest,
    testing::Bool(),
    /*name_generator=*/
    [](const testing::TestParamInfo<LocalStoragePartitionImplTest::ParamType>&
           info) { return info.param ? "SQLite" : "LevelDB"; });

class StoragePartitionShaderClearTest : public testing::Test {
 public:
  StoragePartitionShaderClearTest() {
    InitGpuDiskCacheFactorySingleton();

    gpu::GpuDiskCacheType type = gpu::GpuDiskCacheType::kGlShaders;
    auto handle = GetGpuDiskCacheFactorySingleton()->GetCacheHandle(
        type, browser_context()->GetDefaultStoragePartition()->GetPath().Append(
                  gpu::GetGpuDiskCacheSubdir(type)));
    cache_ =
        GetGpuDiskCacheFactorySingleton()->Create(handle, base::DoNothing());
  }

  ~StoragePartitionShaderClearTest() override {
    cache_ = nullptr;
    DestroyGpuDiskCacheFactorySingletonForTesting();
  }

  void InitCache() {
    net::TestCompletionCallback available_cb;
    int rv = cache_->SetAvailableCallback(available_cb.callback());
    ASSERT_EQ(net::OK, available_cb.GetResult(rv));
    EXPECT_EQ(0, Size());

    cache_->Cache(kCacheKey, kCacheValue);

    net::TestCompletionCallback complete_cb;

    rv = cache_->SetCacheCompleteCallback(complete_cb.callback());
    ASSERT_EQ(net::OK, complete_cb.GetResult(rv));
  }

  int32_t Size() {
    base::test::TestFuture<int32_t> future;
    base::expected<int32_t, net::Error> result =
        cache_->Size(future.GetCallback());
    if (result.has_value()) {
      return result.value();
    }
    CHECK_EQ(result.error(), net::ERR_IO_PENDING);
    return future.Get();
  }

  TestBrowserContext* browser_context() { return &browser_context_; }

 private:
  content::BrowserTaskEnvironment task_environment_;
  TestBrowserContext browser_context_;

  scoped_refptr<gpu::GpuDiskCache> cache_;
};

// Tests ---------------------------------------------------------------------

TEST_F(StoragePartitionShaderClearTest, ClearShaderCache) {
  InitCache();
  EXPECT_EQ(1, Size());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearData,
                                browser_context()->GetDefaultStoragePartition(),
                                &run_loop));
  run_loop.Run();
  EXPECT_EQ(0, Size());
}

TEST_F(StoragePartitionImplTest, QuotaClientTypesGeneration) {
  EXPECT_THAT(
      StoragePartitionImpl::GenerateQuotaClientTypes(
          StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS),
      testing::UnorderedElementsAre(storage::QuotaClientType::kFileSystem));
  EXPECT_THAT(StoragePartitionImpl::GenerateQuotaClientTypes(
                  StoragePartition::REMOVE_DATA_MASK_INDEXEDDB),
              testing::ElementsAre(storage::QuotaClientType::kIndexedDatabase));
  EXPECT_THAT(
      StoragePartitionImpl::GenerateQuotaClientTypes(kAllQuotaRemoveMask),
      testing::UnorderedElementsAre(
          storage::QuotaClientType::kFileSystem,
          storage::QuotaClientType::kIndexedDatabase));
}

storage::BucketInfo AddQuotaManagedBucket(
    storage::MockQuotaManager* manager,
    const blink::StorageKey& storage_key,
    const std::string& bucket_name,
    base::Time modified = base::Time::Now()) {
  storage::BucketInfo bucket =
      manager->CreateBucket({storage_key, bucket_name});
  manager->AddBucket(bucket, {kClientFile}, modified);
  EXPECT_TRUE(manager->BucketHasData(bucket, kClientFile));
  return bucket;
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForever) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName);
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverNone) {
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverSpecificOrigin) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  storage::BucketInfo host1_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, storage::kDefaultBucketName);
  storage::BucketInfo host2_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, storage::kDefaultBucketName);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearQuotaDataForOrigin, partition,
                     kStorageKey1.origin().GetURL(), base::Time(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 1);
  EXPECT_FALSE(GetMockManager()->BucketHasData(host1_bucket, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_bucket, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastHour) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
  const blink::StorageKey kStorageKey3 =
      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");

  // Buckets modified now.
  base::Time now = base::Time::Now();
  storage::BucketInfo host1_bucket_now =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey1, "bucket_now", now);
  storage::BucketInfo host2_bucket_now =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey2, "bucket_now", now);

  // Buckets modified a day ago.
  base::Time yesterday = now - base::Days(1);
  storage::BucketInfo host1_bucket_yesterday = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, "bucket_yesterday", yesterday);
  storage::BucketInfo host2_bucket_yesterday = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, "bucket_yesterday", yesterday);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaDataForOrigin, partition, GURL(),
                                base::Time::Now() - base::Hours(1), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
  EXPECT_FALSE(GetMockManager()->BucketHasData(host1_bucket_now, kClientFile));
  EXPECT_FALSE(GetMockManager()->BucketHasData(host2_bucket_now, kClientFile));
  EXPECT_TRUE(
      GetMockManager()->BucketHasData(host1_bucket_yesterday, kClientFile));
  EXPECT_TRUE(
      GetMockManager()->BucketHasData(host2_bucket_yesterday, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastWeek) {
  const blink::StorageKey kStorageKey =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");

  // Buckets modified yesterday.
  base::Time now = base::Time::Now();
  base::Time yesterday = now - base::Days(1);
  storage::BucketInfo bucket_yesterday = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey, "bucket_yesterday", yesterday);

  // Buckets modified 10 days ago.
  base::Time ten_days_ago = now - base::Days(10);
  storage::BucketInfo bucket_ten_days_ago = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey, "bucket_ten_days_ago", ten_days_ago);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  base::RunLoop run_loop;
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaDataTime, partition,
                                base::Time::Now() - base::Days(7), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 1);
  EXPECT_FALSE(GetMockManager()->BucketHasData(bucket_yesterday, kClientFile));
  EXPECT_TRUE(
      GetMockManager()->BucketHasData(bucket_ten_days_ago, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedUnprotectedOrigins) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  storage::BucketInfo host1_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, storage::kDefaultBucketName);
  storage::BucketInfo host2_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, storage::kDefaultBucketName);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  // Protect kStorageKey1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kStorageKey1.origin().GetURL());

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                     base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
                     base::Time(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 1);
  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_bucket, kClientFile));
  EXPECT_FALSE(GetMockManager()->BucketHasData(host2_bucket, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedProtectedOrigins) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName);
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  // Protect kStorageKey1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kStorageKey1.origin().GetURL());

  // Try to remove kStorageKey1. Expect success.
  base::RunLoop run_loop;
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                     base::BindRepeating(
                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
                     base::Time(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedIgnoreDevTools) {
  const blink::StorageKey kStorageKey =
      blink::StorageKey::CreateFromStringForTesting(
          "devtools://abcdefghijklmnopqrstuvw/");

  storage::BucketInfo bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey, storage::kDefaultBucketName, base::Time());
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 1);

  base::RunLoop run_loop;
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                                base::BindRepeating(&DoesOriginMatchUnprotected,
                                                    kStorageKey.origin()),
                                base::Time(), &run_loop));
  run_loop.Run();

  // Check that devtools data isn't removed.
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 1);
  EXPECT_TRUE(GetMockManager()->BucketHasData(bucket, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveCookieForever) {
  const url::Origin kOrigin = url::Origin::Create(GURL("http://host1:1/"));

  StoragePartition* partition = browser_context()->GetDefaultStoragePartition();

  RemoveCookieTester tester(partition);
  tester.AddCookie(kOrigin);
  ASSERT_TRUE(tester.ContainsCookie(kOrigin));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearCookies, partition, base::Time(),
                                base::Time::Max(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}

TEST_F(StoragePartitionImplTest, RemoveCookieLastHour) {
  const url::Origin kOrigin = url::Origin::Create(GURL("http://host1:1/"));

  StoragePartition* partition = browser_context()->GetDefaultStoragePartition();

  RemoveCookieTester tester(partition);
  tester.AddCookie(kOrigin);
  ASSERT_TRUE(tester.ContainsCookie(kOrigin));

  base::Time an_hour_ago = base::Time::Now() - base::Hours(1);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearCookies, partition, an_hour_ago,
                                base::Time::Max(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}

TEST_F(StoragePartitionImplTest, RemoveCookieWithDeleteInfo) {
  const url::Origin kOrigin = url::Origin::Create(GURL("http://host1:1/"));

  StoragePartition* partition = browser_context()->GetDefaultStoragePartition();

  RemoveCookieTester tester(partition);
  tester.AddCookie(kOrigin);
  ASSERT_TRUE(tester.ContainsCookie(kOrigin));

  base::RunLoop run_loop2;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearCookiesMatchingInfo, partition,
                                CookieDeletionFilter::New(), &run_loop2));
  run_loop2.RunUntilIdle();
  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}

TEST_F(StoragePartitionImplTest, DeleteStaleSessionDataDeferred) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kDeferSessionStorageScavengingOnStartup);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  // Override the delay to 10 seconds.
  partition->OverrideDeleteStaleSessionCleanupDelayForTesting(
      base::Seconds(10));

  // Trigger stale session data deletion.
  partition->DeleteStaleSessionData();

  // Run any immediate tasks. Since scavenging is deferred, it shouldn't have
  // run.
  task_environment()->RunUntilIdle();
  EXPECT_FALSE(
      partition->GetDOMStorageContext()->scavenging_started_for_testing());

  // Fast forward by slightly less than the delay (9 seconds).
  task_environment()->FastForwardBy(base::Seconds(9));
  EXPECT_FALSE(
      partition->GetDOMStorageContext()->scavenging_started_for_testing());

  // Fast forward to complete the delay (total 10 seconds).
  task_environment()->FastForwardBy(base::Seconds(1));
  EXPECT_TRUE(
      partition->GetDOMStorageContext()->scavenging_started_for_testing());
}

TEST_F(StoragePartitionImplTest, DeleteStaleSessionDataImmediate) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kDeferSessionStorageScavengingOnStartup);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  // Override the delay to 10 seconds.
  partition->OverrideDeleteStaleSessionCleanupDelayForTesting(
      base::Seconds(10));

  // Trigger stale session data deletion.
  partition->DeleteStaleSessionData();

  // Run any immediate tasks. Since scavenging is NOT deferred, it should run
  // immediately.
  task_environment()->RunUntilIdle();
  EXPECT_TRUE(
      partition->GetDOMStorageContext()->scavenging_started_for_testing());
}

TEST_P(LocalStoragePartitionImplTest, RemoveUnprotectedLocalStorageForever) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  // Protect kOrigin1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kOrigin1.GetURL());

  RemoveLocalStorageTester tester(browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  tester.ClearLocalStorage(
      partition, base::Time(), base::Time::Max(),
      /*filter_builder=*/nullptr,
      base::BindRepeating(&DoesOriginMatchForUnprotectedWeb));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_P(LocalStoragePartitionImplTest, RemoveProtectedLocalStorageForever) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  // Protect kOrigin1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kOrigin1.GetURL());

  RemoveLocalStorageTester tester(browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  tester.ClearLocalStorage(
      partition, base::Time(), base::Time::Max(),
      /*filter_builder=*/nullptr,
      base::BindRepeating(&DoesOriginMatchForBothProtectedAndUnprotectedWeb));
  // Even if kOrigin1 is protected, it will be deleted since we specify
  // ClearData to delete protected data.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_P(LocalStoragePartitionImplTest, RemoveLocalStorageForLastWeek) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  RemoveLocalStorageTester tester(browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  base::Time a_week_ago = base::Time::Now() - base::Days(7);

  tester.ClearLocalStorage(
      partition, a_week_ago, base::Time::Max(),
      /*filter_builder=*/nullptr,
      base::BindRepeating(&DoesOriginMatchForBothProtectedAndUnprotectedWeb));
  // kOrigin1 and kOrigin2 do not have age more than a week.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_P(LocalStoragePartitionImplTest, RemoveLocalStorageForOrigins) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  RemoveLocalStorageTester tester(browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  auto filter_builder = BrowsingDataFilterBuilder::Create(
      BrowsingDataFilterBuilder::Mode::kDelete);
  filter_builder->AddOrigin(kOrigin1);
  filter_builder->AddOrigin(kOrigin2);

  tester.ClearLocalStorage(partition, base::Time::Min(), base::Time::Max(),
                           filter_builder.get(),
                           StoragePartition::StorageKeyPolicyMatcherFunction());
  // kOrigin3 is not filtered by the filter builder.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_P(LocalStoragePartitionImplTest, RemoveLocalStorageForOneOrigin) {
  const GURL kUrl1 = GURL("http://host1:1/");
  const url::Origin kOrigin1 = url::Origin::Create(kUrl1);
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  RemoveLocalStorageTester tester(browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  base::RunLoop run_loop;
  ClearDataForOrigin(StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
                     partition, kUrl1, &run_loop);
  run_loop.Run();
  tester.GetLocalStorageUsage();

  // kOrigin1 should be cleared.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

using StoragePartitionCodeCacheTest = RenderViewHostTestHarness;

TEST_F(StoragePartitionCodeCacheTest, ClearCodeCache) {
  const GURL kSiteUrl("http://host1:1/");
  const GURL kResourceURL("http://host4/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, data);
  EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
              testing::Optional(data));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache,
                     browser_context()->GetDefaultStoragePartition(),
                     base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
            std::nullopt);

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionCodeCacheTest, ClearCodeCacheSpecificURL) {
  const GURL kSiteUrl("http://host1:1/");
  const GURL kResourceURL("http://host4/script.js");
  const GURL kFilterResourceURLForCodeCache("http://host5/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, data);
  tester.AddEntry(RemoveCodeCacheTester::kJs, kFilterResourceURLForCodeCache,
                  data);
  EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
              testing::Optional(data));
  EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs,
                              kFilterResourceURLForCodeCache),
              testing::Optional(data));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearCodeCache, browser_context()->GetDefaultStoragePartition(),
          base::Time(), base::Time(),
          base::BindRepeating(&FilterURL, kFilterResourceURLForCodeCache),
          &run_loop));
  run_loop.Run();

  if (blink::features::IsPersistentCacheForCodeCacheEnabled()) {
    // TODO(crbug.com/456537775): PersistentCache unconditionally empties the
    // whole cache.
    EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
              std::nullopt);
  } else {
    EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
                testing::Optional(data));
  }
  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs,
                            kFilterResourceURLForCodeCache),
            std::nullopt);

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionCodeCacheTest, ClearCodeCacheDateRange) {
  const GURL kSiteUrl("http://host1:1/");
  const GURL kResourceURL("http://host4/script.js");
  const GURL kFilterResourceURLForCodeCache("http://host5/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  base::Time current_time = base::Time::NowFromSystemTime();
  base::Time out_of_range_time = current_time - base::Hours(3);
  base::Time begin_time = current_time - base::Hours(2);
  base::Time in_range_time = current_time - base::Hours(1);

  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, data);
  EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
              testing::Optional(data));
  tester.SetLastUseTime(RemoveCodeCacheTester::kJs, kResourceURL,
                        out_of_range_time);

  // Add a new entry.
  tester.AddEntry(RemoveCodeCacheTester::kJs, kFilterResourceURLForCodeCache,
                  data);
  EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs,
                              kFilterResourceURLForCodeCache),
              testing::Optional(data));
  tester.SetLastUseTime(RemoveCodeCacheTester::kJs,
                        kFilterResourceURLForCodeCache, in_range_time);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearCodeCache, browser_context()->GetDefaultStoragePartition(),
          begin_time, current_time,
          base::BindRepeating(&FilterURL, kFilterResourceURLForCodeCache),
          &run_loop));
  run_loop.Run();

  if (blink::features::IsPersistentCacheForCodeCacheEnabled()) {
    // TODO(crbug.com/456537775): PersistentCache unconditionally empties the
    // whole cache.
    EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
              std::nullopt);
  } else {
    EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
                testing::Optional(data));
  }
  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs,
                            kFilterResourceURLForCodeCache),
            std::nullopt);

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionCodeCacheTest, ClearWasmCodeCache) {
  const GURL kSiteUrl("http://host1:1/");
  const GURL kResourceURL("http://host4/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  std::string data("SomeData.wasm");
  tester.AddEntry(RemoveCodeCacheTester::kWebAssembly, kResourceURL, data);
  EXPECT_THAT(
      tester.GetEntry(RemoveCodeCacheTester::kWebAssembly, kResourceURL),
      testing::Optional(data));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache,
                     browser_context()->GetDefaultStoragePartition(),
                     base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kWebAssembly, kResourceURL),
            std::nullopt);

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

class StoragePartitionCodeCacheWithWebUiTest
    : public RenderViewHostTestHarness {
 protected:
  StoragePartitionCodeCacheWithWebUiTest() {
    // TODO(374930286): Remove the PersistentCache carve-out when all other
    // blockers for caching WebUI resources with PersistentCache are resolved.
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kWebUICodeCache},
        /*disabled_features=*/{
            blink::features::kUsePersistentCacheForCodeCache});
  }

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

TEST_F(StoragePartitionCodeCacheWithWebUiTest, ClearWebUICodeCache) {
  const GURL kSiteUrl("chrome://settings/");
  const GURL kResourceURL("chrome://host4/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, data);
  EXPECT_THAT(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
              testing::Optional(data));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache,
                     browser_context()->GetDefaultStoragePartition(),
                     base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
            std::nullopt);

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

class StoragePartitionCodeCacheWithoutWebUiTest
    : public RenderViewHostTestHarness {
 protected:
  StoragePartitionCodeCacheWithoutWebUiTest() {
    feature_list_.InitAndDisableFeature(features::kWebUICodeCache);
  }

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

TEST_F(StoragePartitionCodeCacheWithoutWebUiTest, WebUICodeCacheDisabled) {
  const GURL kSiteUrl("chrome://settings/");
  const GURL kResourceURL("chrome://host4/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, data);
  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
            std::nullopt);

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

class StoragePartitionCodeCacheOffTheRecordTest
    : public RenderViewHostTestHarness {
 protected:
  std::unique_ptr<BrowserContext> CreateBrowserContext() override {
    auto browser_context = std::make_unique<TestBrowserContext>();
    browser_context->set_is_off_the_record(true);
    return browser_context;
  }
};

TEST_F(StoragePartitionCodeCacheOffTheRecordTest, ClearCodeCache) {
  const GURL kSiteUrl("http://host1:1/");
  const GURL kResourceURL("http://host4/script.js");

  NavigateAndCommit(kSiteUrl);

  RemoveCodeCacheTester tester(*main_rfh());

  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, data);
  EXPECT_EQ(tester.GetEntry(RemoveCodeCacheTester::kJs, kResourceURL),
            std::nullopt);

  base::RunLoop run_loop;
  // This shouldn't crash.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache,
                     browser_context()->GetDefaultStoragePartition(),
                     base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();
}

TEST(StoragePartitionImplStaticTest, CreatePredicateForHostCookies) {
  GURL url("http://www.example.com/");
  GURL url2("https://www.example.com/");
  GURL url3("https://www.google.com/");

  std::optional<base::Time> server_time = std::nullopt;
  CookieDeletionFilterPtr deletion_filter = CookieDeletionFilter::New();
  deletion_filter->host_name = url.GetHost();

  base::Time now = base::Time::Now();
  std::vector<std::unique_ptr<CanonicalCookie>> valid_cookies;
  valid_cookies.push_back(CanonicalCookie::CreateForTesting(
      url, "A=B", now, net::CookieSourceType::kOther, server_time));
  valid_cookies.push_back(CanonicalCookie::CreateForTesting(
      url, "C=F", now, net::CookieSourceType::kOther, server_time));
  // We should match a different scheme with the same host.
  valid_cookies.push_back(CanonicalCookie::CreateForTesting(
      url2, "A=B", now, net::CookieSourceType::kOther, server_time));

  std::vector<std::unique_ptr<CanonicalCookie>> invalid_cookies;
  // We don't match domain cookies.
  invalid_cookies.push_back(CanonicalCookie::CreateForTesting(
      url2, "A=B;domain=.example.com", now, net::CookieSourceType::kOther,
      server_time));
  invalid_cookies.push_back(CanonicalCookie::CreateForTesting(
      url3, "A=B", now, net::CookieSourceType::kOther, server_time));

  for (const auto& cookie : valid_cookies) {
    EXPECT_TRUE(FilterMatchesCookie(deletion_filter, *cookie))
        << cookie->DebugString();
  }
  for (const auto& cookie : invalid_cookies) {
    EXPECT_FALSE(FilterMatchesCookie(deletion_filter, *cookie))
        << cookie->DebugString();
  }
}

TEST_F(StoragePartitionImplTest, DataRemovalObserver) {
  const uint32_t kTestClearMask =
      content::StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
  const auto kTestOrigin = GURL("https://example.com");
  const auto kBeginTime = base::Time() + base::Hours(1);
  const auto kEndTime = base::Time() + base::Hours(2);
  const auto storage_key_callback_valid =
      [&](content::StoragePartition::StorageKeyMatcherFunction callback) {
        return callback.Run(blink::StorageKey::CreateFirstParty(
            url::Origin::Create(kTestOrigin)));
      };

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  MockDataRemovalObserver observer(partition);

  // Confirm that each of the StoragePartition interfaces for clearing origin
  // based data notify observers appropriately.
  EXPECT_CALL(observer,
              OnStorageKeyDataCleared(
                  kTestClearMask, testing::Truly(storage_key_callback_valid),
                  base::Time(), base::Time::Max()));
  base::RunLoop run_loop;
  partition->ClearDataForOrigin(kTestClearMask, kTestOrigin,
                                run_loop.QuitClosure());
  run_loop.Run();
  testing::Mock::VerifyAndClearExpectations(&observer);

  EXPECT_CALL(observer,
              OnStorageKeyDataCleared(
                  kTestClearMask, testing::Truly(storage_key_callback_valid),
                  kBeginTime, kEndTime));
  partition->ClearData(
      kTestClearMask,
      blink::StorageKey::CreateFirstParty(url::Origin::Create(kTestOrigin)),
      kBeginTime, kEndTime, base::DoNothing());
  testing::Mock::VerifyAndClearExpectations(&observer);

  EXPECT_CALL(observer,
              OnStorageKeyDataCleared(
                  kTestClearMask, testing::Truly(storage_key_callback_valid),
                  kBeginTime, kEndTime));
  partition->ClearData(
      kTestClearMask,
      /*filter_builder=*/nullptr,
      base::BindLambdaForTesting([&](const blink::StorageKey& storage_key,
                                     storage::SpecialStoragePolicy* policy) {
        return storage_key == blink::StorageKey::CreateFirstParty(
                                  url::Origin::Create(kTestOrigin));
      }),
      /*cookie_deletion_filter=*/nullptr, /*perform_storage_cleanup=*/false,
      kBeginTime, kEndTime, base::DoNothing());
}

// https://crbug.com/1221382
// Make sure StorageServiceImpl can be stored in a SequenceLocalStorageSlot and
// that it can be safely destroyed when the thread terminates.
TEST(StorageServiceImplOnSequenceLocalStorage, ThreadDestructionDoesNotFail) {
  mojo::Remote<storage::mojom::StorageService> remote_service;
  mojo::Remote<storage::mojom::LocalStorageControl> storage_control;
  // These remotes must outlive the thread, otherwise PartitionImpl cleanup will
  // not happen in the ~StorageServiceImpl but on the mojo error handler.
  {
    // When this variable gets out of scope the IO thread will be destroyed
    // along with all objects stored in a SequenceLocalStorageSlot.
    content::BrowserTaskEnvironment task_environment(
        content::BrowserTaskEnvironment::REAL_IO_THREAD);

    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            [](mojo::PendingReceiver<storage::mojom::StorageService> receiver) {
              DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
              static base::SequenceLocalStorageSlot<
                  std::unique_ptr<storage::StorageServiceImpl>>
                  service_storage_slot;
              service_storage_slot.GetOrCreateValue() =
                  std::make_unique<storage::StorageServiceImpl>(
                      std::move(receiver),
                      /*io_task_runner=*/nullptr);
            },
            remote_service.BindNewPipeAndPassReceiver()));

    // Make sure PartitionImpl gets to destroy a LocalStorageImpl object.
    base::ScopedTempDir temp_dir;
    CHECK(temp_dir.CreateUniqueTempDir());
    remote_service->BindLocalStorageControl(
        temp_dir.GetPath(), storage_control.BindNewPipeAndPassReceiver());
    storage_control.FlushForTesting();
  }
}

#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
TEST_F(StoragePartitionImplTest, RemoveDeviceBoundSessions) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  auto device_bound_session_manager =
      std::make_unique<network::MockDeviceBoundSessionManager>();
  network::MockDeviceBoundSessionManager* device_bound_session_manager_raw =
      device_bound_session_manager.get();
  partition->OverrideDeviceBoundSessionManagerForTesting(
      std::move(device_bound_session_manager));

  base::Time created_before_time = base::Time::Now() - base::Days(1);
  base::Time created_after_time = base::Time::Now() - base::Days(3);

  EXPECT_CALL(
      *device_bound_session_manager_raw,
      DeleteAllSessions(Eq(net::device_bound_sessions::DeletionReason::
                               kStoragePartitionCleared),
                        Eq(created_after_time), Eq(created_before_time), _, _))
      .WillOnce(base::test::RunOnceClosure<4>());

  base::RunLoop run_loop;
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_DEVICE_BOUND_SESSIONS,
                       blink::StorageKey(), created_after_time,
                       created_before_time, run_loop.QuitClosure());
  run_loop.Run();
}
#endif

// Local network access tests require there to be a (minimal) frame setup.
using StoragePartitionImplLocalNetworkAccessTest = RenderViewHostTestHarness;

// Mock ContentBrowserClient to test Android OS-level platform local network
// permission requests delegated from StoragePartitionImpl.
class PlatformPermissionTestContentBrowserClient : public ContentBrowserClient {
 public:
  void RequestPlatformLocalNetworkPermission(
      WebContents& web_contents,
      base::OnceCallback<void(bool)> callback) override {
    requested_web_contents_ = &web_contents;
    std::move(callback).Run(permission_to_return_);
  }

  void set_permission_to_return(bool permission) {
    permission_to_return_ = permission;
  }
  WebContents* requested_web_contents() const {
    return requested_web_contents_;
  }

 private:
  raw_ptr<WebContents> requested_web_contents_ = nullptr;
  bool permission_to_return_ = true;
};

// Tests triggering the Local Network Access permission check for a subresource
// request.
TEST_F(StoragePartitionImplLocalNetworkAccessTest,
       LocalNetworkAccessPermission_SubresourceContext) {
  base::test::ScopedFeatureList features(
      network::features::kLocalNetworkAccessChecks);
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateURLLoaderNetworkObserverForFrame(GlobalRenderFrameHostId(
          process()->GetID(), main_rfh()->GetRoutingID())));

  base::test::TestFuture<network::mojom::LocalNetworkAccessResult> lna_result;
  observer->OnLocalNetworkAccessPermissionRequired(
      network::mojom::TransportType::kDirect,
      network::mojom::IPAddressSpace::kLocal,
      base::BindOnce(lna_result.GetCallback()));
  EXPECT_EQ(network::mojom::LocalNetworkAccessResult::kDenied,
            lna_result.Get());
}

// Tests triggering the Local Network Access permission check for a subframe
// navigation context.
TEST_F(StoragePartitionImplLocalNetworkAccessTest,
       LocalNetworkAccessPermission_SubframeNavigationContext) {
  base::test::ScopedFeatureList features(
      network::features::kLocalNetworkAccessChecks);
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  // Set up a frame tree with a subframe, start a navigation in the subframe,
  // and get the NavigationRequest for that navigation.
  NavigateAndCommit(GURL("https://foo.com"));
  RenderFrameHost* sub_frame =
      RenderFrameHostTester::For(main_rfh())->AppendChild(std::string("child"));
  std::unique_ptr<NavigationSimulator> simulator =
      NavigationSimulator::CreateRendererInitiated(GURL("http://test.local"),
                                                   sub_frame);
  simulator->Start();
  NavigationRequest* request =
      NavigationRequest::From(simulator->GetNavigationHandle());

  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateURLLoaderNetworkObserverForNavigationRequest(*request));

  base::test::TestFuture<network::mojom::LocalNetworkAccessResult> lna_result;
  observer->OnLocalNetworkAccessPermissionRequired(
      network::mojom::TransportType::kDirect,
      network::mojom::IPAddressSpace::kLocal,
      base::BindOnce(lna_result.GetCallback()));
  EXPECT_EQ(network::mojom::LocalNetworkAccessResult::kDenied,
            lna_result.Get());
}

// Tests triggering the Local Network Access permission check for a worker
// request.
TEST_F(StoragePartitionImplLocalNetworkAccessTest,
       LocalNetworkAccessPermission_WorkerContext) {
  base::test::ScopedFeatureList features(
      network::features::kLocalNetworkAccessChecks);
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  const url::Origin worker_origin =
      url::Origin::Create(GURL("https://foo.com"));

  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateURLLoaderNetworkObserverForServiceOrSharedWorker(
          network::OriginatingProcessId::renderer(
              network::RendererProcessId(1)),
          worker_origin));

  base::test::TestFuture<network::mojom::LocalNetworkAccessResult> lna_result;
  observer->OnLocalNetworkAccessPermissionRequired(
      network::mojom::TransportType::kDirect,
      network::mojom::IPAddressSpace::kLocal,
      base::BindOnce(lna_result.GetCallback()));
  EXPECT_EQ(network::mojom::LocalNetworkAccessResult::kDenied,
            lna_result.Get());
}

// Tests that OnPlatformLocalNetworkPermissionRequired correctly resolves the
// RenderFrameHost to WebContents and invokes ContentBrowserClient when the OS
// permission is granted.
TEST_F(StoragePartitionImplLocalNetworkAccessTest,
       PlatformLocalNetworkPermission_Granted) {
  PlatformPermissionTestContentBrowserClient test_client;
  test_client.set_permission_to_return(true);
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateURLLoaderNetworkObserverForFrame(GlobalRenderFrameHostId(
          process()->GetID(), main_rfh()->GetRoutingID())));

  base::test::TestFuture<bool> result;
  observer->OnPlatformLocalNetworkPermissionRequired(
      base::BindOnce(result.GetCallback()));
  EXPECT_TRUE(result.Get());
  EXPECT_EQ(web_contents(), test_client.requested_web_contents());

  SetBrowserClientForTesting(old_client);
}

// Tests that OnPlatformLocalNetworkPermissionRequired returns false when the OS
// platform local network permission is denied by the user.
TEST_F(StoragePartitionImplLocalNetworkAccessTest,
       PlatformLocalNetworkPermission_Denied) {
  PlatformPermissionTestContentBrowserClient test_client;
  test_client.set_permission_to_return(false);
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateURLLoaderNetworkObserverForFrame(GlobalRenderFrameHostId(
          process()->GetID(), main_rfh()->GetRoutingID())));

  base::test::TestFuture<bool> result;
  observer->OnPlatformLocalNetworkPermissionRequired(
      base::BindOnce(result.GetCallback()));
  EXPECT_FALSE(result.Get());
  EXPECT_EQ(web_contents(), test_client.requested_web_contents());

  SetBrowserClientForTesting(old_client);
}

// Tests that OnPlatformLocalNetworkPermissionRequired returns false when the
// request originates from a worker context (null WebContents).
TEST_F(StoragePartitionImplLocalNetworkAccessTest,
       PlatformLocalNetworkPermission_WorkerContext) {
  // Install a mock ContentBrowserClient to capture
  // RequestPlatformLocalNetworkPermission calls.
  PlatformPermissionTestContentBrowserClient test_client;
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  const url::Origin worker_origin =
      url::Origin::Create(GURL("https://foo.com"));

  // Create an observer bound to a ServiceWorker / SharedWorker context.
  // Background workers do not have an associated WebContents.
  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateURLLoaderNetworkObserverForServiceOrSharedWorker(
          network::OriginatingProcessId::renderer(
              network::RendererProcessId(1)),
          worker_origin));

  base::test::TestFuture<bool> result;
  observer->OnPlatformLocalNetworkPermissionRequired(
      base::BindOnce(result.GetCallback()));

  // Since worker contexts have no associated WebContents,
  // OnPlatformLocalNetworkPermissionRequired must return false early without
  // attempting to prompt for OS permission.
  EXPECT_FALSE(result.Get());
  EXPECT_EQ(nullptr, test_client.requested_web_contents());

  SetBrowserClientForTesting(old_client);
}

TEST_F(StoragePartitionImplTest, ClearDataStorageKeyDeletesPartitionedCookies) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  RemoveCookieTester tester(partition);

  const auto kOrigin = url::Origin::Create(GURL("https://example.com"));
  const auto kPartitionKey =
      net::CookiePartitionKey::FromURLForTesting(GURL("https://a.com"));
  const auto kOtherPartitionKey =
      net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"));

  // Unpartitioned cookie.
  tester.AddCookie(kOrigin);
  // Partitioned cookie with two keys.
  tester.AddCookie(kOrigin, kPartitionKey);
  tester.AddCookie(kOrigin, kOtherPartitionKey);

  ASSERT_TRUE(tester.ContainsCookie(kOrigin));
  ASSERT_TRUE(tester.ContainsCookie(kOrigin, kPartitionKey));
  ASSERT_TRUE(tester.ContainsCookie(kOrigin, kOtherPartitionKey));

  blink::StorageKey storage_key = blink::StorageKey::Create(
      kOrigin, net::SchemefulSite(GURL("https://a.com")),
      blink::mojom::AncestorChainBit::kCrossSite);
  ASSERT_EQ(storage_key.ToCookiePartitionKey(), kPartitionKey);

  base::RunLoop run_loop;
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES, storage_key,
                       base::Time(), base::Time::Max(), run_loop.QuitClosure());
  run_loop.Run();

  // Should delete unpartitioned cookies and those in matching partition.
  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
  EXPECT_FALSE(tester.ContainsCookie(kOrigin, kPartitionKey));
  // Should not delete cookies in other partitions.
  EXPECT_TRUE(tester.ContainsCookie(kOrigin, kOtherPartitionKey));
}


class MockGpuDiskCacheFactory : public gpu::GpuDiskCacheFactory {
 public:
  MockGpuDiskCacheFactory() = default;
  ~MockGpuDiskCacheFactory() override = default;

  MOCK_METHOD(void,
              ClearByPath,
              (const base::FilePath&, base::Time, base::Time, base::OnceClosure),
              (override));
};

class StoragePartitionImplShaderCacheTest : public StoragePartitionImplTest {
 public:
  StoragePartitionImplShaderCacheTest() {
    InitGpuDiskCacheFactorySingleton();
    SetGpuDiskCacheFactorySingletonForTesting(&mock_gpu_disk_cache_factory_);
  }

  ~StoragePartitionImplShaderCacheTest() override {
    SetGpuDiskCacheFactorySingletonForTesting(nullptr);
    DestroyGpuDiskCacheFactorySingletonForTesting();
  }

 protected:
  StoragePartition* storage_partition() {
    return browser_context()->GetDefaultStoragePartition();
  }

  base::test::ScopedFeatureList feature_list_;
  MockGpuDiskCacheFactory mock_gpu_disk_cache_factory_;
};

TEST_F(StoragePartitionImplShaderCacheTest,
       ClearData_PartialCleanupDisabled_NoStorageCleanup) {
  feature_list_.InitAndEnableFeature(
      features::kDisablePartialStorageCleanupForGPUDiskCache);

  EXPECT_CALL(mock_gpu_disk_cache_factory_, ClearByPath(_, _, _, _)).Times(0);

  base::RunLoop run_loop;
  storage_partition()->ClearData(
      StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
      /*filter_builder=*/nullptr,
      /*storage_key_policy_matcher=*/{},
      /*cookie_deletion_filter=*/nullptr,
      /*perform_storage_cleanup=*/false, base::Time(), base::Time::Max(),
      run_loop.QuitClosure());
  run_loop.Run();
}

TEST_F(StoragePartitionImplShaderCacheTest,
       ClearData_PartialCleanupEnabled_WithStorageCleanup) {
  feature_list_.InitAndDisableFeature(
      features::kDisablePartialStorageCleanupForGPUDiskCache);

  EXPECT_CALL(mock_gpu_disk_cache_factory_, ClearByPath(_, _, _, _))
      .Times(gpu::kGpuDiskCacheTypes.size())
      .WillRepeatedly(
          [](const base::FilePath&, base::Time, base::Time,
             base::OnceClosure callback) { std::move(callback).Run(); });

  base::RunLoop run_loop;
  storage_partition()->ClearData(
      StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
      /*filter_builder=*/nullptr,
      /*storage_key_policy_matcher=*/{},
      /*cookie_deletion_filter=*/nullptr,
      /*perform_storage_cleanup=*/true, base::Time(), base::Time::Max(),
      run_loop.QuitClosure());
  run_loop.Run();
}

TEST_F(StoragePartitionImplShaderCacheTest,
       ClearData_PartialCleanupDisabled_WithStorageCleanup) {
  feature_list_.InitAndEnableFeature(
      features::kDisablePartialStorageCleanupForGPUDiskCache);

  EXPECT_CALL(mock_gpu_disk_cache_factory_, ClearByPath(_, _, _, _))
      .Times(gpu::kGpuDiskCacheTypes.size())
      .WillRepeatedly(
          [](const base::FilePath&, base::Time, base::Time,
             base::OnceClosure callback) { std::move(callback).Run(); });

  base::RunLoop run_loop;
  storage_partition()->ClearData(
      StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
      /*filter_builder=*/nullptr,
      /*storage_key_policy_matcher=*/{},
      /*cookie_deletion_filter=*/nullptr,
      /*perform_storage_cleanup=*/true, base::Time(), base::Time::Max(),
      run_loop.QuitClosure());
  run_loop.Run();
}

TEST_F(StoragePartitionImplTest, GetPartitionUuidForOrigin) {
  const auto kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const auto kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  StoragePartitionImpl* partition1 = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  const base::UnguessableToken& uuid1 =
      partition1->GetPartitionUUIDPerStorageKey(kStorageKey1);
  EXPECT_TRUE(!uuid1.is_empty());

  // Check that UUID is consistent across the same partition.
  const base::UnguessableToken& uuid2 =
      partition1->GetPartitionUUIDPerStorageKey(kStorageKey1);
  EXPECT_TRUE(!uuid2.is_empty());
  EXPECT_EQ(uuid1, uuid2);

  // Check that UUID is different per-origin.
  const base::UnguessableToken& uuid3 =
      partition1->GetPartitionUUIDPerStorageKey(kStorageKey2);
  EXPECT_TRUE(!uuid3.is_empty());
  EXPECT_NE(uuid2, uuid3);

  // Check that different partitions have different UUIDs.
  StoragePartitionConfig config = StoragePartitionConfig::Create(
      browser_context(), "in_memory", "TestInMemory", /*in_memory=*/true);
  StoragePartitionImpl* partition2 = static_cast<StoragePartitionImpl*>(
      browser_context()->GetStoragePartition(config, /*can_create=*/true));
  const base::UnguessableToken& uuid4 =
      partition2->GetPartitionUUIDPerStorageKey(kStorageKey1);
  EXPECT_TRUE(!uuid4.is_empty());
  EXPECT_NE(uuid1, uuid4);
}

TEST_F(StoragePartitionImplTest, RemoveDeclarativePerformanceObserverData) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  DeclarativePerformanceObserverStore* store =
      partition->GetDeclarativePerformanceObserverStore();
  ASSERT_TRUE(store);

  const url::Origin kOrigin = url::Origin::Create(GURL("https://example.com"));
  const url::Origin kOtherOrigin =
      url::Origin::Create(GURL("https://example.net"));

  // Populate data in the store:
  {
    base::RunLoop run_loop;
    store->SetEarlyFailurePolicy(kOrigin, true, run_loop.QuitClosure());
    run_loop.Run();
  }
  {
    base::RunLoop run_loop;
    store->SetEarlyFailurePolicy(kOtherOrigin, true, run_loop.QuitClosure());
    run_loop.Run();
  }

  base::DictValue sample;
  sample.Set("entryType", "navigation");
  {
    base::RunLoop run_loop;
    store->StoreEarlyFailureReport(kOrigin, sample.Clone(),
                                   run_loop.QuitClosure());
    run_loop.Run();
  }
  {
    base::RunLoop run_loop;
    store->StoreEarlyFailureReport(kOtherOrigin, sample.Clone(),
                                   run_loop.QuitClosure());
    run_loop.Run();
  }

  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOrigin));
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOtherOrigin));

  // 1. Clear data for a specific origin with the correct mask:
  {
    base::RunLoop run_loop;
    partition->ClearData(
        StoragePartition::REMOVE_DATA_MASK_DECLARATIVE_PERFORMANCE_OBSERVER,
        blink::StorageKey::CreateFirstParty(kOrigin), base::Time(),
        base::Time::Max(), run_loop.QuitClosure());
    run_loop.Run();
  }

  // kOrigin data should be cleared:
  EXPECT_FALSE(store->HasEarlyFailurePolicy(kOrigin));
  {
    base::ListValue reports;
    base::RunLoop run_loop;
    store->TakeEarlyFailureReports(
        kOrigin, base::BindOnce(
                     [](base::ListValue* out, base::OnceClosure quit,
                        base::ListValue res) {
                       *out = std::move(res);
                       std::move(quit).Run();
                     },
                     &reports, run_loop.QuitClosure()));
    run_loop.Run();
    EXPECT_TRUE(reports.empty());
  }

  // kOtherOrigin data should still be present:
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOtherOrigin));
  {
    base::ListValue reports;
    base::RunLoop run_loop;
    store->TakeEarlyFailureReports(
        kOtherOrigin, base::BindOnce(
                          [](base::ListValue* out, base::OnceClosure quit,
                             base::ListValue res) {
                            *out = std::move(res);
                            std::move(quit).Run();
                          },
                          &reports, run_loop.QuitClosure()));
    run_loop.Run();
    EXPECT_EQ(reports.size(), 1u);
  }

  // 2. Clear data with a wrong mask (should not delete anything):
  {
    // Re-populate kOrigin:
    base::RunLoop run_loop;
    store->SetEarlyFailurePolicy(kOrigin, true, run_loop.QuitClosure());
    run_loop.Run();
  }
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOrigin));

  {
    base::RunLoop run_loop;
    partition->ClearData(
        StoragePartition::REMOVE_DATA_MASK_COOKIES,  // Wrong mask
        blink::StorageKey::CreateFirstParty(kOrigin), base::Time(),
        base::Time::Max(), run_loop.QuitClosure());
    run_loop.Run();
  }

  // Data should still be present because of wrong mask:
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOrigin));

  // 3. Clear data with a filter (should selectively clear matching origin):
  {
    // Re-populate both:
    base::RunLoop run_loop1;
    store->SetEarlyFailurePolicy(kOrigin, true, run_loop1.QuitClosure());
    run_loop1.Run();

    base::RunLoop run_loop2;
    store->SetEarlyFailurePolicy(kOtherOrigin, true, run_loop2.QuitClosure());
    run_loop2.Run();
  }
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOrigin));
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOtherOrigin));

  {
    std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
        BrowsingDataFilterBuilder::Create(
            BrowsingDataFilterBuilder::Mode::kDelete);
    filter_builder->AddOrigin(kOrigin);

    base::RunLoop run_loop;
    partition->ClearData(
        StoragePartition::REMOVE_DATA_MASK_DECLARATIVE_PERFORMANCE_OBSERVER,
        filter_builder.get(),
        StoragePartition::StorageKeyPolicyMatcherFunction(),
        network::mojom::CookieDeletionFilter::New(),
        /*perform_storage_cleanup=*/false, base::Time(), base::Time::Max(),
        run_loop.QuitClosure());
    run_loop.Run();
  }

  // kOrigin should be cleared, but kOtherOrigin should remain:
  EXPECT_FALSE(store->HasEarlyFailurePolicy(kOrigin));
  EXPECT_TRUE(store->HasEarlyFailurePolicy(kOtherOrigin));

  // 4. Clear all data (empty/opaque storage key) with the correct mask:
  {
    base::RunLoop run_loop;
    partition->ClearData(
        StoragePartition::REMOVE_DATA_MASK_DECLARATIVE_PERFORMANCE_OBSERVER,
        blink::StorageKey(), base::Time(), base::Time::Max(),
        run_loop.QuitClosure());
    run_loop.Run();
  }

  // All data should be cleared:
  EXPECT_FALSE(store->HasEarlyFailurePolicy(kOrigin));
  EXPECT_FALSE(store->HasEarlyFailurePolicy(kOtherOrigin));
}

}  // namespace content
