#include "services/network/shared_dictionary/shared_dictionary_manager.h"
#include <optional>
#include <string>
#include <vector>
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/format_macros.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "crypto/hash.h"
#include "net/base/hash_value.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/disk_cache_test_util.h"
#include "net/extras/shared_dictionary/shared_dictionary_info.h"
#include "net/extras/shared_dictionary/shared_dictionary_usage_info.h"
#include "net/http/http_response_headers.h"
#include "net/shared_dictionary/shared_dictionary.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/shared_dictionary_error.mojom.h"
#include "services/network/shared_dictionary/shared_dictionary_constants.h"
#include "services/network/shared_dictionary/shared_dictionary_disk_cache.h"
#include "services/network/shared_dictionary/shared_dictionary_in_memory.h"
#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
#include "services/network/shared_dictionary/shared_dictionary_storage.h"
#include "services/network/shared_dictionary/shared_dictionary_storage_in_memory.h"
#include "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h"
#include "services/network/shared_dictionary/shared_dictionary_writer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
using ::testing::UnorderedElementsAreArray;
namespace network {
namespace {
enum class TestManagerType {
kInMemory,
kOnDisk,
};
const GURL kUrl1("https://origin1.test/");
const GURL kUrl2("https://origin2.test/");
const GURL kUrl3("https://origin3.test/");
const net::SchemefulSite kSite1(kUrl1);
const net::SchemefulSite kSite2(kUrl2);
const net::SchemefulSite kSite3(kUrl3);
const std::string kTestData1 = "Hello world";
const std::string kTestData2 = "Bonjour le monde";
const net::SHA256HashValue kTestData1Hash = {
{0x64, 0xec, 0x88, 0xca, 0x00, 0xb2, 0x68, 0xe5, 0xba, 0x1a, 0x35,
0x67, 0x8a, 0x1b, 0x53, 0x16, 0xd2, 0x12, 0xf4, 0xf3, 0x66, 0xb2,
0x47, 0x72, 0x32, 0x53, 0x4a, 0x8a, 0xec, 0xa3, 0x7f, 0x3c}};
const net::SHA256HashValue kTestData2Hash = {
{0x4d, 0xc4, 0x5b, 0x5e, 0xd3, 0xde, 0x20, 0x2a, 0x56, 0x93, 0xc9,
0x26, 0xca, 0xf9, 0x5b, 0xd9, 0x71, 0x0b, 0xef, 0x4f, 0xe5, 0xfb,
0x16, 0xa1, 0xc2, 0x4a, 0x08, 0xed, 0x42, 0x8e, 0x8a, 0xe0}};
const size_t kCacheMaxCount = 100;
const std::string kDefaultCacheControlHeader =
"cache-control: max-age=2592000\n";
const base::TimeDelta kDefaultExpiration(base::Seconds(2592000));
std::string ToString(TestManagerType type) {
switch (type) {
case TestManagerType::kInMemory:
return "InMemory";
case TestManagerType::kOnDisk:
return "OnDisk";
}
}
void CheckDiskCacheEntryDataEquals(
SharedDictionaryDiskCache& disk_cache,
const base::UnguessableToken& disk_cache_key_token,
const std::string& expected_data) {
TestEntryResultCompletionCallback open_callback;
disk_cache::EntryResult open_result = open_callback.GetResult(
disk_cache.OpenOrCreateEntry(disk_cache_key_token.ToString(),
false, open_callback.callback()));
EXPECT_EQ(net::OK, open_result.net_error());
disk_cache::ScopedEntryPtr entry;
entry.reset(open_result.ReleaseEntry());
ASSERT_TRUE(entry);
EXPECT_EQ(base::checked_cast<int32_t>(expected_data.size()),
entry->GetDataSize(1));
scoped_refptr<net::IOBufferWithSize> read_buffer =
base::MakeRefCounted<net::IOBufferWithSize>(expected_data.size());
net::TestCompletionCallback read_callback;
EXPECT_EQ(read_buffer->size(),
read_callback.GetResult(entry->ReadData(
1, 0, read_buffer.get(),
expected_data.size(), read_callback.callback())));
EXPECT_EQ(expected_data,
std::string(reinterpret_cast<const char*>(read_buffer->data()),
read_buffer->size()));
}
void WriteDictionary(
SharedDictionaryStorage* storage,
const GURL& dictionary_url,
const std::string& match,
const std::vector<std::string>& data_list,
const std::string& additional_options = std::string(),
const std::string& additional_header = kDefaultCacheControlHeader) {
const std::string use_as_dictionary_header =
base::StrCat({"match=\"/", match, "\"", additional_options});
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\n", additional_header, "\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage, mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindOnce([]() {
return true;
}));
ASSERT_TRUE(writer.has_value());
ASSERT_TRUE(*writer);
for (const std::string& data : data_list) {
(*writer)->Append(base::as_byte_span(data));
}
(*writer)->Finish();
}
}
class SharedDictionaryManagerTest
: public ::testing::Test,
public ::testing::WithParamInterface<TestManagerType> {
public:
SharedDictionaryManagerTest() = default;
~SharedDictionaryManagerTest() override = default;
SharedDictionaryManagerTest(const SharedDictionaryManagerTest&) = delete;
SharedDictionaryManagerTest& operator=(const SharedDictionaryManagerTest&) =
delete;
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
network::features::kCompressionDictionaryTTL);
if (GetManagerType() == TestManagerType::kOnDisk) {
ASSERT_TRUE(tmp_directory_.CreateUniqueTempDir());
database_path_ = tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("db"));
cache_directory_path_ =
tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("cache"));
}
}
void TearDown() override {
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
}
protected:
TestManagerType GetManagerType() const { return GetParam(); }
std::unique_ptr<SharedDictionaryManager> CreateSharedDictionaryManager() {
switch (GetManagerType()) {
case TestManagerType::kInMemory:
return SharedDictionaryManager::CreateInMemory(0,
kCacheMaxCount);
case TestManagerType::kOnDisk:
return SharedDictionaryManager::CreateOnDisk(
database_path_, cache_directory_path_, 0,
kCacheMaxCount,
#if BUILDFLAG(IS_ANDROID)
disk_cache::ApplicationStatusListenerGetter(),
#endif
nullptr);
}
}
const std::map<
url::SchemeHostPort,
std::map<std::tuple<std::string, std::set<mojom::RequestDestination>>,
SharedDictionaryStorageInMemory::DictionaryInfo>>&
GetInMemoryDictionaryMap(SharedDictionaryStorage* storage) {
return static_cast<SharedDictionaryStorageInMemory*>(storage)
->GetDictionaryMap();
}
const std::map<
url::SchemeHostPort,
std::map<std::tuple<std::string, std::set<mojom::RequestDestination>>,
SharedDictionaryStorageOnDisk::WrappedDictionaryInfo>>&
GetOnDiskDictionaryMap(SharedDictionaryStorage* storage) {
return static_cast<SharedDictionaryStorageOnDisk*>(storage)
->GetDictionaryMapForTesting();
}
void FlushCacheTasks() {
disk_cache::FlushCacheThreadForTesting();
task_environment_.RunUntilIdle();
}
std::vector<network::mojom::SharedDictionaryInfoPtr> GetSharedDictionaryInfo(
SharedDictionaryManager* manager,
const net::SharedDictionaryIsolationKey& isolation_key) {
base::test::TestFuture<std::vector<network::mojom::SharedDictionaryInfoPtr>>
result;
manager->GetSharedDictionaryInfo(isolation_key, result.GetCallback());
return result.Take();
}
std::vector<url::Origin> GetOriginsBetween(SharedDictionaryManager* manager,
base::Time start_time,
base::Time end_time) {
base::test::TestFuture<const std::vector<url::Origin>&> result;
manager->GetOriginsBetween(start_time, end_time, result.GetCallback());
return result.Get();
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
private:
base::ScopedTempDir tmp_directory_;
base::FilePath database_path_;
base::FilePath cache_directory_path_;
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SharedDictionaryManagerTest,
testing::ValuesIn({TestManagerType::kInMemory, TestManagerType::kOnDisk}),
[](const testing::TestParamInfo<TestManagerType>& info) {
return ToString(info.param);
});
TEST_P(SharedDictionaryManagerTest, SameStorageForSameIsolationKey) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key1(url::Origin::Create(kUrl1),
kSite1);
net::SharedDictionaryIsolationKey isolation_key2(url::Origin::Create(kUrl1),
kSite1);
EXPECT_EQ(isolation_key1, isolation_key1);
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
EXPECT_TRUE(storage1);
EXPECT_TRUE(storage2);
EXPECT_EQ(storage1.get(), storage2.get());
}
TEST_P(SharedDictionaryManagerTest, DifferentStorageForDifferentIsolationKey) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key1(url::Origin::Create(kUrl1),
kSite1);
net::SharedDictionaryIsolationKey isolation_key2(url::Origin::Create(kUrl2),
kSite2);
EXPECT_NE(isolation_key1, isolation_key2);
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
EXPECT_TRUE(storage1);
EXPECT_TRUE(storage2);
EXPECT_NE(storage1.get(), storage2.get());
}
TEST_P(SharedDictionaryManagerTest, CachedStorage) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
storage = manager->GetStorage(isolation_key);
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, CachedStorageEvicted) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
for (int i = 0; i < 9; ++i) {
const GURL url = GURL(base::StringPrintf("https://test%d.test", i));
scoped_refptr<SharedDictionaryStorage> tmp_storage =
manager->GetStorage(net::SharedDictionaryIsolationKey(
url::Origin::Create(url), net::SchemefulSite(url)));
EXPECT_TRUE(tmp_storage);
}
storage = manager->GetStorage(isolation_key);
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
for (int i = 0; i < 10; ++i) {
const GURL url = GURL(base::StringPrintf("https://test%d.test", i));
scoped_refptr<SharedDictionaryStorage> tmp_storage =
manager->GetStorage(net::SharedDictionaryIsolationKey(
url::Origin::Create(url), net::SchemefulSite(url)));
EXPECT_TRUE(tmp_storage);
}
storage = manager->GetStorage(isolation_key);
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest,
StorageNotCachedWithModerateMemoryPressure) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
base::MemoryPressureListener::SimulatePressureNotificationAsync(
base::MEMORY_PRESSURE_LEVEL_MODERATE);
task_environment_.RunUntilIdle();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
storage = manager->GetStorage(isolation_key);
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest,
StorageNotCachedWithCriticalMemoryPressure) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
base::MemoryPressureListener::SimulatePressureNotificationAsync(
base::MEMORY_PRESSURE_LEVEL_CRITICAL);
task_environment_.RunUntilIdle();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
storage = manager->GetStorage(isolation_key);
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest,
CachedStorageClearedOnModerateMemoryPressure) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
base::MemoryPressureListener::SimulatePressureNotificationAsync(
base::MEMORY_PRESSURE_LEVEL_MODERATE);
task_environment_.RunUntilIdle();
storage = manager->GetStorage(isolation_key);
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest,
CachedStorageClearedOnCriticalMemoryPressure) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
storage.reset();
base::MemoryPressureListener::SimulatePressureNotificationAsync(
base::MEMORY_PRESSURE_LEVEL_CRITICAL);
task_environment_.RunUntilIdle();
storage = manager->GetStorage(isolation_key);
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, WriterForUseAsDictionaryHeader) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
struct {
std::string header_string;
std::optional<mojom::SharedDictionaryError> error_status;
} kTestCases[] = {
{"", mojom::SharedDictionaryError::kWriteErrorNoMatchField},
{"()", mojom::SharedDictionaryError::kWriteErrorInvalidStructuredHeader},
{"dummy", mojom::SharedDictionaryError::kWriteErrorNoMatchField},
{"match=\"/test\"", std::nullopt},
{"match=\"test\"", std::nullopt},
{"match=(\"test1\" \"test2\")",
mojom::SharedDictionaryError::kWriteErrorNonStringMatchField},
{"match=test",
mojom::SharedDictionaryError::kWriteErrorNonStringMatchField},
{"match=\"test\", type=raw", std::nullopt},
{"match=\"test\", type=(raw)", std::nullopt},
{"match=\"test\", type=\"raw\"",
mojom::SharedDictionaryError::kWriteErrorNonTokenTypeField},
{"match=\"test\", type=other",
mojom::SharedDictionaryError::kWriteErrorUnsupportedType},
{"match=\"test\", type=(raw, rawx)",
mojom::SharedDictionaryError::kWriteErrorInvalidStructuredHeader},
};
for (const auto& testcase : kTestCases) {
SCOPED_TRACE(base::StringPrintf("header_string: %s",
testcase.header_string.c_str()));
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", testcase.header_string, "\n", kDefaultCacheControlHeader,
"\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
testcase.header_string, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindOnce([]() {
return true;
}));
if (testcase.error_status.has_value()) {
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(testcase.error_status.value(), writer.error());
} else {
ASSERT_TRUE(writer.has_value());
ASSERT_TRUE(*writer);
}
}
}
TEST_P(SharedDictionaryManagerTest, DictionaryLifetimeFromCacheControlHeader) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
struct {
std::string header_string;
std::optional<base::TimeDelta> expected_expiration;
} kTestCases[] = {
{"", std::nullopt},
{"cache-control:max-age=100", base::Seconds(100)},
{"cache-control:max-age=100, stale-while-revalidate=50",
base::Seconds(150)},
{"cache-control:max-age=100\nage:10", base::Seconds(90)},
};
for (const auto& testcase : kTestCases) {
SCOPED_TRACE(base::StringPrintf("header_string: %s",
testcase.header_string.c_str()));
const std::string use_as_dictionary_header = "match=\"/test\"";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\n", testcase.header_string,
"\n"}));
ASSERT_TRUE(headers);
const base::Time request_time = base::Time::Now();
const base::Time response_time = request_time;
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"), request_time,
response_time, *headers,
false,
base::BindOnce([]() {
return true;
}));
if (!testcase.expected_expiration) {
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(mojom::SharedDictionaryError::kWriteErrorExpiredResponse,
writer.error());
continue;
}
ASSERT_TRUE(writer.has_value());
ASSERT_TRUE(*writer);
(*writer)->Append(base::as_byte_span(kTestData1));
(*writer)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
std::vector<network::mojom::SharedDictionaryInfoPtr> result =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, result.size());
EXPECT_EQ(*testcase.expected_expiration, result[0]->expiration);
}
}
TEST_P(SharedDictionaryManagerTest, DictionaryLifetimeFromTTLOption) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
struct {
std::string header_string;
base::expected<base::TimeDelta, mojom::SharedDictionaryError>
expected_expiration_or_error_status;
} kTestCases[] = {
{"match=\"test\"", kDefaultExpiration},
{"match=\"test\", ttl=100", base::Seconds(100)},
{"match=\"test\", ttl=token",
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorNonIntegerTTLField)},
{"match=\"test\", ttl=-1",
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorInvalidTTLField)},
{"match=\"test\", ttl=0",
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorInvalidTTLField)},
};
for (const auto& testcase : kTestCases) {
SCOPED_TRACE(base::StringPrintf("header_string: %s",
testcase.header_string.c_str()));
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", testcase.header_string, "\n", kDefaultCacheControlHeader,
"\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
testcase.header_string, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindOnce([]() {
return true;
}));
if (!testcase.expected_expiration_or_error_status.has_value()) {
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(writer.error(),
testcase.expected_expiration_or_error_status.error());
continue;
}
ASSERT_TRUE(writer.has_value());
ASSERT_TRUE(*writer);
(*writer)->Append(base::as_byte_span(kTestData1));
(*writer)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
continue;
}
std::vector<network::mojom::SharedDictionaryInfoPtr> result =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, result.size());
EXPECT_EQ(*testcase.expected_expiration_or_error_status,
result[0]->expiration);
}
}
TEST_P(SharedDictionaryManagerTest,
DictionaryLifetimeNotExtendedOnFetchWithoutTTL) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
GURL dictionary_url = GURL("https://origin1.test/testfile.txt");
base::Time response_time = base::Time::Now();
const std::string use_as_dictionary_header = "match=\"/test\"";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer1 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers,
false,
base::BindLambdaForTesting([&]() {
return true;
}));
ASSERT_TRUE(writer1.has_value());
ASSERT_TRUE(*writer1);
(*writer1)->Append(base::as_byte_span(kTestData1));
(*writer1)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
base::Time last_fetch_time = base::Time::Now();
std::vector<network::mojom::SharedDictionaryInfoPtr> before =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, before.size());
EXPECT_EQ(response_time, before[0]->response_time);
EXPECT_NE(last_fetch_time, before[0]->response_time);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer2 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers,
true,
base::BindLambdaForTesting([&]() {
return true;
}));
EXPECT_FALSE(writer2.has_value());
EXPECT_EQ(writer2.error(),
mojom::SharedDictionaryError::kWriteErrorAlreadyRegistered);
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
std::vector<network::mojom::SharedDictionaryInfoPtr> after =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, after.size());
EXPECT_EQ(response_time, before[0]->response_time);
EXPECT_NE(last_fetch_time, before[0]->response_time);
}
TEST_P(SharedDictionaryManagerTest, DictionaryLifetimeExtendedOnFetchWithTTL) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
GURL dictionary_url = GURL("https://origin1.test/testfile.txt");
base::Time response_time = base::Time::Now();
const std::string use_as_dictionary_header = "match=\"/test\", ttl=100";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer1 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers,
false,
base::BindLambdaForTesting([&]() {
return true;
}));
ASSERT_TRUE(writer1.has_value());
ASSERT_TRUE(*writer1);
(*writer1)->Append(base::as_byte_span(kTestData1));
(*writer1)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
base::Time last_fetch_time = base::Time::Now();
std::vector<network::mojom::SharedDictionaryInfoPtr> before =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, before.size());
EXPECT_EQ(response_time, before[0]->response_time);
EXPECT_NE(last_fetch_time, before[0]->response_time);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer2 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers,
true,
base::BindLambdaForTesting([&]() {
return true;
}));
EXPECT_FALSE(writer2.has_value());
EXPECT_EQ(writer2.error(),
mojom::SharedDictionaryError::kWriteErrorAlreadyRegistered);
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
std::vector<network::mojom::SharedDictionaryInfoPtr> after =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, after.size());
EXPECT_NE(response_time, after[0]->response_time);
EXPECT_EQ(last_fetch_time, after[0]->response_time);
}
TEST_P(SharedDictionaryManagerTest, WriterForUseAsDictionaryIdOption) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
struct {
std::string header_string;
base::expected<std::string, mojom::SharedDictionaryError>
expected_id_or_error_status;
} kTestCases[] = {
{"match=\"test\", id=\"test_id\"", "test_id"},
{"match=\"test\", id=\"test\\\\id\"", "test\\id"},
{"match=\"test\", id=\"test\\\"id\"", "test\"id"},
{"match=\"test\", id=(\"id1\" \"id2\")",
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorNonStringIdField)},
{base::StrCat({"match=\"test\", id=\"", std::string(1024, 'x'), "\""}),
std::string(1024, 'x')},
{base::StrCat({"match=\"test\", id=\"", std::string(1025, 'x'), "\""}),
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorTooLongIdField)},
};
for (const auto& testcase : kTestCases) {
SCOPED_TRACE(base::StringPrintf("header_string: %s",
testcase.header_string.c_str()));
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", testcase.header_string, "\n", kDefaultCacheControlHeader,
"\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
testcase.header_string, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindOnce([]() {
return true;
}));
if (!testcase.expected_id_or_error_status.has_value()) {
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(writer.error(), testcase.expected_id_or_error_status.error());
continue;
}
ASSERT_TRUE(writer.has_value());
ASSERT_TRUE(*writer);
(*writer)->Append(base::as_byte_span(kTestData1));
(*writer)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
continue;
}
std::vector<network::mojom::SharedDictionaryInfoPtr> result =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, result.size());
EXPECT_EQ(*testcase.expected_id_or_error_status, result[0]->id);
}
}
TEST_P(SharedDictionaryManagerTest, WriterForUseAsDictionaryMatchDestOption) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
struct {
std::string header_string;
base::expected<std::vector<mojom::RequestDestination>,
mojom::SharedDictionaryError>
expected_match_dest_or_error_status;
} kTestCases[] = {
{"match=\"test\"", {}},
{"match=\"test\", match-dest=(\"document\")",
std::vector<mojom::RequestDestination>(
{mojom::RequestDestination::kDocument})},
{"match=\"test\", match-dest=\"document\"",
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorNonListMatchDestField)},
{"match=\"test\", match-dest=(\"unknown\")", {}},
{"match=\"test\", match-dest=(document)",
base::unexpected(
mojom::SharedDictionaryError::kWriteErrorNonStringInMatchDestList)},
{"match=\"test\", match-dest=(\"\")",
std::vector<mojom::RequestDestination>(
{mojom::RequestDestination::kEmpty})},
{"match=\"test\", match-dest=(\"document\" \"frame\" \"iframe\")",
std::vector<mojom::RequestDestination>(
{mojom::RequestDestination::kDocument,
mojom::RequestDestination::kFrame,
mojom::RequestDestination::kIframe})},
{"match=\"test\", match-dest=(\"document\" \"\")",
std::vector<mojom::RequestDestination>(
{mojom::RequestDestination::kEmpty,
mojom::RequestDestination::kDocument})}};
for (const auto& testcase : kTestCases) {
base::RunLoop run_loop;
manager->ClearDataForIsolationKey(isolation_key, run_loop.QuitClosure());
run_loop.Run();
SCOPED_TRACE(base::StringPrintf("header_string: %s",
testcase.header_string.c_str()));
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", testcase.header_string, "\n", kDefaultCacheControlHeader,
"\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
testcase.header_string, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindOnce([]() {
return true;
}));
if (!testcase.expected_match_dest_or_error_status.has_value()) {
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(writer.error(),
testcase.expected_match_dest_or_error_status.error());
continue;
}
ASSERT_TRUE(writer.has_value());
ASSERT_TRUE(*writer);
(*writer)->Append(base::as_byte_span(kTestData1));
(*writer)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
continue;
}
std::vector<network::mojom::SharedDictionaryInfoPtr> result =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, result.size());
EXPECT_EQ(*testcase.expected_match_dest_or_error_status,
result[0]->match_dest);
}
}
TEST_P(SharedDictionaryManagerTest, InvalidMatch) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
std::string kTestCases[] = {
"{",
"(a|b)",
};
for (const auto& testcase : kTestCases) {
SCOPED_TRACE(base::StringPrintf("match: %s", testcase.c_str()));
const std::string use_as_dictionary_header =
base::StrCat({"match=\"/", testcase, "\""});
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\n",
"cache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindOnce([]() {
return true;
}));
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(writer.error(),
mojom::SharedDictionaryError::kWriteErrorInvalidMatchField);
}
}
TEST_P(SharedDictionaryManagerTest, AccessAllowedCheckReturnTrue) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
const std::string use_as_dictionary_header = "match=\"/test\"";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers);
bool callback_called = false;
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindLambdaForTesting([&]() {
callback_called = true;
return true;
}));
EXPECT_TRUE(writer.has_value());
EXPECT_TRUE(*writer);
EXPECT_TRUE(callback_called);
}
TEST_P(SharedDictionaryManagerTest, AccessAllowedCheckReturnFalse) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
const std::string use_as_dictionary_header = "match=\"/test\"";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers);
bool callback_called = false;
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic,
GURL("https://origin1.test/testfile.txt"),
base::Time::Now(),
base::Time::Now(), *headers,
false,
base::BindLambdaForTesting([&]() {
callback_called = true;
return false;
}));
EXPECT_FALSE(writer.has_value());
EXPECT_EQ(writer.error(),
mojom::SharedDictionaryError::kWriteErrorDisallowedBySettings);
EXPECT_TRUE(callback_called);
}
TEST_P(SharedDictionaryManagerTest, SameDictionaryFromDiskCache) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
GURL dictionary_url = GURL("https://origin1.test/testfile.txt");
base::Time response_time = base::Time::Now();
const std::string use_as_dictionary_header = "match=\"/test\"";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer1 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers,
false,
base::BindLambdaForTesting([&]() {
return true;
}));
ASSERT_TRUE(writer1.has_value());
ASSERT_TRUE(*writer1);
(*writer1)->Append(base::as_byte_span(kTestData1));
(*writer1)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer2 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers,
true,
base::BindLambdaForTesting([&]() {
return true;
}));
EXPECT_FALSE(writer2.has_value());
EXPECT_EQ(writer2.error(),
mojom::SharedDictionaryError::kWriteErrorAlreadyRegistered);
}
TEST_P(SharedDictionaryManagerTest, DifferentDictionaryFromDiskCache) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
GURL dictionary_url = GURL("https://origin1.test/testfile.txt");
base::Time response_time = base::Time::Now();
const std::string use_as_dictionary_header1 = "match=\"/test1\"";
scoped_refptr<net::HttpResponseHeaders> headers1 =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header1,
"\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers1);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer1 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header1, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers1,
false,
base::BindLambdaForTesting([&]() {
return true;
}));
ASSERT_TRUE(writer1.has_value());
ASSERT_TRUE(*writer1);
(*writer1)->Append(base::as_byte_span(kTestData1));
(*writer1)->Finish();
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
const std::string use_as_dictionary_header2 = "match=\"/test2\"";
scoped_refptr<net::HttpResponseHeaders> headers2 =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header2,
"\ncache-control:max-age=100\n\n"}));
ASSERT_TRUE(headers1);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer2 = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header2, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, dictionary_url,
response_time,
response_time, *headers2,
true,
base::BindLambdaForTesting([&]() {
return true;
}));
EXPECT_TRUE(writer2.has_value());
}
TEST_P(SharedDictionaryManagerTest, WriteAndGetDictionary) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*",
{"hello world"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin1.test/testfile"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin2.test/testfile"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/test"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, WriteAndReadDictionary) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
base::Time now_time = base::Time::Now();
const std::string data1 = "hello ";
const std::string data2 = "world";
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*",
{data1, data2});
crypto::hash::Hasher hasher(crypto::hash::kSha256);
hasher.Update(data1);
hasher.Update(data2);
net::SHA256HashValue sha256;
hasher.Finish(sha256);
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
scoped_refptr<net::SharedDictionary> dict =
storage->GetDictionarySync(GURL("https://origin1.test/testfile?hello"),
mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dict);
EXPECT_EQ(data1.size() + data2.size(), dict->size());
EXPECT_EQ(sha256, dict->hash());
switch (GetManagerType()) {
case TestManagerType::kInMemory: {
EXPECT_EQ(net::OK,
dict->ReadAll(base::BindOnce([](int rv) { NOTREACHED(); })));
break;
}
case TestManagerType::kOnDisk: {
base::RunLoop run_loop;
EXPECT_EQ(net::ERR_IO_PENDING,
dict->ReadAll(base::BindLambdaForTesting([&](int rv) {
EXPECT_EQ(net::OK, rv);
run_loop.Quit();
})));
run_loop.Run();
break;
}
}
ASSERT_TRUE(dict->data());
EXPECT_EQ(data1 + data2, std::string(dict->data()->data(), dict->size()));
switch (GetManagerType()) {
case TestManagerType::kInMemory: {
const auto& dictionary_map = GetInMemoryDictionaryMap(storage.get());
EXPECT_EQ(1u, dictionary_map.size());
EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")),
dictionary_map.begin()->first);
EXPECT_EQ(1u, dictionary_map.begin()->second.size());
EXPECT_EQ(
std::make_tuple("/testfile*", std::set<mojom::RequestDestination>()),
dictionary_map.begin()->second.begin()->first);
const auto& dictionary_info =
dictionary_map.begin()->second.begin()->second;
EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url());
EXPECT_EQ(now_time, dictionary_info.response_time());
EXPECT_EQ(kDefaultExpiration, dictionary_info.expiration());
EXPECT_EQ("/testfile*", dictionary_info.match());
EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size());
EXPECT_EQ(net::OK, dictionary_info.dictionary()->ReadAll(
base::BindOnce([](int) { NOTREACHED(); })));
EXPECT_EQ(data1 + data2,
std::string(dictionary_info.dictionary()->data()->data(),
dictionary_info.size()));
EXPECT_EQ(sha256, dictionary_info.dictionary()->hash());
break;
}
case TestManagerType::kOnDisk: {
const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get());
EXPECT_EQ(1u, dictionary_map.size());
EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")),
dictionary_map.begin()->first);
EXPECT_EQ(1u, dictionary_map.begin()->second.size());
EXPECT_EQ(
std::make_tuple("/testfile*", std::set<mojom::RequestDestination>()),
dictionary_map.begin()->second.begin()->first);
const auto& dictionary_info =
dictionary_map.begin()->second.begin()->second;
EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url());
EXPECT_EQ(now_time, dictionary_info.response_time());
EXPECT_EQ(kDefaultExpiration, dictionary_info.expiration());
EXPECT_EQ("/testfile*", dictionary_info.match());
EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size());
CheckDiskCacheEntryDataEquals(
static_cast<SharedDictionaryManagerOnDisk*>(manager.get())
->disk_cache(),
dictionary_info.disk_cache_key_token(), data1 + data2);
EXPECT_EQ(sha256, dictionary_info.hash());
break;
}
}
}
TEST_P(SharedDictionaryManagerTest, LongestMatchDictionaryWin) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "*estfile*",
{"Longer match"});
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "test*",
{"Shorter match"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
auto dict = storage->GetDictionarySync(GURL("https://origin1.test/testfile"),
mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dict);
net::TestCompletionCallback read_callback;
EXPECT_EQ(net::OK,
read_callback.GetResult(dict->ReadAll(read_callback.callback())));
EXPECT_EQ("Longer match", std::string(dict->data()->data(), dict->size()));
}
TEST_P(SharedDictionaryManagerTest, LastFetchedDictionaryWin) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
base::Time first_dictionary_time = base::Time::Now();
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "test*",
{"Dict 1"});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "*est*",
{"Dict 2"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
{
auto dict =
storage->GetDictionarySync(GURL("https://origin1.test/testfile"),
mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dict);
net::TestCompletionCallback read_callback;
EXPECT_EQ(net::OK,
read_callback.GetResult(dict->ReadAll(read_callback.callback())));
EXPECT_EQ("Dict 2", std::string(dict->data()->data(), dict->size()));
}
task_environment_.FastForwardBy(base::Seconds(1));
const std::string use_as_dictionary_header = "match=\"/test*\"";
scoped_refptr<net::HttpResponseHeaders> headers =
net::HttpResponseHeaders::TryToCreate(base::StrCat(
{"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName,
": ", use_as_dictionary_header, "\n", kDefaultCacheControlHeader,
"\n"}));
ASSERT_TRUE(headers);
base::expected<scoped_refptr<SharedDictionaryWriter>,
mojom::SharedDictionaryError>
writer_or_error = SharedDictionaryStorage::MaybeCreateWriter(
use_as_dictionary_header, true,
storage.get(), mojom::RequestMode::kSameOrigin,
mojom::FetchResponseType::kBasic, GURL("https://origin1.test/dict"),
first_dictionary_time, first_dictionary_time, *headers,
true,
base::BindOnce([]() {
return true;
}));
ASSERT_FALSE(writer_or_error.has_value());
EXPECT_EQ(mojom::SharedDictionaryError::kWriteErrorAlreadyRegistered,
writer_or_error.error());
{
auto dict =
storage->GetDictionarySync(GURL("https://origin1.test/testfile"),
mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dict);
net::TestCompletionCallback read_callback;
EXPECT_EQ(net::OK,
read_callback.GetResult(dict->ReadAll(read_callback.callback())));
EXPECT_EQ("Dict 1", std::string(dict->data()->data(), dict->size()));
}
}
TEST_P(SharedDictionaryManagerTest, OverrideDictionary) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
const GURL url1 = GURL("https://origin1.test/dict1");
const GURL url2 = GURL("https://origin1.test/dict2");
const std::string match = "path*";
const std::string data1 = "hello";
const std::string data2 = "world";
WriteDictionary(storage.get(), url1, match, {data1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
std::vector<network::mojom::SharedDictionaryInfoPtr> result1 =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, result1.size());
EXPECT_EQ(url1, result1[0]->dictionary_url);
WriteDictionary(storage.get(), url2, match, {data2});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
std::vector<network::mojom::SharedDictionaryInfoPtr> result2 =
GetSharedDictionaryInfo(manager.get(), isolation_key);
ASSERT_EQ(1u, result2.size());
EXPECT_EQ(url2, result2[0]->dictionary_url);
}
TEST_P(SharedDictionaryManagerTest, ZeroSizeDictionaryShouldNotBeStored) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*",
{});
scoped_refptr<net::SharedDictionary> dict =
storage->GetDictionarySync(GURL("https://origin1.test/testfile?hello"),
mojom::RequestDestination::kEmpty);
EXPECT_FALSE(dict);
}
TEST_P(SharedDictionaryManagerTest,
CacheEvictionSizeExceededOnSetCacheMaxSize) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
WriteDictionary(storage.get(), GURL("https://origin1.test/d1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage.get(), GURL("https://origin2.test/d2"), "p2*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage.get(), GURL("https://origin3.test/d1"), "p3*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
manager->SetCacheMaxSize(kTestData1.size() * 2);
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin1.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin2.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin3.test/p3?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, CacheEvictionZeroMaxSizeCountExceeded) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
std::vector<scoped_refptr<SharedDictionaryStorage>> storages;
for (size_t i = 0; i < kCacheMaxCount; ++i) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(
GURL(base::StringPrintf("https://origind%03" PRIuS ".test", i))),
net::SchemefulSite(
GURL(base::StringPrintf("https://origind%03" PRIuS ".test", i))));
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
storages.push_back(storage);
WriteDictionary(
storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS, i)),
base::StringPrintf("p%03" PRIuS, i), {kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
}
for (size_t i = 0; i < kCacheMaxCount; ++i) {
EXPECT_TRUE(storages[i]->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
}
{
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL(base::StringPrintf(
"https://origind%03" PRIuS ".test", kCacheMaxCount))),
net::SchemefulSite(GURL(base::StringPrintf(
"https://origind%03" PRIuS ".test", kCacheMaxCount))));
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
storages.push_back(storage);
WriteDictionary(storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS,
kCacheMaxCount)),
base::StringPrintf("p%03" PRIuS, kCacheMaxCount),
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
}
for (size_t i = 0; i < kCacheMaxCount - kCacheMaxCount * 0.9; ++i) {
EXPECT_FALSE(storages[i]->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
}
for (size_t i = kCacheMaxCount - kCacheMaxCount * 0.9 + 1;
i <= kCacheMaxCount; ++i) {
EXPECT_TRUE(storages[i]->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
}
}
TEST_P(SharedDictionaryManagerTest,
CacheEvictionOnNewDictionaryMultiIsolation) {
net::SharedDictionaryIsolationKey isolation_key1(url::Origin::Create(kUrl1),
kSite1);
net::SharedDictionaryIsolationKey isolation_key2(url::Origin::Create(kUrl2),
kSite2);
net::SharedDictionaryIsolationKey isolation_key3(url::Origin::Create(kUrl3),
kSite3);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
manager->SetCacheMaxSize(kTestData1.size() * 2);
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
ASSERT_TRUE(storage1);
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
ASSERT_TRUE(storage2);
scoped_refptr<SharedDictionaryStorage> storage3 =
manager->GetStorage(isolation_key3);
ASSERT_TRUE(storage3);
WriteDictionary(storage1.get(), GURL("https://origin1.test/d1"), "p1*",
{kTestData1});
WriteDictionary(storage2.get(), GURL("https://origin2.test/d2"), "p2*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin1.test/p1?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p2?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage3.get(), GURL("https://origin3.test/d1"), "p3*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_FALSE(storage1->GetDictionarySync(GURL("https://origin1.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage2->GetDictionarySync(GURL("https://origin2.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage3->GetDictionarySync(GURL("https://origin3.test/p3?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, CacheEvictionAfterUpdatingLastUsedTime) {
net::SharedDictionaryIsolationKey isolation_key1(url::Origin::Create(kUrl1),
kSite1);
net::SharedDictionaryIsolationKey isolation_key2(url::Origin::Create(kUrl2),
kSite2);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
ASSERT_TRUE(storage1);
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
ASSERT_TRUE(storage2);
WriteDictionary(storage1.get(), GURL("https://origin1.test/d1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage1.get(), GURL("https://origin1.test/d2"), "p2*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage2.get(), GURL("https://origin2.test/d1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage2.get(), GURL("https://origin2.test/d2"), "p2*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
scoped_refptr<net::SharedDictionary> dict1 = storage1->GetDictionarySync(
GURL("https://origin1.test/p1?"), mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dict1);
manager->SetCacheMaxSize(kTestData1.size() * 3);
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin1.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage1->GetDictionarySync(GURL("https://origin1.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage2->GetDictionarySync(GURL("https://origin2.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p2?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, CacheEvictionPerSiteSizeExceeded) {
net::SharedDictionaryIsolationKey isolation_key1(url::Origin::Create(kUrl1),
kSite1);
net::SharedDictionaryIsolationKey isolation_key2(url::Origin::Create(kUrl1),
kSite2);
net::SharedDictionaryIsolationKey isolation_key3(url::Origin::Create(kUrl2),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
manager->SetCacheMaxSize(kTestData1.size() * 4);
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
scoped_refptr<SharedDictionaryStorage> storage3 =
manager->GetStorage(isolation_key3);
WriteDictionary(storage1.get(), GURL("https://origin1.test/d"), "p*",
{kTestData1});
WriteDictionary(storage2.get(), GURL("https://origin2.test/d"), "p*",
{kTestData1});
WriteDictionary(storage3.get(), GURL("https://origin3.test/d"), "p*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_TRUE(storage3->GetDictionarySync(GURL("https://origin3.test/p?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage1.get(), GURL("https://origin4.test/d"), "p*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_FALSE(storage1->GetDictionarySync(GURL("https://origin1.test/p?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage3->GetDictionarySync(GURL("https://origin3.test/p?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin4.test/p?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest,
CacheEvictionPerSiteZeroMaxSizeCountExceeded) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
size_t cache_max_count_per_site = kCacheMaxCount / 2;
for (size_t i = 0; i < cache_max_count_per_site; ++i) {
WriteDictionary(
storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS, i)),
base::StringPrintf("p%03" PRIuS, i), {kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
}
for (size_t i = 0; i < cache_max_count_per_site; ++i) {
EXPECT_TRUE(storage->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
}
WriteDictionary(storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS,
cache_max_count_per_site)),
base::StringPrintf("p%03" PRIuS, cache_max_count_per_site),
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p000?"),
mojom::RequestDestination::kEmpty));
for (size_t i = 1; i <= cache_max_count_per_site; ++i) {
EXPECT_TRUE(storage->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
}
}
TEST_P(SharedDictionaryManagerTest,
CacheEvictionPerSiteNonZeroMaxSizeCountExceeded) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
manager->SetCacheMaxSize(kTestData1.size() *
kCacheMaxCount);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
size_t cache_max_count_per_site = kCacheMaxCount / 2;
for (size_t i = 0; i < cache_max_count_per_site; ++i) {
WriteDictionary(
storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS, i)),
base::StringPrintf("p%03" PRIuS, i), {kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
}
for (size_t i = 0; i < cache_max_count_per_site; ++i) {
EXPECT_TRUE(storage->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
}
WriteDictionary(storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS,
cache_max_count_per_site)),
base::StringPrintf("p%03" PRIuS, cache_max_count_per_site),
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p000?"),
mojom::RequestDestination::kEmpty));
for (size_t i = 1; i <= cache_max_count_per_site; ++i) {
EXPECT_TRUE(storage->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
}
}
TEST_P(SharedDictionaryManagerTest,
CacheEvictionPerSiteBothSizeAndCountExceeded) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
manager->SetCacheMaxSize(kTestData1.size() *
kCacheMaxCount);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
ASSERT_TRUE(storage);
size_t cache_max_count_per_site = kCacheMaxCount / 2;
for (size_t i = 0; i < cache_max_count_per_site; ++i) {
WriteDictionary(
storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS, i)),
base::StringPrintf("p%03" PRIuS, i), {kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
}
for (size_t i = 0; i < cache_max_count_per_site; ++i) {
EXPECT_TRUE(storage->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
}
WriteDictionary(storage.get(),
GURL(base::StringPrintf("https://origin.test/d%03" PRIuS,
cache_max_count_per_site)),
base::StringPrintf("p%03" PRIuS, cache_max_count_per_site),
{kTestData1, kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p000?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p001?"),
mojom::RequestDestination::kEmpty));
for (size_t i = 2; i <= cache_max_count_per_site; ++i) {
EXPECT_TRUE(storage->GetDictionarySync(
GURL(base::StringPrintf("https://origin.test/p%03" PRIuS "?", i)),
mojom::RequestDestination::kEmpty));
}
}
TEST_P(SharedDictionaryManagerTest, ClearDataMatchFrameOrigin) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL("https://target.test/")),
net::SchemefulSite(GURL("https://top-frame.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*",
{kTestData1});
task_environment_.FastForwardBy(base::Hours(12));
base::RunLoop run_loop;
manager->ClearData(base::Time::Now() - base::Days(2),
base::Time::Now() - base::Days(1),
base::BindRepeating([](const GURL& url) {
return url == GURL("https://target.test/");
}),
run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p3?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, ClearDataMatchTopFrameSite) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL("https://frame.test/")),
net::SchemefulSite(GURL("https://target.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*",
{kTestData1});
task_environment_.FastForwardBy(base::Hours(12));
base::RunLoop run_loop;
manager->ClearData(base::Time::Now() - base::Days(2),
base::Time::Now() - base::Days(1),
base::BindRepeating([](const GURL& url) {
return url == GURL("https://target.test/");
}),
run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p3?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, ClearDataMatchDictionaryUrl) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL("https://frame.test/")),
net::SchemefulSite(GURL("https://top-frame.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://target.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://target.test/2"), "p2*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://target.test/3"), "p3*",
{kTestData1});
task_environment_.FastForwardBy(base::Hours(12));
base::RunLoop run_loop;
manager->ClearData(base::Time::Now() - base::Days(2),
base::Time::Now() - base::Days(1),
base::BindRepeating([](const GURL& url) {
return url == GURL("https://target.test/");
}),
run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://target.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://target.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://target.test/p3?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, ClearDataNullUrlMatcher) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL("https://frame.test/")),
net::SchemefulSite(GURL("https://top-frame.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*",
{kTestData1});
task_environment_.FastForwardBy(base::Hours(12));
base::RunLoop run_loop;
manager->ClearData(
base::Time::Now() - base::Days(2), base::Time::Now() - base::Days(1),
base::RepeatingCallback<bool(const GURL&)>(), run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p3?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, ClearDataDoNotInvalidateActiveDictionary) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL("https://frame.test/")),
net::SchemefulSite(GURL("https://top-frame.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*",
{kTestData2});
task_environment_.FastForwardBy(base::Days(1));
WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*",
{kTestData1});
task_environment_.FastForwardBy(base::Hours(12));
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
scoped_refptr<net::SharedDictionary> dict = storage->GetDictionarySync(
GURL("https://origin.test/p2?"), mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dict);
base::RunLoop run_loop;
manager->ClearData(
base::Time::Now() - base::Days(2), base::Time::Now() - base::Days(1),
base::RepeatingCallback<bool(const GURL&)>(), run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p3?"),
mojom::RequestDestination::kEmpty));
net::TestCompletionCallback read_callback;
EXPECT_EQ(net::OK,
read_callback.GetResult(dict->ReadAll(read_callback.callback())));
EXPECT_EQ(kTestData2,
std::string(reinterpret_cast<const char*>(dict->data()->data()),
dict->size()));
}
TEST_P(SharedDictionaryManagerTest, ClearDataForIsolationKey) {
net::SharedDictionaryIsolationKey isolation_key1(
url::Origin::Create(GURL("https://frame1.test/")),
net::SchemefulSite(GURL("https://target1.test")));
net::SharedDictionaryIsolationKey isolation_key2(
url::Origin::Create(GURL("https://frame2.test/")),
net::SchemefulSite(GURL("https://target2.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
WriteDictionary(storage1.get(), GURL("https://origin1.test/1"), "p1*",
{kTestData1});
WriteDictionary(storage1.get(), GURL("https://origin1.test/2"), "p2*",
{kTestData1});
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
WriteDictionary(storage2.get(), GURL("https://origin2.test/1"), "p1*",
{kTestData1});
WriteDictionary(storage2.get(), GURL("https://origin2.test/2"), "p2*",
{kTestData1});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin1.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin1.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p2?"),
mojom::RequestDestination::kEmpty));
base::RunLoop run_loop;
manager->ClearDataForIsolationKey(isolation_key1, run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(storage1->GetDictionarySync(GURL("https://origin1.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_FALSE(storage1->GetDictionarySync(GURL("https://origin1.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage2->GetDictionarySync(GURL("https://origin2.test/p2?"),
mojom::RequestDestination::kEmpty));
}
TEST_P(SharedDictionaryManagerTest, GetUsageInfo) {
net::SharedDictionaryIsolationKey isolation_key1(
url::Origin::Create(GURL("https://frame1.test/")),
net::SchemefulSite(GURL("https://target1.test")));
net::SharedDictionaryIsolationKey isolation_key2(
url::Origin::Create(GURL("https://frame2.test/")),
net::SchemefulSite(GURL("https://target2.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
WriteDictionary(storage1.get(), GURL("https://origin1.test/1"), "p1*",
{kTestData1});
WriteDictionary(storage1.get(), GURL("https://origin1.test/2"), "p2*",
{kTestData2});
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
WriteDictionary(storage2.get(), GURL("https://origin2.test/1"), "p1*",
{kTestData2});
WriteDictionary(storage2.get(), GURL("https://origin2.test/2"), "p2*",
{kTestData2});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
base::RunLoop run_loop;
manager->GetUsageInfo(base::BindLambdaForTesting(
[&](const std::vector<net::SharedDictionaryUsageInfo>& usage_info) {
EXPECT_THAT(
usage_info,
UnorderedElementsAreArray(
{net::SharedDictionaryUsageInfo{
.isolation_key = isolation_key1,
.total_size_bytes = kTestData1.size() + kTestData2.size()},
net::SharedDictionaryUsageInfo{
.isolation_key = isolation_key2,
.total_size_bytes = kTestData2.size() * 2}}));
run_loop.Quit();
}));
run_loop.Run();
}
TEST_P(SharedDictionaryManagerTest, GetUsageInfoEmptyResult) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
base::RunLoop run_loop;
manager->GetUsageInfo(base::BindLambdaForTesting(
[&](const std::vector<net::SharedDictionaryUsageInfo>& usage_info) {
EXPECT_TRUE(usage_info.empty());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_P(SharedDictionaryManagerTest, GetSharedDictionaryInfo) {
net::SharedDictionaryIsolationKey isolation_key1(
url::Origin::Create(GURL("https://frame1.test/")),
net::SchemefulSite(GURL("https://target1.test")));
net::SharedDictionaryIsolationKey isolation_key2(
url::Origin::Create(GURL("https://frame2.test/")),
net::SchemefulSite(GURL("https://target2.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
const base::Time start_time = base::Time::Now();
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
WriteDictionary(storage1.get(), GURL("https://origin1.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage1.get(), GURL("https://origin1.test/2"), "p2*",
{kTestData2});
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage2.get(), GURL("https://origin2.test/d"), "p*",
{kTestData2}, ",expires=123456",
"cache-control:max-age=123456\n");
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_TRUE(storage1->GetDictionarySync(GURL("https://origin1.test/p2?"),
mojom::RequestDestination::kEmpty));
std::vector<network::mojom::SharedDictionaryInfoPtr> result1 =
GetSharedDictionaryInfo(manager.get(), isolation_key1);
ASSERT_EQ(2u, result1.size());
EXPECT_EQ("/p1*", result1[0]->match);
EXPECT_EQ(GURL("https://origin1.test/1"), result1[0]->dictionary_url);
EXPECT_EQ(start_time, result1[0]->response_time);
EXPECT_EQ(kDefaultExpiration, result1[0]->expiration);
EXPECT_EQ(start_time, result1[0]->last_used_time);
EXPECT_EQ(kTestData1.size(), result1[0]->size);
EXPECT_EQ(kTestData1Hash, result1[0]->hash);
EXPECT_EQ("/p2*", result1[1]->match);
EXPECT_EQ(GURL("https://origin1.test/2"), result1[1]->dictionary_url);
EXPECT_EQ(start_time + base::Seconds(1), result1[1]->response_time);
EXPECT_EQ(kDefaultExpiration, result1[1]->expiration);
EXPECT_EQ(start_time + base::Seconds(3), result1[1]->last_used_time);
EXPECT_EQ(kTestData2.size(), result1[1]->size);
EXPECT_EQ(kTestData2Hash, result1[1]->hash);
std::vector<network::mojom::SharedDictionaryInfoPtr> result2 =
GetSharedDictionaryInfo(manager.get(), isolation_key2);
ASSERT_EQ(1u, result2.size());
EXPECT_EQ("/p*", result2[0]->match);
EXPECT_EQ(GURL("https://origin2.test/d"), result2[0]->dictionary_url);
EXPECT_EQ(start_time + base::Seconds(2), result2[0]->response_time);
EXPECT_EQ(base::Seconds(123456), result2[0]->expiration);
EXPECT_EQ(start_time + base::Seconds(2), result2[0]->last_used_time);
EXPECT_EQ(kTestData2.size(), result2[0]->size);
EXPECT_EQ(kTestData2Hash, result2[0]->hash);
}
TEST_P(SharedDictionaryManagerTest, GetSharedDictionaryInfoEmptyResult) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
EXPECT_TRUE(GetSharedDictionaryInfo(
manager.get(),
net::SharedDictionaryIsolationKey(
url::Origin::Create(GURL("https://frame.test/")),
net::SchemefulSite(GURL("https://top-frame.test"))))
.empty());
}
TEST_P(SharedDictionaryManagerTest, GetTotalSizeAndOrigins) {
net::SharedDictionaryIsolationKey isolation_key1(
url::Origin::Create(GURL("https://frame1.test/")),
net::SchemefulSite(GURL("https://target1.test")));
net::SharedDictionaryIsolationKey isolation_key2(
url::Origin::Create(GURL("https://frame2.test/")),
net::SchemefulSite(GURL("https://target2.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
const base::Time start_time = base::Time::Now();
scoped_refptr<SharedDictionaryStorage> storage1 =
manager->GetStorage(isolation_key1);
WriteDictionary(storage1.get(), GURL("https://origin1.test/1"), "p1*",
{kTestData1});
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage1.get(), GURL("https://origin1.test/2"), "p2*",
{kTestData2});
scoped_refptr<SharedDictionaryStorage> storage2 =
manager->GetStorage(isolation_key2);
task_environment_.FastForwardBy(base::Seconds(1));
WriteDictionary(storage2.get(), GURL("https://origin2.test/d"), "p*",
{kTestData2});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_TRUE(GetOriginsBetween(manager.get(), start_time - base::Seconds(1),
start_time)
.empty());
EXPECT_THAT(GetOriginsBetween(manager.get(), start_time,
start_time + base::Seconds(1)),
testing::ElementsAreArray({isolation_key1.frame_origin()}));
EXPECT_THAT(
GetOriginsBetween(manager.get(), start_time,
start_time + base::Seconds(3)),
testing::UnorderedElementsAreArray(
{isolation_key1.frame_origin(), isolation_key2.frame_origin()}));
}
TEST_P(SharedDictionaryManagerTest, DeleteExpiredDictionariesOnGetDictionary) {
net::SharedDictionaryIsolationKey isolation_key(
url::Origin::Create(GURL("https://frame.test/")),
net::SchemefulSite(GURL("https://target.test")));
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin.test/d1"), "p1*",
{kTestData1}, ",expires=20",
"cache-control:max-age=20\n");
task_environment_.FastForwardBy(base::Seconds(10));
WriteDictionary(storage.get(), GURL("https://origin.test/d1"), "p2*",
{kTestData2}, ",expires=5",
"cache-control:max-age=5\n");
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
task_environment_.FastForwardBy(base::Seconds(4));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p2?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_EQ(2u, GetSharedDictionaryInfo(manager.get(), isolation_key).size());
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p2?"),
mojom::RequestDestination::kEmpty));
EXPECT_EQ(1u, GetSharedDictionaryInfo(manager.get(), isolation_key).size());
task_environment_.FastForwardBy(base::Seconds(4));
EXPECT_TRUE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
task_environment_.FastForwardBy(base::Seconds(1));
EXPECT_EQ(1u, GetSharedDictionaryInfo(manager.get(), isolation_key).size());
EXPECT_FALSE(storage->GetDictionarySync(GURL("https://origin.test/p1?"),
mojom::RequestDestination::kEmpty));
EXPECT_TRUE(GetSharedDictionaryInfo(manager.get(), isolation_key).empty());
}
TEST_P(SharedDictionaryManagerTest, DictionaryEquality) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "a*",
{"Hello"});
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "b*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
auto dictionary_a1 = storage->GetDictionarySync(
GURL("https://origin1.test/a1"), mojom::RequestDestination::kEmpty);
auto dictionary_a2 = storage->GetDictionarySync(
GURL("https://origin1.test/a2"), mojom::RequestDestination::kEmpty);
auto dictionary_b = storage->GetDictionarySync(
GURL("https://origin1.test/b"), mojom::RequestDestination::kEmpty);
ASSERT_TRUE(dictionary_a1);
ASSERT_TRUE(dictionary_a2);
ASSERT_TRUE(dictionary_b);
EXPECT_TRUE(dictionary_a1.get() == dictionary_a2.get());
EXPECT_TRUE(dictionary_a1.get() != dictionary_b.get());
EXPECT_TRUE(dictionary_a2.get() != dictionary_b.get());
}
TEST_P(SharedDictionaryManagerTest, PreloadSharedDictionaryInfo) {
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_FALSE(manager->HasPreloadedSharedDictionaryInfo());
mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
handle;
manager->PreloadSharedDictionaryInfoForDocument(
{GURL("https://origin1.test/p1"), GURL("https://origin1.test/p2")},
handle.InitWithNewPipeAndPassReceiver());
EXPECT_TRUE(manager->HasPreloadedSharedDictionaryInfo());
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
auto dictionary = storage->GetDictionarySync(
GURL("https://origin1.test/p3"), mojom::RequestDestination::kEmpty);
EXPECT_EQ(net::OK,
dictionary->ReadAll(base::BindOnce([](int) { NOTREACHED(); })));
handle.reset();
task_environment_.RunUntilIdle();
EXPECT_FALSE(manager->HasPreloadedSharedDictionaryInfo());
}
TEST_P(SharedDictionaryManagerTest,
PreloadSharedDictionaryInfoOpaqueOriginDoNotCrash) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
handle;
manager->PreloadSharedDictionaryInfoForDocument(
{GURL("opaque-origin://url")}, handle.InitWithNewPipeAndPassReceiver());
}
TEST_P(SharedDictionaryManagerTest, MaybeCreateSharedDictionaryGetterFlags) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
EXPECT_FALSE(manager->MaybeCreateSharedDictionaryGetter(
net::LOAD_NORMAL, mojom::RequestDestination::kDocument));
EXPECT_TRUE(manager->MaybeCreateSharedDictionaryGetter(
net::LOAD_CAN_USE_SHARED_DICTIONARY,
mojom::RequestDestination::kDocument));
}
TEST_P(SharedDictionaryManagerTest, MaybeCreateSharedDictionaryGetter) {
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
auto dictionary_getter = manager->MaybeCreateSharedDictionaryGetter(
net::LOAD_CAN_USE_SHARED_DICTIONARY,
mojom::RequestDestination::kDocument);
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
EXPECT_TRUE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/p1")));
EXPECT_FALSE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/x1")));
EXPECT_FALSE(dictionary_getter.Run(std::nullopt,
GURL("https://origin1.test/p1")));
manager.reset();
EXPECT_FALSE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/p1")));
}
TEST_P(SharedDictionaryManagerTest, PreloadedDictionaryConditionalUseEnabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({features::kPreloadedDictionaryConditionalUse},
{});
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
auto dictionary_getter = manager->MaybeCreateSharedDictionaryGetter(
net::LOAD_CAN_USE_SHARED_DICTIONARY,
mojom::RequestDestination::kDocument);
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
handle;
manager->PreloadSharedDictionaryInfoForDocument(
{GURL("https://origin1.test/p1")},
handle.InitWithNewPipeAndPassReceiver());
if (GetManagerType() == TestManagerType::kInMemory) {
EXPECT_TRUE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/p1")));
return;
}
EXPECT_FALSE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/p2")));
FlushCacheTasks();
EXPECT_TRUE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/p3")));
}
TEST_P(SharedDictionaryManagerTest, PreloadedDictionaryConditionalUseDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({},
{features::kPreloadedDictionaryConditionalUse});
std::unique_ptr<SharedDictionaryManager> manager =
CreateSharedDictionaryManager();
auto dictionary_getter = manager->MaybeCreateSharedDictionaryGetter(
net::LOAD_CAN_USE_SHARED_DICTIONARY,
mojom::RequestDestination::kDocument);
net::SharedDictionaryIsolationKey isolation_key(url::Origin::Create(kUrl1),
kSite1);
scoped_refptr<SharedDictionaryStorage> storage =
manager->GetStorage(isolation_key);
WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "p*",
{"Hello"});
if (GetManagerType() == TestManagerType::kOnDisk) {
FlushCacheTasks();
}
mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
handle;
manager->PreloadSharedDictionaryInfoForDocument(
{GURL("https://origin1.test/p1")},
handle.InitWithNewPipeAndPassReceiver());
EXPECT_TRUE(
dictionary_getter.Run(isolation_key, GURL("https://origin1.test/p2")));
}
}