blob: 860a0f12cd20d29dc8572c94f00477a11347ce00 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// 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 <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/sequence_local_storage_slot.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/services/storage/dom_storage/async_dom_storage_database.h"
#include "components/services/storage/dom_storage/dom_storage_database.h"
#include "components/services/storage/dom_storage/local_storage_database.pb.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/partition.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/shared_storage/async_shared_storage_database_impl.h"
#include "components/services/storage/shared_storage/shared_storage_manager.h"
#include "components/services/storage/shared_storage/shared_storage_options.h"
#include "components/services/storage/storage_service_impl.h"
#include "content/browser/aggregation_service/aggregation_service_impl.h"
#include "content/browser/attribution_reporting/attribution_manager_impl.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/browser/attribution_reporting/attribution_trigger.h"
#include "content/browser/code_cache/generated_code_cache.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/gpu/shader_cache_factory.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/generated_code_cache_settings.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/trust_tokens.mojom.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "net/base/network_isolation_key.h"
#include "net/base/test_completion_callback.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "ppapi/buildflags/buildflags.h"
#include "services/network/cookie_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/interest_group/interest_group.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "url/gurl.h"
#include "url/origin.h"
#if BUILDFLAG(ENABLE_PLUGINS)
#include "storage/browser/file_system/async_file_util.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_operation_context.h"
#include "storage/browser/file_system/isolated_context.h"
#include "storage/common/file_system/file_system_util.h"
#include "url/origin.h"
#endif // BUILDFLAG(ENABLE_PLUGINS)
#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;
namespace content {
namespace {
const int kDefaultClientId = 42;
const char kCacheKey[] = "key";
const char kCacheValue[] = "cached value";
#if BUILDFLAG(ENABLE_PLUGINS)
const char kWidevineCdmPluginId[] = "application_x-ppapi-widevine-cdm";
const char kClearKeyCdmPluginId[] = "application_x-ppapi-clearkey-cdm";
#endif // BUILDFLAG(ENABLE_PLUGINS)
const blink::mojom::StorageType kTemporary =
blink::mojom::StorageType::kTemporary;
const blink::mojom::StorageType kPersistent =
blink::mojom::StorageType::kPersistent;
const storage::QuotaClientType kClientFile =
storage::QuotaClientType::kFileSystem;
const uint32_t kAllQuotaRemoveMask =
StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS |
StoragePartition::REMOVE_DATA_MASK_INDEXEDDB |
StoragePartition::REMOVE_DATA_MASK_WEBSQL;
class AwaitCompletionHelper {
public:
AwaitCompletionHelper() : start_(false), already_quit_(false) {}
AwaitCompletionHelper(const AwaitCompletionHelper&) = delete;
AwaitCompletionHelper& operator=(const AwaitCompletionHelper&) = delete;
virtual ~AwaitCompletionHelper() = default;
void BlockUntilNotified() {
if (!already_quit_) {
DCHECK(!start_);
start_ = true;
base::RunLoop().Run();
} else {
DCHECK(!start_);
already_quit_ = false;
}
}
void Notify() {
if (start_) {
DCHECK(!already_quit_);
base::RunLoop::QuitCurrentWhenIdleDeprecated();
start_ = false;
} else {
DCHECK(!already_quit_);
already_quit_ = true;
}
}
private:
// Helps prevent from running message_loop, if the callback invoked
// immediately.
bool start_;
bool already_quit_;
};
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) {
get_cookie_success_ = false;
storage_partition_->GetCookieManagerForBrowserProcess()->GetCookieList(
origin.GetURL(), net::CookieOptions::MakeAllInclusive(),
net::CookiePartitionKeyCollection(),
base::BindOnce(&RemoveCookieTester::GetCookieListCallback,
base::Unretained(this)));
await_completion_.BlockUntilNotified();
return get_cookie_success_;
}
void AddCookie(const url::Origin& origin) {
net::CookieInclusionStatus status;
std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create(
origin.GetURL(), "A=1", base::Time::Now(),
absl::nullopt /* server_time */,
absl::nullopt /* cookie_partition_key */, &status));
storage_partition_->GetCookieManagerForBrowserProcess()->SetCanonicalCookie(
*cc, origin.GetURL(), net::CookieOptions::MakeAllInclusive(),
base::BindOnce(&RemoveCookieTester::SetCookieCallback,
base::Unretained(this)));
await_completion_.BlockUntilNotified();
}
private:
void GetCookieListCallback(
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 {
EXPECT_EQ("", cookie_line);
get_cookie_success_ = false;
}
await_completion_.Notify();
}
void SetCookieCallback(net::CookieAccessResult result) {
ASSERT_TRUE(result.status.IsInclude());
await_completion_.Notify();
}
bool get_cookie_success_;
AwaitCompletionHelper await_completion_;
raw_ptr<StoragePartition> storage_partition_;
};
class RemoveInterestGroupTester {
public:
explicit RemoveInterestGroupTester(StoragePartitionImpl* storage_partition)
: storage_partition_(storage_partition) {}
RemoveInterestGroupTester(const RemoveInterestGroupTester&) = delete;
RemoveInterestGroupTester& operator=(const RemoveInterestGroupTester&) =
delete;
// Returns true, if the given interest group owner has any interest groups in
// InterestGroupStorage.
bool ContainsInterestGroupOwner(const url::Origin& origin) {
get_interest_group_success_ = false;
EXPECT_TRUE(storage_partition_->GetInterestGroupManager());
static_cast<InterestGroupManagerImpl*>(
storage_partition_->GetInterestGroupManager())
->GetInterestGroupsForOwner(
origin, base::BindOnce(
&RemoveInterestGroupTester::GetInterestGroupsCallback,
base::Unretained(this)));
await_completion_.BlockUntilNotified();
return get_interest_group_success_;
}
void AddInterestGroup(const url::Origin& origin) {
EXPECT_TRUE(storage_partition_->GetInterestGroupManager());
blink::InterestGroup group;
group.owner = origin;
group.name = "Name";
group.expiry = base::Time::Now() + base::Days(30);
static_cast<InterestGroupManagerImpl*>(
storage_partition_->GetInterestGroupManager())
->JoinInterestGroup(group, origin.GetURL());
}
private:
void GetInterestGroupsCallback(std::vector<StorageInterestGroup> groups) {
get_interest_group_success_ = groups.size() > 0;
await_completion_.Notify();
}
bool get_interest_group_success_ = false;
AwaitCompletionHelper await_completion_;
raw_ptr<StoragePartitionImpl> storage_partition_;
};
class RemoveLocalStorageTester {
public:
RemoveLocalStorageTester(content::BrowserTaskEnvironment* task_environment,
TestBrowserContext* browser_context)
: task_environment_(task_environment),
storage_partition_(browser_context->GetDefaultStoragePartition()),
dom_storage_context_(storage_partition_->GetDOMStorageContext()) {}
RemoveLocalStorageTester(const RemoveLocalStorageTester&) = delete;
RemoveLocalStorageTester& operator=(const RemoveLocalStorageTester&) = delete;
~RemoveLocalStorageTester() {
// Tests which bring up a real Local Storage context need to shut it down
// and wait for the database to be closed before terminating; otherwise the
// TestBrowserContext may fail to delete its temp dir, and it will not be
// happy about that.
static_cast<DOMStorageContextWrapper*>(dom_storage_context_)->Shutdown();
task_environment_->RunUntilIdle();
}
// Returns true, if the given origin URL exists.
bool DOMStorageExistsForOrigin(const url::Origin& origin) {
GetLocalStorageUsage();
await_completion_.BlockUntilNotified();
for (size_t i = 0; i < infos_.size(); ++i) {
if (origin == infos_[i].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.
base::RunLoop open_loop;
leveldb_env::Options options;
options.create_if_missing = true;
auto database = storage::AsyncDomStorageDatabase::OpenDirectory(
std::move(options),
storage_partition_->GetPath().Append(storage::kLocalStoragePath),
storage::kLocalStorageLeveldbName, absl::nullopt,
base::ThreadTaskRunnerHandle::Get(),
base::BindLambdaForTesting([&](leveldb::Status status) {
ASSERT_TRUE(status.ok());
open_loop.Quit();
}));
open_loop.Run();
base::RunLoop populate_loop;
database->database().PostTaskWithThisObject(
base::BindLambdaForTesting([&](const storage::DomStorageDatabase& db) {
PopulateDatabase(db, origin1, origin2, origin3);
populate_loop.Quit();
}));
populate_loop.Run();
// Ensure that this database is fully closed before returning.
database.reset();
task_environment_->RunUntilIdle();
EXPECT_TRUE(DOMStorageExistsForOrigin(origin1));
EXPECT_TRUE(DOMStorageExistsForOrigin(origin2));
EXPECT_TRUE(DOMStorageExistsForOrigin(origin3));
}
static void PopulateDatabase(const storage::DomStorageDatabase& db,
const url::Origin& origin1,
const url::Origin& origin2,
const url::Origin& origin3) {
storage::LocalStorageStorageKeyMetaData data;
std::map<std::vector<uint8_t>, std::vector<uint8_t>> entries;
base::Time now = base::Time::Now();
data.set_last_modified(now.ToInternalValue());
data.set_size_bytes(16);
ASSERT_TRUE(
db.Put(CreateMetaDataKey(origin1),
base::as_bytes(base::make_span(data.SerializeAsString())))
.ok());
ASSERT_TRUE(db.Put(CreateDataKey(origin1), {}).ok());
base::Time one_day_ago = now - base::Days(1);
data.set_last_modified(one_day_ago.ToInternalValue());
ASSERT_TRUE(
db.Put(CreateMetaDataKey(origin2),
base::as_bytes(base::make_span((data.SerializeAsString()))))
.ok());
ASSERT_TRUE(db.Put(CreateDataKey(origin2), {}).ok());
base::Time sixty_days_ago = now - base::Days(60);
data.set_last_modified(sixty_days_ago.ToInternalValue());
ASSERT_TRUE(
db.Put(CreateMetaDataKey(origin3),
base::as_bytes(base::make_span(data.SerializeAsString())))
.ok());
ASSERT_TRUE(db.Put(CreateDataKey(origin3), {}).ok());
}
private:
static std::vector<uint8_t> CreateDataKey(const url::Origin& origin) {
auto origin_str = origin.Serialize();
std::vector<uint8_t> serialized_origin(origin_str.begin(),
origin_str.end());
std::vector<uint8_t> key = {'_'};
key.insert(key.end(), serialized_origin.begin(), serialized_origin.end());
key.push_back(0);
key.push_back('X');
return key;
}
static std::vector<uint8_t> CreateMetaDataKey(const url::Origin& origin) {
const uint8_t kMetaPrefix[] = {'M', 'E', 'T', 'A', ':'};
auto origin_str = origin.Serialize();
std::vector<uint8_t> serialized_origin(origin_str.begin(),
origin_str.end());
std::vector<uint8_t> key;
key.reserve(std::size(kMetaPrefix) + serialized_origin.size());
key.insert(key.end(), kMetaPrefix, kMetaPrefix + std::size(kMetaPrefix));
key.insert(key.end(), serialized_origin.begin(), serialized_origin.end());
return key;
}
void GetLocalStorageUsage() {
dom_storage_context_->GetLocalStorageUsage(
base::BindOnce(&RemoveLocalStorageTester::OnGotLocalStorageUsage,
base::Unretained(this)));
}
void OnGotLocalStorageUsage(
const std::vector<content::StorageUsageInfo>& infos) {
infos_ = infos;
await_completion_.Notify();
}
// We don't own these pointers.
const raw_ptr<BrowserTaskEnvironment> task_environment_;
const raw_ptr<StoragePartition> storage_partition_;
raw_ptr<DOMStorageContext> dom_storage_context_;
std::vector<content::StorageUsageInfo> infos_;
AwaitCompletionHelper await_completion_;
};
class RemoveCodeCacheTester {
public:
explicit RemoveCodeCacheTester(GeneratedCodeCacheContext* code_cache_context)
: code_cache_context_(code_cache_context) {}
RemoveCodeCacheTester(const RemoveCodeCacheTester&) = delete;
RemoveCodeCacheTester& operator=(const RemoveCodeCacheTester&) = delete;
enum Cache { kJs, kWebAssembly, kWebUiJs };
bool ContainsEntry(Cache cache, const GURL& url, const GURL& origin_lock) {
entry_exists_ = false;
base::RunLoop loop;
GeneratedCodeCacheContext::RunOrPostTask(
code_cache_context_.get(), FROM_HERE,
base::BindOnce(&RemoveCodeCacheTester::ContainsEntryOnThread,
base::Unretained(this), cache, url, origin_lock,
loop.QuitClosure()));
loop.Run();
return entry_exists_;
}
void ContainsEntryOnThread(Cache cache,
const GURL& url,
const GURL& origin_lock,
base::OnceClosure quit) {
GeneratedCodeCache::ReadDataCallback callback =
base::BindOnce(&RemoveCodeCacheTester::FetchEntryCallback,
base::Unretained(this), std::move(quit));
GetCache(cache)->FetchEntry(url, origin_lock, net::NetworkIsolationKey(),
std::move(callback));
}
void AddEntry(Cache cache,
const GURL& url,
const GURL& origin_lock,
const std::string& data) {
base::RunLoop loop;
GeneratedCodeCacheContext::RunOrPostTask(
code_cache_context_.get(), FROM_HERE,
base::BindOnce(&RemoveCodeCacheTester::AddEntryOnThread,
base::Unretained(this), cache, url, origin_lock, data,
loop.QuitClosure()));
loop.Run();
}
void AddEntryOnThread(Cache cache,
const GURL& url,
const GURL& origin_lock,
const std::string& data,
base::OnceClosure quit) {
std::vector<uint8_t> data_vector(data.begin(), data.end());
GetCache(cache)->WriteEntry(url, origin_lock, net::NetworkIsolationKey(),
base::Time::Now(), data_vector);
std::move(quit).Run();
}
void SetLastUseTime(Cache cache,
const GURL& url,
const GURL& origin_lock,
base::Time time) {
base::RunLoop loop;
GeneratedCodeCacheContext::RunOrPostTask(
code_cache_context_.get(), FROM_HERE,
base::BindOnce(&RemoveCodeCacheTester::SetLastUseTimeOnThread,
base::Unretained(this), cache, url, origin_lock, time,
loop.QuitClosure()));
loop.Run();
}
void SetLastUseTimeOnThread(Cache cache,
const GURL& url,
const GURL& origin_lock,
base::Time time,
base::OnceClosure quit) {
GetCache(cache)->SetLastUsedTimeForTest(
url, origin_lock, net::NetworkIsolationKey(), time, std::move(quit));
}
std::string received_data() { return received_data_; }
private:
GeneratedCodeCache* GetCache(Cache cache) {
if (cache == kJs)
return code_cache_context_->generated_js_code_cache();
else if (cache == kWebAssembly)
return code_cache_context_->generated_wasm_code_cache();
else
return code_cache_context_->generated_webui_js_code_cache();
}
void FetchEntryCallback(base::OnceClosure quit,
const base::Time& response_time,
mojo_base::BigBuffer data) {
if (!response_time.is_null()) {
entry_exists_ = true;
received_data_ = std::string(data.data(), data.data() + data.size());
} else {
entry_exists_ = false;
}
std::move(quit).Run();
}
bool entry_exists_;
AwaitCompletionHelper await_completion_;
raw_ptr<GeneratedCodeCacheContext> code_cache_context_;
std::string received_data_;
};
#if BUILDFLAG(ENABLE_PLUGINS)
class RemovePluginPrivateDataTester {
public:
explicit RemovePluginPrivateDataTester(
storage::FileSystemContext* filesystem_context)
: filesystem_context_(filesystem_context) {}
RemovePluginPrivateDataTester(const RemovePluginPrivateDataTester&) = delete;
RemovePluginPrivateDataTester& operator=(
const RemovePluginPrivateDataTester&) = delete;
// Add some files to the PluginPrivateFileSystem. They are created as follows:
// url1 - ClearKey - 1 file - timestamp 10 days ago
// url2 - Widevine - 2 files - timestamps now and 60 days ago
void AddPluginPrivateTestData(const GURL& url1, const GURL& url2) {
base::Time now = base::Time::Now();
base::Time ten_days_ago = now - base::Days(10);
base::Time sixty_days_ago = now - base::Days(60);
// Create a PluginPrivateFileSystem for ClearKey and add a single file
// with a timestamp of 1 day ago.
std::string clearkey_fsid = CreateFileSystem(kClearKeyCdmPluginId, url1);
clearkey_file_ = CreateFile(url1, clearkey_fsid, "foo");
SetFileTimestamp(clearkey_file_, ten_days_ago);
// Create a second PluginPrivateFileSystem for Widevine and add two files
// with different times.
std::string widevine_fsid = CreateFileSystem(kWidevineCdmPluginId, url2);
storage::FileSystemURL widevine_file1 =
CreateFile(url2, widevine_fsid, "bar1");
storage::FileSystemURL widevine_file2 =
CreateFile(url2, widevine_fsid, "bar2");
SetFileTimestamp(widevine_file1, now);
SetFileTimestamp(widevine_file2, sixty_days_ago);
}
void DeleteClearKeyTestData() { DeleteFile(clearkey_file_); }
// Returns true, if the given origin exists in a PluginPrivateFileSystem.
bool DataExistsForOrigin(const url::Origin& origin) {
AwaitCompletionHelper await_completion;
bool data_exists_for_origin = false;
filesystem_context_->default_file_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&RemovePluginPrivateDataTester::
CheckIfDataExistsForOriginOnFileTaskRunner,
base::Unretained(this), origin, &data_exists_for_origin,
&await_completion));
await_completion.BlockUntilNotified();
return data_exists_for_origin;
}
private:
// Creates a PluginPrivateFileSystem for the |plugin_name| and |origin|
// provided. Returns the file system ID for the created
// PluginPrivateFileSystem.
std::string CreateFileSystem(const std::string& plugin_name,
const GURL& origin) {
AwaitCompletionHelper await_completion;
std::string fsid =
storage::IsolatedContext::GetInstance()
->RegisterFileSystemForVirtualPath(
storage::kFileSystemTypePluginPrivate,
storage::kPluginPrivateRootName, base::FilePath());
EXPECT_TRUE(storage::ValidateIsolatedFileSystemId(fsid));
filesystem_context_->OpenPluginPrivateFileSystem(
url::Origin::Create(origin), storage::kFileSystemTypePluginPrivate,
fsid, plugin_name, storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
base::BindOnce(&RemovePluginPrivateDataTester::OnFileSystemOpened,
base::Unretained(this), &await_completion));
await_completion.BlockUntilNotified();
return fsid;
}
// Creates a file named |file_name| in the PluginPrivateFileSystem identified
// by |origin| and |fsid|. Returns the URL for the created file. The file
// must not already exist or the test will fail.
storage::FileSystemURL CreateFile(const GURL& origin,
const std::string& fsid,
const std::string& file_name) {
AwaitCompletionHelper await_completion;
std::string root = storage::GetIsolatedFileSystemRootURIString(
origin, fsid, storage::kPluginPrivateRootName);
storage::FileSystemURL file_url =
filesystem_context_->CrackURLInFirstPartyContext(
GURL(root + file_name));
storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
storage::kFileSystemTypePluginPrivate);
std::unique_ptr<storage::FileSystemOperationContext> operation_context =
std::make_unique<storage::FileSystemOperationContext>(
filesystem_context_);
operation_context->set_allowed_bytes_growth(
storage::QuotaManager::kNoLimit);
file_util->EnsureFileExists(
std::move(operation_context), file_url,
base::BindOnce(&RemovePluginPrivateDataTester::OnFileCreated,
base::Unretained(this), &await_completion));
await_completion.BlockUntilNotified();
return file_url;
}
void DeleteFile(storage::FileSystemURL file_url) {
AwaitCompletionHelper await_completion;
storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
storage::kFileSystemTypePluginPrivate);
std::unique_ptr<storage::FileSystemOperationContext> operation_context =
std::make_unique<storage::FileSystemOperationContext>(
filesystem_context_);
file_util->DeleteFile(
std::move(operation_context), file_url,
base::BindOnce(&RemovePluginPrivateDataTester::OnFileDeleted,
base::Unretained(this), &await_completion));
await_completion.BlockUntilNotified();
}
// Sets the last_access_time and last_modified_time to |time_stamp| on the
// file specified by |file_url|. The file must already exist.
void SetFileTimestamp(const storage::FileSystemURL& file_url,
const base::Time& time_stamp) {
AwaitCompletionHelper await_completion;
storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
storage::kFileSystemTypePluginPrivate);
std::unique_ptr<storage::FileSystemOperationContext> operation_context =
std::make_unique<storage::FileSystemOperationContext>(
filesystem_context_);
file_util->Touch(
std::move(operation_context), file_url, time_stamp, time_stamp,
base::BindOnce(&RemovePluginPrivateDataTester::OnFileTouched,
base::Unretained(this), &await_completion));
await_completion.BlockUntilNotified();
}
void OnFileSystemOpened(AwaitCompletionHelper* await_completion,
base::File::Error result) {
EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result);
await_completion->Notify();
}
void OnFileCreated(AwaitCompletionHelper* await_completion,
base::File::Error result,
bool created) {
EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result);
EXPECT_TRUE(created);
await_completion->Notify();
}
void OnFileDeleted(AwaitCompletionHelper* await_completion,
base::File::Error result) {
EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result);
await_completion->Notify();
}
void OnFileTouched(AwaitCompletionHelper* await_completion,
base::File::Error result) {
EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result);
await_completion->Notify();
}
// If |origin| exists in the PluginPrivateFileSystem, set
// |data_exists_for_origin| to true, false otherwise.
void CheckIfDataExistsForOriginOnFileTaskRunner(
const url::Origin& origin,
bool* data_exists_for_origin,
AwaitCompletionHelper* await_completion) {
storage::FileSystemBackend* backend =
filesystem_context_->GetFileSystemBackend(
storage::kFileSystemTypePluginPrivate);
storage::FileSystemQuotaUtil* quota_util = backend->GetQuotaUtil();
// Determine the set of StorageKeys used.
std::vector<blink::StorageKey> storage_keys =
quota_util->GetStorageKeysForTypeOnFileTaskRunner(
storage::kFileSystemTypePluginPrivate);
// TODO(https://crbug.com/1231162): determine whether EME/CDM/plugin private
// file system will be partitioned; if so, replace the in-line conversion
// with the correct third-party StorageKey.
*data_exists_for_origin =
base::Contains(storage_keys, blink::StorageKey(origin));
// AwaitCompletionHelper and MessageLoop don't work on a
// SequencedTaskRunner, so post a task on the IO thread.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&AwaitCompletionHelper::Notify,
base::Unretained(await_completion)));
}
// We don't own this pointer.
raw_ptr<storage::FileSystemContext> filesystem_context_;
// Keep track of the URL for the ClearKey file so that it can be written to
// or deleted.
storage::FileSystemURL clearkey_file_;
};
#endif // BUILDFLAG(ENABLE_PLUGINS)
class MockDataRemovalObserver : public StoragePartition::DataRemovalObserver {
public:
explicit MockDataRemovalObserver(StoragePartition* partition) {
observation_.Observe(partition);
}
MOCK_METHOD4(OnOriginDataCleared,
void(uint32_t,
base::RepeatingCallback<bool(const url::Origin&)>,
base::Time,
base::Time));
private:
base::ScopedObservation<StoragePartition,
StoragePartition::DataRemovalObserver>
observation_{this};
};
class MockAggregationService : public AggregationServiceImpl {
public:
explicit MockAggregationService(StoragePartitionImpl* partition)
: AggregationServiceImpl(/*run_in_memory=*/true,
/*user_data_directory=*/base::FilePath(),
partition) {}
MOCK_METHOD(void,
ClearData,
(base::Time delete_begin,
base::Time delete_end,
base::OnceClosure done),
(override));
};
bool IsWebSafeSchemeForTest(const std::string& scheme) {
return scheme == url::kHttpScheme;
}
bool DoesOriginMatchForUnprotectedWeb(
const url::Origin& origin,
storage::SpecialStoragePolicy* special_storage_policy) {
if (IsWebSafeSchemeForTest(origin.scheme()))
return !special_storage_policy->IsStorageProtected(origin.GetURL());
return false;
}
bool DoesOriginMatchForBothProtectedAndUnprotectedWeb(
const url::Origin& origin,
storage::SpecialStoragePolicy* special_storage_policy) {
return true;
}
bool DoesOriginMatchUnprotected(
const url::Origin& desired_origin,
const url::Origin& origin,
storage::SpecialStoragePolicy* special_storage_policy) {
return origin.scheme() != desired_origin.scheme();
}
void ClearQuotaData(content::StoragePartition* partition,
base::RunLoop* loop_to_quit) {
partition->ClearData(
kAllQuotaRemoveMask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
GURL(), base::Time(), base::Time::Max(), loop_to_quit->QuitClosure());
}
void ClearQuotaDataWithOriginMatcher(
content::StoragePartition* partition,
StoragePartition::OriginMatcherFunction origin_matcher,
const base::Time delete_begin,
base::RunLoop* loop_to_quit) {
partition->ClearData(kAllQuotaRemoveMask,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
std::move(origin_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,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
remove_origin, delete_begin, base::Time::Max(),
loop_to_quit->QuitClosure());
}
void ClearQuotaDataForNonPersistent(content::StoragePartition* partition,
const base::Time delete_begin,
base::RunLoop* loop_to_quit) {
partition->ClearData(kAllQuotaRemoveMask,
~StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT,
GURL(), 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,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, GURL(),
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,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
StoragePartition::OriginMatcherFunction(),
std::move(delete_filter), false, delete_begin,
delete_end, run_loop->QuitClosure());
}
void ClearStuff(uint32_t remove_mask,
content::StoragePartition* partition,
const base::Time delete_begin,
const base::Time delete_end,
StoragePartition::OriginMatcherFunction origin_matcher,
base::RunLoop* run_loop) {
partition->ClearData(remove_mask,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
std::move(origin_matcher), nullptr, false, delete_begin,
delete_end, run_loop->QuitClosure());
}
void ClearData(content::StoragePartition* partition, base::RunLoop* run_loop) {
base::Time time;
partition->ClearData(StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, GURL(),
time, time, 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;
}
#if BUILDFLAG(ENABLE_PLUGINS)
void ClearPluginPrivateData(content::StoragePartition* partition,
const GURL& storage_origin,
const base::Time delete_begin,
const base::Time delete_end,
base::RunLoop* run_loop) {
partition->ClearData(
StoragePartitionImpl::REMOVE_DATA_MASK_PLUGIN_PRIVATE_DATA,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, storage_origin,
delete_begin, delete_end, run_loop->QuitClosure());
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
void ClearInterestGroups(content::StoragePartition* partition,
const base::Time delete_begin,
const base::Time delete_end,
base::RunLoop* run_loop) {
partition->ClearData(StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, GURL(),
delete_begin, delete_end, run_loop->QuitClosure());
}
bool FilterMatchesCookie(const CookieDeletionFilterPtr& filter,
const net::CanonicalCookie& cookie) {
return network::DeletionFilterToInfo(filter.Clone())
.Matches(cookie,
net::CookieAccessParams{
net::CookieAccessSemantics::NONLEGACY, false,
net::CookieSamePartyStatus::kNoSamePartyEnforcement});
}
} // namespace
class StoragePartitionImplTest : public testing::Test {
public:
StoragePartitionImplTest()
: task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
browser_context_(new TestBrowserContext()) {
// Prevent test flakiness as a result of randomized responses in the
// Attribution Reporting API.
command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kConversionsDebugMode);
// Configures the Conversion API to run in memory to speed up its
// initialization and avoid timeouts. See https://crbug.com/1080764.
AttributionManagerImpl::RunInMemoryForTesting();
feature_list_.InitWithFeatures({blink::features::kInterestGroupStorage,
blink::features::kSharedStorageAPI},
{});
}
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,
{blink::mojom::StorageType::kTemporary,
blink::mojom::StorageType::kPersistent});
}
return quota_manager_.get();
}
TestBrowserContext* browser_context() { return browser_context_.get(); }
content::BrowserTaskEnvironment* task_environment() {
return &task_environment_;
}
private:
base::test::ScopedCommandLine command_line_;
base::test::ScopedFeatureList feature_list_;
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestBrowserContext> browser_context_;
scoped_refptr<storage::MockQuotaManager> quota_manager_;
};
class StoragePartitionShaderClearTest : public testing::Test {
public:
StoragePartitionShaderClearTest()
: task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
browser_context_(new TestBrowserContext()) {
InitShaderCacheFactorySingleton();
GetShaderCacheFactorySingleton()->SetCacheInfo(
kDefaultClientId,
browser_context()->GetDefaultStoragePartition()->GetPath());
cache_ = GetShaderCacheFactorySingleton()->Get(kDefaultClientId);
}
~StoragePartitionShaderClearTest() override {
cache_ = nullptr;
GetShaderCacheFactorySingleton()->RemoveCacheInfo(kDefaultClientId);
}
void InitCache() {
net::TestCompletionCallback available_cb;
int rv = cache_->SetAvailableCallback(available_cb.callback());
ASSERT_EQ(net::OK, available_cb.GetResult(rv));
EXPECT_EQ(0, cache_->Size());
cache_->Cache(kCacheKey, kCacheValue);
net::TestCompletionCallback complete_cb;
rv = cache_->SetCacheCompleteCallback(complete_cb.callback());
ASSERT_EQ(net::OK, complete_cb.GetResult(rv));
}
size_t Size() { return cache_->Size(); }
TestBrowserContext* browser_context() { return browser_context_.get(); }
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestBrowserContext> browser_context_;
scoped_refptr<gpu::ShaderDiskCache> cache_;
};
// Tests ---------------------------------------------------------------------
TEST_F(StoragePartitionShaderClearTest, ClearShaderCache) {
InitCache();
EXPECT_EQ(1u, Size());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearData,
browser_context()->GetDefaultStoragePartition(),
&run_loop));
run_loop.Run();
EXPECT_EQ(0u, Size());
}
TEST_F(StoragePartitionImplTest, QuotaClientTypesGeneration) {
EXPECT_THAT(
StoragePartitionImpl::GenerateQuotaClientTypes(
StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS),
testing::UnorderedElementsAre(storage::QuotaClientType::kFileSystem,
storage::QuotaClientType::kNativeIO));
EXPECT_THAT(StoragePartitionImpl::GenerateQuotaClientTypes(
StoragePartition::REMOVE_DATA_MASK_WEBSQL),
testing::ElementsAre(storage::QuotaClientType::kDatabase));
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::kDatabase,
storage::QuotaClientType::kIndexedDatabase,
storage::QuotaClientType::kNativeIO));
}
storage::BucketInfo AddQuotaManagedBucket(
storage::MockQuotaManager* manager,
const blink::StorageKey& storage_key,
const std::string& bucket_name,
blink::mojom::StorageType type,
base::Time modified = base::Time::Now()) {
storage::BucketInfo bucket =
manager->CreateBucket(storage_key, bucket_name, type);
manager->AddBucket(bucket, {kClientFile}, modified);
EXPECT_TRUE(manager->BucketHasData(bucket, kClientFile));
return bucket;
}
TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverBoth) {
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/");
AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
storage::kDefaultBucketName, kTemporary);
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
storage::kDefaultBucketName, kTemporary);
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
storage::kDefaultBucketName, kPersistent);
AddQuotaManagedBucket(GetMockManager(), kStorageKey3,
storage::kDefaultBucketName, kPersistent);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}
TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyTemporary) {
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, kTemporary);
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
storage::kDefaultBucketName, kTemporary);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}
TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyPersistent) {
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, kPersistent);
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
storage::kDefaultBucketName, kPersistent);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}
TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverNeither) {
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->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/");
const blink::StorageKey kStorageKey3 =
blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary);
storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary);
storage::BucketInfo host2_perm_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kPersistent);
storage::BucketInfo host3_perm_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey3, storage::kDefaultBucketName, kPersistent);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearQuotaDataForOrigin, partition,
kStorageKey1.origin().GetURL(), base::Time(), &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);
EXPECT_FALSE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host2_perm_bucket, kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host3_perm_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_temp_bucket_now = AddQuotaManagedBucket(
GetMockManager(), kStorageKey1, "temp_bucket_now", kTemporary, now);
storage::BucketInfo host1_perm_bucket_now = AddQuotaManagedBucket(
GetMockManager(), kStorageKey1, "perm_bucket_now", kPersistent, now);
storage::BucketInfo host2_temp_bucket_now = AddQuotaManagedBucket(
GetMockManager(), kStorageKey2, "temp_bucket_now", kTemporary, now);
storage::BucketInfo host2_perm_bucket_now = AddQuotaManagedBucket(
GetMockManager(), kStorageKey2, "perm_bucket_now", kPersistent, now);
// Buckets modified a day ago.
base::Time yesterday = now - base::Days(1);
storage::BucketInfo host1_temp_bucket_yesterday =
AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
"temp_bucket_yesterday", kTemporary, yesterday);
storage::BucketInfo host1_perm_bucket_yesterday =
AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
"perm_bucket_yesterday", kPersistent, yesterday);
storage::BucketInfo host2_temp_bucket_yesterday =
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
"temp_bucket_yesterday", kTemporary, yesterday);
storage::BucketInfo host2_perm_bucket_yesterday =
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
"perm_bucket_yesterday", kPersistent, yesterday);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 8);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearQuotaDataForOrigin, partition, GURL(),
base::Time::Now() - base::Hours(1), &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
EXPECT_FALSE(
GetMockManager()->BucketHasData(host1_temp_bucket_now, kClientFile));
EXPECT_FALSE(
GetMockManager()->BucketHasData(host1_perm_bucket_now, kClientFile));
EXPECT_FALSE(
GetMockManager()->BucketHasData(host2_temp_bucket_now, kClientFile));
EXPECT_FALSE(
GetMockManager()->BucketHasData(host2_perm_bucket_now, kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket_yesterday,
kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host1_perm_bucket_yesterday,
kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket_yesterday,
kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host2_perm_bucket_yesterday,
kClientFile));
}
TEST_F(StoragePartitionImplTest,
RemoveQuotaManagedNonPersistentDataForLastWeek) {
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 temp_bucket_yesterday =
AddQuotaManagedBucket(GetMockManager(), kStorageKey,
"temp_bucket_yesterday", kTemporary, yesterday);
storage::BucketInfo perm_bucket_yesterday =
AddQuotaManagedBucket(GetMockManager(), kStorageKey,
"perm_bucket_yesterday", kPersistent, yesterday);
// Buckets modified 10 days ago.
base::Time ten_days_ago = now - base::Days(10);
storage::BucketInfo temp_bucket_ten_days_ago = AddQuotaManagedBucket(
GetMockManager(), kStorageKey, "temp_bucket_ten_days_ago", kTemporary,
ten_days_ago);
storage::BucketInfo perm_bucket_ten_days_ago = AddQuotaManagedBucket(
GetMockManager(), kStorageKey, "perm_bucket_ten_days_ago", kPersistent,
ten_days_ago);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
base::RunLoop run_loop;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearQuotaDataForNonPersistent, partition,
base::Time::Now() - base::Days(7), &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);
EXPECT_FALSE(
GetMockManager()->BucketHasData(temp_bucket_yesterday, kClientFile));
EXPECT_TRUE(
GetMockManager()->BucketHasData(perm_bucket_yesterday, kClientFile));
EXPECT_TRUE(
GetMockManager()->BucketHasData(temp_bucket_ten_days_ago, kClientFile));
EXPECT_TRUE(
GetMockManager()->BucketHasData(perm_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_temp_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary);
storage::BucketInfo host1_perm_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kPersistent);
storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary);
storage::BucketInfo host2_perm_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kPersistent);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
// 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::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
base::Time(), &run_loop));
run_loop.Run();
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(host1_perm_bucket, kClientFile));
EXPECT_FALSE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile));
EXPECT_FALSE(GetMockManager()->BucketHasData(host2_perm_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, kTemporary);
AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
storage::kDefaultBucketName, kPersistent);
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
storage::kDefaultBucketName, kTemporary);
AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
storage::kDefaultBucketName, kPersistent);
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
// 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::ThreadTaskRunnerHandle::Get()->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 temp_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey, storage::kDefaultBucketName, kTemporary,
base::Time());
storage::BucketInfo perm_bucket = AddQuotaManagedBucket(
GetMockManager(), kStorageKey, storage::kDefaultBucketName, kPersistent,
base::Time());
EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
base::RunLoop run_loop;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideQuotaManagerForTesting(GetMockManager());
base::ThreadTaskRunnerHandle::Get()->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), 2);
EXPECT_TRUE(GetMockManager()->BucketHasData(temp_bucket, kClientFile));
EXPECT_TRUE(GetMockManager()->BucketHasData(perm_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::ThreadTaskRunnerHandle::Get()->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::ThreadTaskRunnerHandle::Get()->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::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearCookiesMatchingInfo, partition,
CookieDeletionFilter::New(), &run_loop2));
run_loop2.RunUntilIdle();
EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}
TEST_F(StoragePartitionImplTest, RemoveInterestGroupForever) {
const url::Origin kOrigin = url::Origin::Create(GURL("https://host1:1/"));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
RemoveInterestGroupTester tester(partition);
tester.AddInterestGroup(kOrigin);
ASSERT_TRUE(tester.ContainsInterestGroupOwner(kOrigin));
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearInterestGroups, partition, base::Time(),
base::Time::Max(), &run_loop));
run_loop.Run();
EXPECT_FALSE(tester.ContainsInterestGroupOwner(kOrigin));
}
TEST_F(StoragePartitionImplTest, 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(task_environment(), browser_context());
tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&ClearStuff, StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
partition, base::Time(), base::Time::Max(),
base::BindRepeating(&DoesOriginMatchForUnprotectedWeb), &run_loop));
run_loop.Run();
// ClearData only guarantees that tasks to delete data are scheduled when its
// callback is invoked. It doesn't guarantee data has actually been cleared.
// So run all scheduled tasks to make sure data is cleared.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
}
TEST_F(StoragePartitionImplTest, 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(task_environment(), browser_context());
tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearStuff,
StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
partition, base::Time(), base::Time::Max(),
base::BindRepeating(
&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
&run_loop));
run_loop.Run();
// ClearData only guarantees that tasks to delete data are scheduled when its
// callback is invoked. It doesn't guarantee data has actually been cleared.
// So run all scheduled tasks to make sure data is cleared.
base::RunLoop().RunUntilIdle();
// 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_F(StoragePartitionImplTest, 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(task_environment(), 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);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearStuff,
StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
partition, a_week_ago, base::Time::Max(),
base::BindRepeating(
&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
&run_loop));
run_loop.Run();
// ClearData only guarantees that tasks to delete data are scheduled when its
// callback is invoked. It doesn't guarantee data has actually been cleared.
// So run all scheduled tasks to make sure data is cleared.
base::RunLoop().RunUntilIdle();
// 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_F(StoragePartitionImplTest, ClearCodeCache) {
const GURL kResourceURL("http://host4/script.js");
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
// Ensure code cache is initialized.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());
GURL origin = GURL("http://host1:1/");
std::string data("SomeData");
tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin, data);
EXPECT_TRUE(
tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
EXPECT_EQ(tester.received_data(), data);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
run_loop.Run();
EXPECT_FALSE(
tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
// Make sure there isn't a second invalid callback sitting in the queue.
// (this used to be a bug).
base::RunLoop().RunUntilIdle();
}
TEST_F(StoragePartitionImplTest, ClearCodeCacheSpecificURL) {
const GURL kResourceURL("http://host4/script.js");
const GURL kFilterResourceURLForCodeCache("http://host5/script.js");
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
// Ensure code cache is initialized.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());
GURL origin = GURL("http://host1:1/");
std::string data("SomeData");
tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin, data);
tester.AddEntry(RemoveCodeCacheTester::kJs, kFilterResourceURLForCodeCache,
origin, data);
EXPECT_TRUE(
tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
kFilterResourceURLForCodeCache, origin));
EXPECT_EQ(tester.received_data(), data);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&ClearCodeCache, partition, base::Time(), base::Time(),
base::BindRepeating(&FilterURL, kFilterResourceURLForCodeCache),
&run_loop));
run_loop.Run();
EXPECT_TRUE(
tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
kFilterResourceURLForCodeCache, origin));
// Make sure there isn't a second invalid callback sitting in the queue.
// (this used to be a bug).
base::RunLoop().RunUntilIdle();
}
TEST_F(StoragePartitionImplTest, ClearCodeCacheDateRange) {
const GURL kResourceURL("http://host4/script.js");
const GURL kFilterResourceURLForCodeCache("http://host5/script.js");
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
// Ensure code cache is initialized.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());
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);
GURL origin = GURL("http://host1:1/");
std::string data("SomeData");
tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin, data);
EXPECT_TRUE(
tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
EXPECT_EQ(tester.received_data(), data);
tester.SetLastUseTime(RemoveCodeCacheTester::kJs, kResourceURL, origin,
out_of_range_time);
// Add a new entry.
tester.AddEntry(RemoveCodeCacheTester::kJs, kFilterResourceURLForCodeCache,
origin, data);
EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
kFilterResourceURLForCodeCache, origin));
tester.SetLastUseTime(RemoveCodeCacheTester::kJs,
kFilterResourceURLForCodeCache, origin, in_range_time);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&ClearCodeCache, partition, begin_time, current_time,
base::BindRepeating(&FilterURL, kFilterResourceURLForCodeCache),
&run_loop));
run_loop.Run();
EXPECT_TRUE(
tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
kFilterResourceURLForCodeCache, origin));
// Make sure there isn't a second invalid callback sitting in the queue.
// (this used to be a bug).
base::RunLoop().RunUntilIdle();
}
TEST_F(StoragePartitionImplTest, ClearWasmCodeCache) {
const GURL kResourceURL("http://host4/script.js");
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
// Ensure code cache is initialized.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());
GURL origin = GURL("http://host1:1/");
std::string data("SomeData.wasm");
tester.AddEntry(RemoveCodeCacheTester::kWebAssembly, kResourceURL, origin,
data);
EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kWebAssembly,
kResourceURL, origin));
EXPECT_EQ(tester.received_data(), data);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
run_loop.Run();
EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kWebAssembly,
kResourceURL, origin));
// Make sure there isn't a second invalid callback sitting in the queue.
// (this used to be a bug).
base::RunLoop().RunUntilIdle();
}
TEST_F(StoragePartitionImplTest, ClearWebUICodeCache) {
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(features::kWebUICodeCache);
const GURL kResourceURL("chrome://host4/script.js");
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
// Ensure code cache is initialized.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());
GURL origin = GURL("chrome://host1:1/");
std::string data("SomeData");
tester.AddEntry(RemoveCodeCacheTester::kWebUiJs, kResourceURL, origin, data);
EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kWebUiJs,
kResourceURL, origin));
EXPECT_EQ(tester.received_data(), data);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
run_loop.Run();
EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kWebUiJs,
kResourceURL, origin));
// Make sure there isn't a second invalid callback sitting in the queue.
// (this used to be a bug).
base::RunLoop().RunUntilIdle();
}
TEST_F(StoragePartitionImplTest, WebUICodeCacheDisabled) {
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(features::kWebUICodeCache);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
// Ensure code cache is initialized.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
base::RunLoop run_loop;
auto* context = partition->GetGeneratedCodeCacheContext();
GeneratedCodeCacheContext::RunOrPostTask(
context, FROM_HERE, base::BindLambdaForTesting([&]() {
EXPECT_EQ(partition->GetGeneratedCodeCacheContext()
->generated_webui_js_code_cache(),
nullptr);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(StoragePartitionImplTest, ClearCodeCacheIncognito) {
browser_context()->set_is_off_the_record(true);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
base::RunLoop().RunUntilIdle();
// We should not create GeneratedCodeCacheContext for off the record mode.
EXPECT_EQ(nullptr, partition->GetGeneratedCodeCacheContext());
base::RunLoop run_loop;
// This shouldn't crash.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
run_loop.Run();
}
#if BUILDFLAG(ENABLE_PLUGINS)
TEST_F(StoragePartitionImplTest, RemovePluginPrivateDataForever) {
const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
RemovePluginPrivateDataTester tester(partition->GetFileSystemContext());
tester.AddPluginPrivateTestData(kOrigin1.GetURL(), kOrigin2.GetURL());
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin2));
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearPluginPrivateData, partition, GURL(),
base::Time(), base::Time::Max(), &run_loop));
run_loop.Run();
EXPECT_FALSE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_FALSE(tester.DataExistsForOrigin(kOrigin2));
}
TEST_F(StoragePartitionImplTest, RemovePluginPrivateDataLastWeek) {
const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
base::Time a_week_ago = base::Time::Now() - base::Days(7);
RemovePluginPrivateDataTester tester(partition->GetFileSystemContext());
tester.AddPluginPrivateTestData(kOrigin1.GetURL(), kOrigin2.GetURL());
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin2));
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearPluginPrivateData, partition, GURL(),
a_week_ago, base::Time::Max(), &run_loop));
run_loop.Run();
// Origin1 has 1 file from 10 days ago, so it should remain around.
// Origin2 has a current file, so it should be removed (even though the
// second file is much older).
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_FALSE(tester.DataExistsForOrigin(kOrigin2));
}
TEST_F(StoragePartitionImplTest, RemovePluginPrivateDataForOrigin) {
const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
RemovePluginPrivateDataTester tester(partition->GetFileSystemContext());
tester.AddPluginPrivateTestData(kOrigin1.GetURL(), kOrigin2.GetURL());
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin2));
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearPluginPrivateData, partition, kOrigin1.GetURL(),
base::Time(), base::Time::Max(), &run_loop));
run_loop.Run();
// Only Origin1 should be deleted.
EXPECT_FALSE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin2));
}
TEST_F(StoragePartitionImplTest, RemovePluginPrivateDataAfterDeletion) {
const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
RemovePluginPrivateDataTester tester(partition->GetFileSystemContext());
tester.AddPluginPrivateTestData(kOrigin1.GetURL(), kOrigin2.GetURL());
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin2));
// Delete the single file saved for |kOrigin1|. This does not remove the
// origin from the list of Origins. However, ClearPluginPrivateData() will
// remove it.
tester.DeleteClearKeyTestData();
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_TRUE(tester.DataExistsForOrigin(kOrigin2));
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ClearPluginPrivateData, partition, GURL(),
base::Time(), base::Time::Max(), &run_loop));
run_loop.Run();
EXPECT_FALSE(tester.DataExistsForOrigin(kOrigin1));
EXPECT_FALSE(tester.DataExistsForOrigin(kOrigin2));
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
TEST(StoragePartitionImplStaticTest, CreatePredicateForHostCookies) {
GURL url("http://www.example.com/");
GURL url2("https://www.example.com/");
GURL url3("https://www.google.com/");
absl::optional<base::Time> server_time = absl::nullopt;
CookieDeletionFilterPtr deletion_filter = CookieDeletionFilter::New();
deletion_filter->host_name = url.host();
base::Time now = base::Time::Now();
std::vector<std::unique_ptr<CanonicalCookie>> valid_cookies;
valid_cookies.push_back(CanonicalCookie::Create(
url, "A=B", now, server_time, absl::nullopt /* cookie_partition_key */));
valid_cookies.push_back(CanonicalCookie::Create(
url, "C=F", now, server_time, absl::nullopt /* cookie_partition_key */));
// We should match a different scheme with the same host.
valid_cookies.push_back(CanonicalCookie::Create(
url2, "A=B", now, server_time, absl::nullopt /* cookie_partition_key */));
std::vector<std::unique_ptr<CanonicalCookie>> invalid_cookies;
// We don't match domain cookies.
invalid_cookies.push_back(
CanonicalCookie::Create(url2, "A=B;domain=.example.com", now, server_time,
absl::nullopt /* cookie_partition_key */));
invalid_cookies.push_back(CanonicalCookie::Create(
url3, "A=B", now, server_time, absl::nullopt /* cookie_partition_key */));
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, ConversionsClearDataForOrigin) {
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
AttributionManagerImpl* attribution_manager =
partition->GetAttributionManager();
base::Time now = base::Time::Now();
auto source = SourceBuilder(now).SetExpiry(base::Days(2)).Build();
attribution_manager->HandleSource(source);
attribution_manager->HandleTrigger(DefaultTrigger());
base::RunLoop run_loop;
partition->ClearData(StoragePartition::REMOVE_DATA_MASK_CONVERSIONS, 0,
source.common_info().impression_origin().GetURL(), now,
now, run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(
GetAttributionReportsForTesting(attribution_manager, base::Time::Max())
.empty());
}
TEST_F(StoragePartitionImplTest, ConversionsClearDataWrongMask) {
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
AttributionManagerImpl* attribution_manager =
partition->GetAttributionManager();
base::Time now = base::Time::Now();
auto source = SourceBuilder(now).SetExpiry(base::Days(2)).Build();
attribution_manager->HandleSource(source);
attribution_manager->HandleTrigger(DefaultTrigger());
EXPECT_FALSE(
GetAttributionReportsForTesting(attribution_manager, base::Time::Max())
.empty());
// Arbitrary non-conversions mask.
base::RunLoop run_loop;
partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES, 0,
source.common_info().impression_origin().GetURL(), now,
now, run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(
GetAttributionReportsForTesting(attribution_manager, base::Time::Max())
.empty());
}
TEST_F(StoragePartitionImplTest, ConversionsClearAllData) {
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
AttributionManagerImpl* attribution_manager =
partition->GetAttributionManager();
base::Time now = base::Time::Now();
for (int i = 0; i < 20; i++) {
auto origin = url::Origin::Create(
GURL(base::StringPrintf("https://www.%d.test/", i)));
auto source = SourceBuilder(now)
.SetExpiry(base::Days(2))
.SetImpressionOrigin(origin)
.SetReportingOrigin(origin)
.SetConversionOrigin(origin)
.Build();
attribution_manager->HandleSource(source);
}
base::RunLoop run_loop;
partition->ClearData(StoragePartition::REMOVE_DATA_MASK_CONVERSIONS, 0,
GURL(), now, now, run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(
GetAttributionReportsForTesting(attribution_manager, base::Time::Max())
.empty());
}
TEST_F(StoragePartitionImplTest, ConversionsClearDataForFilter) {
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
AttributionManagerImpl* attribution_manager =
partition->GetAttributionManager();
base::Time now = base::Time::Now();
for (int i = 0; i < 5; i++) {
auto impression =
url::Origin::Create(GURL(base::StringPrintf("https://imp-%d.com/", i)));
auto reporter = url::Origin::Create(
GURL(base::StringPrintf("https://reporter-%d.com/", i)));
auto conv = url::Origin::Create(
GURL(base::StringPrintf("https://conv-%d.com/", i)));
attribution_manager->HandleSource(SourceBuilder(now)
.SetImpressionOrigin(impression)
.SetReportingOrigin(reporter)
.SetConversionOrigin(conv)
.SetExpiry(base::Days(2))
.Build());
attribution_manager->HandleTrigger(TriggerBuilder()
.SetDestinationOrigin(conv)
.SetReportingOrigin(reporter)
.Build());
}
EXPECT_EQ(5u, GetAttributionReportsForTesting(attribution_manager,
base::Time::Max())
.size());
// Match against enough Origins to delete three of the imp/conv pairs.
base::RunLoop run_loop;
StoragePartition::OriginMatcherFunction func = base::BindRepeating(
[](const url::Origin& origin, storage::SpecialStoragePolicy* policy) {
return origin == url::Origin::Create(GURL("https://imp-2.com/")) ||
origin == url::Origin::Create(GURL("https://conv-3.com/")) ||
origin == url::Origin::Create(GURL("https://rep-4.com/")) ||
origin == url::Origin::Create(GURL("https://imp-4.com/"));
});
partition->ClearData(StoragePartition::REMOVE_DATA_MASK_CONVERSIONS, 0, func,
nullptr, false, now, now, run_loop.QuitClosure());
run_loop.Run();
EXPECT_EQ(2u, GetAttributionReportsForTesting(attribution_manager,
base::Time::Max())
.size());
}
TEST_F(StoragePartitionImplTest, DataRemovalObserver) {
const uint32_t kTestClearMask =
content::StoragePartition::REMOVE_DATA_MASK_INDEXEDDB |
content::StoragePartition::REMOVE_DATA_MASK_WEBSQL;
const uint32_t kTestQuotaClearMask = 0;
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 origin_callback_valid =
[&](base::RepeatingCallback<bool(const url::Origin&)> callback) {
return callback.Run(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,
OnOriginDataCleared(kTestClearMask, testing::Truly(origin_callback_valid),
base::Time(), base::Time::Max()));
base::RunLoop run_loop;
partition->ClearDataForOrigin(kTestClearMask, kTestQuotaClearMask,
kTestOrigin, run_loop.QuitClosure());
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(&observer);
EXPECT_CALL(
observer,
OnOriginDataCleared(kTestClearMask, testing::Truly(origin_callback_valid),
kBeginTime, kEndTime));
partition->ClearData(kTestClearMask, kTestQuotaClearMask, kTestOrigin,
kBeginTime, kEndTime, base::DoNothing());
testing::Mock::VerifyAndClearExpectations(&observer);
EXPECT_CALL(
observer,
OnOriginDataCleared(kTestClearMask, testing::Truly(origin_callback_valid),
kBeginTime, kEndTime));
partition->ClearData(
kTestClearMask, kTestQuotaClearMask,
base::BindLambdaForTesting([&](const url::Origin& origin,
storage::SpecialStoragePolicy* policy) {
return origin == url::Origin::Create(kTestOrigin);
}),
/* cookie_deletion_filter */ nullptr, /* perform_storage_cleanup */ false,
kBeginTime, kEndTime, base::DoNothing());
}
namespace {
class MockLocalTrustTokenFulfiller : public mojom::LocalTrustTokenFulfiller {
public:
enum IgnoreRequestsTag { kIgnoreRequestsIndefinitely };
explicit MockLocalTrustTokenFulfiller(IgnoreRequestsTag) {}
explicit MockLocalTrustTokenFulfiller(
const network::mojom::FulfillTrustTokenIssuanceAnswerPtr& answer)
: answer_(answer.Clone()) {}
void FulfillTrustTokenIssuance(
network::mojom::FulfillTrustTokenIssuanceRequestPtr request,
FulfillTrustTokenIssuanceCallback callback) override {
if (answer_)
std::move(callback).Run(answer_.Clone());
// Otherwise, this class was constructed with an IgnoreRequestsTag; drop the
// request.
}
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<mojom::LocalTrustTokenFulfiller>(
std::move(handle)));
}
private:
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer_;
mojo::Receiver<mojom::LocalTrustTokenFulfiller> receiver_{this};
};
} // namespace
#if BUILDFLAG(IS_ANDROID)
TEST_F(StoragePartitionImplTest, BindsTrustTokenFulfiller) {
auto expected_answer = network::mojom::FulfillTrustTokenIssuanceAnswer::New();
expected_answer->status =
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk;
expected_answer->response = "Okay, here are some tokens";
MockLocalTrustTokenFulfiller mock_fulfiller(expected_answer);
// On Android, binding a local trust token operation delegate should succeed
// by default, but it can be explicitly rejected by the Android-side
// implementation code: to avoid making assumptions about that code's
// behavior, manually override the bind to make it succeed.
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
int num_binds_attempted = 0;
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindLambdaForTesting([&num_binds_attempted, &mock_fulfiller](
mojo::ScopedMessagePipeHandle handle) {
++num_binds_attempted;
mock_fulfiller.Bind(std::move(handle));
}));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
auto request = network::mojom::FulfillTrustTokenIssuanceRequest::New();
request->request = "Some tokens, please";
{
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
base::RunLoop run_loop;
partition->OnTrustTokenIssuanceDivertedToSystem(
request.Clone(),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(mojo::Equals(received_answer, expected_answer));
EXPECT_EQ(num_binds_attempted, 1);
}
{
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
base::RunLoop run_loop;
// Execute another operation to cover the case where we've already
// successfully bound the fulfiller, ensuring that we don't attempt to bind
// it again.
partition->OnTrustTokenIssuanceDivertedToSystem(
request.Clone(),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(mojo::Equals(received_answer, expected_answer));
EXPECT_EQ(num_binds_attempted, 1);
}
}
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_ANDROID)
TEST_F(StoragePartitionImplTest, HandlesDisconnectedTrustTokenFulfiller) {
// Construct a mock fulfiller that doesn't reply to issuance requests it
// receives...
MockLocalTrustTokenFulfiller mock_fulfiller(
MockLocalTrustTokenFulfiller::kIgnoreRequestsIndefinitely);
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindRepeating(&MockLocalTrustTokenFulfiller::Bind,
base::Unretained(&mock_fulfiller)));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
auto request = network::mojom::FulfillTrustTokenIssuanceRequest::New();
base::RunLoop run_loop;
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
partition->OnTrustTokenIssuanceDivertedToSystem(
std::move(request),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
// ... and, when the pipe disconnects, the disconnection handler should still
// ensure we get an error response.
partition->OnLocalTrustTokenFulfillerConnectionError();
run_loop.Run();
ASSERT_TRUE(received_answer);
EXPECT_EQ(received_answer->status,
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound);
}
#endif // BUILDFLAG(IS_ANDROID)
TEST_F(StoragePartitionImplTest, HandlesMissingTrustTokenFulfiller) {
#if BUILDFLAG(IS_ANDROID)
// On Android, binding can be explicitly rejected by the Android-side
// implementation code: to ensure we can handle the rejection, manually force
// the bind to fail.
//
// On other platforms, local Trust Tokens issuance isn't yet implemented, so
// StoragePartitionImpl won't attempt to bind the fulfiller.
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
// Instead of using interface_overrider.ClearBinder(name), it's necessary to
// provide a callback that explicitly closes the pipe, since
// InterfaceProvider's contract requires that it either bind or close pipes
// it's given (see its comments in interface_provider.mojom).
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindRepeating([](mojo::ScopedMessagePipeHandle handle) {
mojo::Close(std::move(handle));
}));
#endif // BUILDFLAG(IS_ANDROID)
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
auto request = network::mojom::FulfillTrustTokenIssuanceRequest::New();
base::RunLoop run_loop;
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
partition->OnTrustTokenIssuanceDivertedToSystem(
std::move(request),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(received_answer);
EXPECT_EQ(received_answer->status,
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound);
}
TEST_F(StoragePartitionImplTest, RemoveAggregationServiceData) {
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
auto aggregation_service =
std::make_unique<MockAggregationService>(partition);
auto* aggregation_service_ptr = aggregation_service.get();
partition->OverrideAggregationServiceForTesting(
std::move(aggregation_service));
const uint32_t kTestClearMask =
StoragePartition::REMOVE_DATA_MASK_AGGREGATION_SERVICE;
const uint32_t kTestQuotaClearMask =
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL;
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 invoke_callback =
[](base::Time delete_begin, base::Time delete_end,
base::OnceClosure done) { std::move(done).Run(); };
// Verify that each of the StoragePartition interfaces for clearing origin
// based data calls aggregation service appropriately.
EXPECT_CALL(*aggregation_service_ptr,
ClearData(base::Time(), base::Time::Max(), testing::_))
.WillOnce(testing::Invoke(invoke_callback));
base::RunLoop run_loop;
partition->ClearDataForOrigin(kTestClearMask, kTestQuotaClearMask,
kTestOrigin, run_loop.QuitClosure());
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
EXPECT_CALL(*aggregation_service_ptr,
ClearData(kBeginTime, kEndTime, testing::_))
.WillOnce(testing::Invoke(invoke_callback));
partition->ClearData(kTestClearMask, kTestQuotaClearMask, kTestOrigin,
kBeginTime, kEndTime, base::DoNothing());
testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
EXPECT_CALL(*aggregation_service_ptr,
ClearData(kBeginTime, kEndTime, testing::_))
.WillOnce(testing::Invoke(invoke_callback));
partition->ClearData(
kTestClearMask, kTestQuotaClearMask,
base::BindLambdaForTesting([&](const url::Origin& origin,
storage::SpecialStoragePolicy* policy) {
return origin == url::Origin::Create(kTestOrigin);
}),
/*cookie_deletion_filter=*/nullptr, /*perform_storage_cleanup=*/false,
kBeginTime, kEndTime, base::DoNothing());
testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
EXPECT_CALL(*aggregation_service_ptr,
ClearData(kBeginTime, kEndTime, testing::_))
.WillOnce(testing::Invoke(invoke_callback));
partition->ClearData(kTestClearMask, kTestQuotaClearMask, GURL(), 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::Partition> persistent_partition;
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->BindPartition(
temp_dir.GetPath(), persistent_partition.BindNewPipeAndPassReceiver());
persistent_partition->BindLocalStorageControl(
storage_control.BindNewPipeAndPassReceiver());
storage_control.FlushForTesting();
}
}
class StoragePartitionImplSharedStorageTest : public StoragePartitionImplTest {
public:
StoragePartitionImplSharedStorageTest()
: storage_partition_(browser_context()->GetDefaultStoragePartition()),
shared_storage_manager_(
static_cast<StoragePartitionImpl*>(storage_partition_)
->GetSharedStorageManager()) {
feature_list_.InitWithFeatures({blink::features::kInterestGroupStorage,
blink::features::kSharedStorageAPI},
{});
}
StoragePartitionImplSharedStorageTest(
const StoragePartitionImplSharedStorageTest&) = delete;
StoragePartitionImplSharedStorageTest& operator=(
const StoragePartitionImplSharedStorageTest&) = delete;
~StoragePartitionImplSharedStorageTest() override {
task_environment()->RunUntilIdle();
}
scoped_refptr<storage::SpecialStoragePolicy> GetSpecialStoragePolicy() {
return base::WrapRefCounted<storage::SpecialStoragePolicy>(
static_cast<content::StoragePartitionImpl*>(storage_partition_)
->browser_context()
->GetSpecialStoragePolicy());
}
// Returns true, if the given origin URL exists.
bool SharedStorageExistsForOrigin(const url::Origin& origin) {
for (const auto& info : GetSharedStorageUsage()) {
if (origin == info->origin)
return true;
}
return false;
}
void AddSharedStorageTestData(const url::Origin& origin1,
const url::Origin& origin2,
const url::Origin& origin3) {
base::FilePath path =
storage_partition_->GetPath().Append(storage::kSharedStoragePath);
std::unique_ptr<storage::AsyncSharedStorageDatabase> database =
storage::AsyncSharedStorageDatabaseImpl::Create(
path,
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
GetSpecialStoragePolicy(),
storage::SharedStorageOptions::Create()->GetDatabaseOptions());
base::test::TestFuture<bool> future;
DCHECK(database);
DCHECK(static_cast<storage::AsyncSharedStorageDatabaseImpl*>(database.get())
->GetSequenceBoundDatabaseForTesting());
static_cast<storage::AsyncSharedStorageDatabaseImpl*>(database.get())
->GetSequenceBoundDatabaseForTesting()
->AsyncCall(&storage::SharedStorageDatabase::PopulateDatabaseForTesting)
.WithArgs(origin1, origin2, origin3)
.Then(future.GetCallback());
EXPECT_TRUE(future.Get());
// Ensure that this database is fully closed before checking for existence.
database.reset();
task_environment()->RunUntilIdle();
EXPECT_TRUE(SharedStorageExistsForOrigin(origin1));
EXPECT_TRUE(SharedStorageExistsForOrigin(origin2));
EXPECT_TRUE(SharedStorageExistsForOrigin(origin3));
task_environment()->RunUntilIdle();
}
private:
std::vector<storage::mojom::StorageUsageInfoPtr> GetSharedStorageUsage() {
DCHECK(shared_storage_manager_);
base::test::TestFuture<std::vector<storage::mojom::StorageUsageInfoPtr>>
future;
shared_storage_manager_->FetchOrigins(future.GetCallback());
return future.Take();
}
base::test::ScopedFeatureList feature_list_;
// We don't own these pointers.
StoragePartition* const storage_partition_;
storage::SharedStorageManager* shared_storage_manager_;
};
TEST_F(StoragePartitionImplSharedStorageTest,
RemoveUnprotectedSharedStorageForever) {
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());
AddSharedStorageTestData(kOrigin1, kOrigin2, kOrigin3);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->GetSharedStorageManager()->OverrideSpecialStoragePolicyForTesting(
mock_policy.get());
base::RunLoop clear_run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearStuff,
StoragePartitionImpl::REMOVE_DATA_MASK_SHARED_STORAGE,
partition, base::Time(), base::Time::Max(),
base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
&clear_run_loop));
clear_run_loop.Run();
// ClearData only guarantees that tasks to delete data are scheduled when its
// callback is invoked. It doesn't guarantee data has actually been cleared.
// So run all scheduled tasks to make sure data is cleared.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(SharedStorageExistsForOrigin(kOrigin1));
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin2));
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin3));
}
TEST_F(StoragePartitionImplSharedStorageTest,
RemoveProtectedSharedStorageForever) {
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());
AddSharedStorageTestData(kOrigin1, kOrigin2, kOrigin3);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
partition->GetSharedStorageManager()->OverrideSpecialStoragePolicyForTesting(
mock_policy.get());
base::RunLoop clear_run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearStuff,
StoragePartitionImpl::REMOVE_DATA_MASK_SHARED_STORAGE,
partition, base::Time(), base::Time::Max(),
base::BindRepeating(
&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
&clear_run_loop));
clear_run_loop.Run();
// ClearData only guarantees that tasks to delete data are scheduled when its
// callback is invoked. It doesn't guarantee data has actually been cleared.
// So run all scheduled tasks to make sure data is cleared.
base::RunLoop().RunUntilIdle();
// Even if kOrigin1 is protected, it will be deleted since we specify
// ClearData to delete protected data.
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin1));
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin2));
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin3));
}
TEST_F(StoragePartitionImplSharedStorageTest, RemoveSharedStorageForLastWeek) {
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/"));
AddSharedStorageTestData(kOrigin1, kOrigin2, kOrigin3);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition());
DCHECK(partition);
base::Time a_week_ago = base::Time::Now() - base::Days(7);
base::RunLoop clear_run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ClearStuff,
StoragePartitionImpl::REMOVE_DATA_MASK_SHARED_STORAGE,
partition, a_week_ago, base::Time::Max(),
base::BindRepeating(
&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
&clear_run_loop));
clear_run_loop.Run();
// ClearData only guarantees that tasks to delete data are scheduled when its
// callback is invoked. It doesn't guarantee data has actually been cleared.
// So run all scheduled tasks to make sure data is cleared.
base::RunLoop().RunUntilIdle();
// kOrigin1 and kOrigin2 do not have age more than a week.
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin1));
EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin2));
EXPECT_TRUE(SharedStorageExistsForOrigin(kOrigin3));
}
} // namespace content