| // Copyright 2018 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "content/browser/code_cache/generated_code_cache.h" | 
 |  | 
 | #include <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/compiler_specific.h" | 
 | #include "base/containers/span.h" | 
 | #include "base/files/scoped_temp_dir.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_view_util.h" | 
 | #include "base/test/metrics/histogram_tester.h" | 
 | #include "base/test/scoped_feature_list.h" | 
 | #include "base/test/task_environment.h" | 
 | #include "base/time/time.h" | 
 | #include "content/common/features.h" | 
 | #include "content/public/browser/content_browser_client.h" | 
 | #include "content/public/common/content_client.h" | 
 | #include "content/public/test/browser_task_environment.h" | 
 | #include "content/public/test/test_utils.h" | 
 | #include "net/base/network_isolation_key.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "url/gurl.h" | 
 | #include "url/origin.h" | 
 |  | 
 | namespace content { | 
 |  | 
 | class GeneratedCodeCacheTest : public testing::TestWithParam<bool> { | 
 |  public: | 
 |   // This should be larger than |kSmallDataLimit| in generated_code_cache.cc. | 
 |   static const size_t kLargeSizeInBytes = 8192; | 
 |   // This should be larger than |kLargeDataLimit| in generated_code_cache.cc. | 
 |   // Additionally, this shouldn't exceed 1/8 of the maximum cache size below, | 
 |   // |kMaxSizeInBytes|. | 
 |   static const size_t kVeryLargeSizeInBytes = 128 * 1024; | 
 |   static const size_t kMaxSizeInBytes = 1024 * 1024; | 
 |   static_assert(kMaxSizeInBytes / kVeryLargeSizeInBytes > 0UL, | 
 |                 "Cache will be too small to hold a very large item"); | 
 |   static constexpr char kInitialUrl[] = "http://example.com/script.js"; | 
 |   static constexpr char kInitialOrigin[] = "http://example.com"; | 
 |   static constexpr char kInitialData[] = "InitialData"; | 
 |  | 
 |   GeneratedCodeCacheTest() { | 
 |     scoped_feature_list_.InitWithFeatureState(features::kInMemoryCodeCache, | 
 |                                               GetParam()); | 
 |   } | 
 |  | 
 |   void SetUp() override { | 
 |     ASSERT_TRUE(cache_dir_.CreateUniqueTempDir()); | 
 |     cache_path_ = cache_dir_.GetPath(); | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     disk_cache::FlushCacheThreadForTesting(); | 
 |     backend_ = nullptr; | 
 |     generated_code_cache_.reset(); | 
 |     task_environment_.RunUntilIdle(); | 
 |     EXPECT_TRUE(cache_dir_.Delete()) << cache_dir_.GetPath(); | 
 |   } | 
 |  | 
 |   // This function initializes the cache and waits till the transaction is | 
 |   // finished. When this function returns, the backend is already initialized. | 
 |   void InitializeCache(GeneratedCodeCache::CodeCacheType cache_type, | 
 |                        const char* initial_url = kInitialUrl, | 
 |                        const char* initial_origin = kInitialOrigin) { | 
 |     // Create code cache | 
 |     generated_code_cache_ = std::make_unique<GeneratedCodeCache>( | 
 |         cache_path_, kMaxSizeInBytes, cache_type); | 
 |  | 
 |     GeneratedCodeCache::GetBackendCallback callback = base::BindOnce( | 
 |         &GeneratedCodeCacheTest::GetBackendCallback, base::Unretained(this)); | 
 |     generated_code_cache_->GetBackend(std::move(callback)); | 
 |  | 
 |     WriteToCache(GURL(initial_url), GURL(initial_origin), kInitialData, | 
 |                  base::Time::Now()); | 
 |     task_environment_.RunUntilIdle(); | 
 |   } | 
 |  | 
 |   // This function initializes the cache and reopens it. When this function | 
 |   // returns, the backend initialization is not complete yet. This is used | 
 |   // to test the pending operaions path. | 
 |   void InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType cache_type) { | 
 |     InitializeCache(cache_type); | 
 |     backend_ = nullptr; | 
 |     generated_code_cache_ = std::make_unique<GeneratedCodeCache>( | 
 |         cache_path_, kMaxSizeInBytes, cache_type); | 
 |   } | 
 |  | 
 |   void WriteToCache(const GURL& url, | 
 |                     const GURL& origin_lock, | 
 |                     const std::string& data, | 
 |                     base::Time response_time) { | 
 |     generated_code_cache_->WriteEntry(url, origin_lock, | 
 |                                       net::NetworkIsolationKey(), response_time, | 
 |                                       base::as_byte_span(data)); | 
 |   } | 
 |  | 
 |   void DeleteFromCache(const GURL& url, const GURL& origin_lock) { | 
 |     generated_code_cache_->DeleteEntry(url, origin_lock, | 
 |                                        net::NetworkIsolationKey()); | 
 |   } | 
 |  | 
 |   void FetchFromCache(const GURL& url, const GURL& origin_lock) { | 
 |     received_ = false; | 
 |     GeneratedCodeCache::ReadDataCallback callback = base::BindOnce( | 
 |         &GeneratedCodeCacheTest::FetchEntryCallback, base::Unretained(this)); | 
 |     generated_code_cache_->FetchEntry( | 
 |         url, origin_lock, net::NetworkIsolationKey(), std::move(callback)); | 
 |   } | 
 |  | 
 |   void DoomAll() { | 
 |     net::CompletionOnceCallback callback = base::BindOnce( | 
 |         &GeneratedCodeCacheTest::DoomAllCallback, base::Unretained(this)); | 
 |     backend_->DoomAllEntries(std::move(callback)); | 
 |   } | 
 |  | 
 |   void GetBackendCallback(disk_cache::Backend* backend) { backend_ = backend; } | 
 |  | 
 |   void DoomAllCallback(int rv) {} | 
 |  | 
 |   void FetchEntryCallback(const base::Time& response_time, | 
 |                           mojo_base::BigBuffer data) { | 
 |     if (data.size() == 0) { | 
 |       received_ = true; | 
 |       received_null_ = true; | 
 |       received_response_time_ = response_time; | 
 |       return; | 
 |     } | 
 |     received_ = true; | 
 |     received_null_ = false; | 
 |     received_data_ = base::as_string_view(base::span(data)); | 
 |     received_response_time_ = response_time; | 
 |   } | 
 |  | 
 |  protected: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 |   base::ScopedTempDir cache_dir_; | 
 |   base::test::TaskEnvironment task_environment_; | 
 |   std::unique_ptr<GeneratedCodeCache> generated_code_cache_; | 
 |   std::string received_data_; | 
 |   base::Time received_response_time_; | 
 |   bool received_; | 
 |   bool received_null_; | 
 |   base::FilePath cache_path_; | 
 |   raw_ptr<disk_cache::Backend> backend_ = nullptr; | 
 | }; | 
 |  | 
 | constexpr char GeneratedCodeCacheTest::kInitialUrl[]; | 
 | constexpr char GeneratedCodeCacheTest::kInitialOrigin[]; | 
 | constexpr char GeneratedCodeCacheTest::kInitialData[]; | 
 | const size_t GeneratedCodeCacheTest::kMaxSizeInBytes; | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, GetResourceURLFromKey) { | 
 |   // These must be kept in sync with the values in generated_code_cache.cc. | 
 |   constexpr char kPrefix[] = "_key"; | 
 |   constexpr char kSeparator[] = " \n"; | 
 |  | 
 |   // Test that we correctly extract the resource URL from a key. | 
 |   std::string key(kPrefix); | 
 |   key.append(kInitialUrl); | 
 |   key.append(kSeparator); | 
 |   key.append(kInitialOrigin); | 
 |  | 
 |   EXPECT_EQ(GeneratedCodeCache::GetResourceURLFromKey(key), kInitialUrl); | 
 |  | 
 |   // Invalid key formats should return the empty string. | 
 |   ASSERT_TRUE(GeneratedCodeCache::GetResourceURLFromKey("").empty()); | 
 |   ASSERT_TRUE(GeneratedCodeCache::GetResourceURLFromKey("foobar").empty()); | 
 |   ASSERT_TRUE( | 
 |       GeneratedCodeCache::GetResourceURLFromKey( | 
 |           "43343250B630900F20597168708E14F17A6263F5251FCA10746EA7BDEA881085") | 
 |           .empty()); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, CheckResponseTime) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string data = "SerializedCodeForScript"; | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(kInitialData, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteEntry) { | 
 |   GURL new_url("http://example1.com/script.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string data = "SerializedCodeForScript"; | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(new_url, origin_lock, data, response_time); | 
 |   FetchFromCache(new_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteLargeEntry) { | 
 |   GURL new_url("http://example1.com/script.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(new_url, origin_lock, large_data, response_time); | 
 |   FetchFromCache(new_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(large_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteVeryLargeEntry) { | 
 |   GURL new_url("http://example1.com/script.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kVeryLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(new_url, origin_lock, large_data, response_time); | 
 |   FetchFromCache(new_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(large_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, DeleteEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   DeleteFromCache(url, origin_lock); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   ASSERT_TRUE(received_null_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteEntryWithEmptyData) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, std::string(), response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   ASSERT_TRUE(received_null_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteEntryFailure) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   std::string too_big_data(kMaxSizeInBytes * 8, 0); | 
 |   WriteToCache(url, origin_lock, too_big_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   // Fetch should return empty data, with invalid response time. | 
 |   ASSERT_TRUE(received_); | 
 |   ASSERT_TRUE(received_null_); | 
 |   EXPECT_EQ(base::Time(), received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteEntryFailureOutOfOrder) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   // Dooming adds pending activity for all entries. This makes the following | 
 |   // write block for stream 0, while the stream 1 write fails synchronously. The | 
 |   // two callbacks are received in reverse order. | 
 |   DoomAll(); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   std::string too_big_data(kMaxSizeInBytes * 8, 0); | 
 |   WriteToCache(url, origin_lock, too_big_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   // Fetch should return empty data, with invalid response time. | 
 |   ASSERT_TRUE(received_); | 
 |   ASSERT_TRUE(received_null_); | 
 |   EXPECT_EQ(base::Time(), received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchEntryPendingOp) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(kInitialData, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteEntryPendingOp) { | 
 |   GURL new_url("http://example1.com/script1.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string data = "SerializedCodeForScript"; | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(new_url, origin_lock, data, response_time); | 
 |   FetchFromCache(new_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteLargeEntryPendingOp) { | 
 |   GURL new_url("http://example1.com/script1.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(new_url, origin_lock, large_data, response_time); | 
 |   FetchFromCache(new_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(large_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WriteVeryLargeEntryPendingOp) { | 
 |   GURL new_url("http://example1.com/script1.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kVeryLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(new_url, origin_lock, large_data, response_time); | 
 |   FetchFromCache(new_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(large_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, DeleteEntryPendingOp) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   DeleteFromCache(url, origin_lock); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   ASSERT_TRUE(received_null_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, UpdateDataOfExistingEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string new_data = "SerializedCodeForScriptOverwrite"; | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, new_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(new_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, UpdateDataOfSmallExistingEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string new_data(kLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, new_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(new_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, UpdateDataOfLargeExistingEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, large_data, response_time); | 
 |   std::string new_data = large_data + "Overwrite"; | 
 |   response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, new_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(new_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, UpdateDataOfVeryLargeExistingEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kVeryLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, large_data, response_time); | 
 |   std::string new_data = large_data + "Overwrite"; | 
 |   response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, new_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(new_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, TruncateDataOfLargeExistingEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, large_data, response_time); | 
 |   std::string new_data = "SerializedCodeForScriptOverwrite"; | 
 |   response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, new_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(new_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, TruncateDataOfVeryLargeExistingEntry) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string large_data(kVeryLargeSizeInBytes, 'x'); | 
 |   base::Time response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, large_data, response_time); | 
 |   std::string new_data = "SerializedCodeForScriptOverwrite"; | 
 |   response_time = base::Time::Now(); | 
 |   WriteToCache(url, origin_lock, new_data, response_time); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(new_data, received_data_); | 
 |   EXPECT_EQ(response_time, received_response_time_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchFailsForNonexistingOrigin) { | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   GURL new_origin_lock = GURL("http://not-example.com"); | 
 |   FetchFromCache(GURL(kInitialUrl), new_origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   ASSERT_TRUE(received_null_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchEntriesFromSameOrigin) { | 
 |   GURL url("http://example.com/script.js"); | 
 |   GURL second_url("http://script.com/one.js"); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string data_first_resource = "SerializedCodeForFirstResource"; | 
 |   WriteToCache(url, origin_lock, data_first_resource, base::Time()); | 
 |  | 
 |   std::string data_second_resource = "SerializedCodeForSecondResource"; | 
 |   WriteToCache(second_url, origin_lock, data_second_resource, base::Time()); | 
 |  | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data_first_resource, received_data_); | 
 |  | 
 |   FetchFromCache(second_url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data_second_resource, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchSucceedsFromDifferentOrigins) { | 
 |   GURL url("http://example.com/script.js"); | 
 |   GURL origin_lock = GURL("http://example.com"); | 
 |   GURL origin_lock1 = GURL("http://example1.com"); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string data_origin = "SerializedCodeForFirstOrigin"; | 
 |   WriteToCache(url, origin_lock, data_origin, base::Time()); | 
 |  | 
 |   std::string data_origin1 = "SerializedCodeForSecondOrigin"; | 
 |   WriteToCache(url, origin_lock1, data_origin1, base::Time()); | 
 |  | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data_origin, received_data_); | 
 |  | 
 |   FetchFromCache(url, origin_lock1); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data_origin1, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, VeryLargeEntriesAreMerged) { | 
 |   GURL url("http://example.com/script.js"); | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |  | 
 |   // Write more copies of the same resource than the cache can hold unless they | 
 |   // are merged by content. | 
 |   for (size_t i = 0; i < 2 * kMaxSizeInBytes / kVeryLargeSizeInBytes; ++i) { | 
 |     GURL origin_lock = GURL(std::string("http://example") + | 
 |                             base::NumberToString(i) + std::string(".com")); | 
 |     std::string large_data(kVeryLargeSizeInBytes, 'x'); | 
 |     WriteToCache(url, origin_lock, large_data, base::Time()); | 
 |   } | 
 |  | 
 |   for (size_t i = 0; i < 2 * kMaxSizeInBytes / kVeryLargeSizeInBytes; ++i) { | 
 |     GURL origin_lock = GURL(std::string("http://example") + | 
 |                             base::NumberToString(i) + std::string(".com")); | 
 |     std::string large_data(kVeryLargeSizeInBytes, 'x'); | 
 |     FetchFromCache(url, origin_lock); | 
 |     task_environment_.RunUntilIdle(); | 
 |     ASSERT_TRUE(received_); | 
 |     EXPECT_EQ(large_data, received_data_); | 
 |     received_ = false; | 
 |     received_data_ = std::string(); | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, StressVeryLargeEntries) { | 
 |   GURL url("http://example.com/script.js"); | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   // Fill the cache with very large data keyed by the SHA-256 checksum. | 
 |   char data1 = 0; | 
 |   for (size_t i = 0; i < kMaxSizeInBytes / kVeryLargeSizeInBytes - 1; | 
 |        ++i, ++data1) { | 
 |     GURL origin_lock = GURL(std::string("http://example") + | 
 |                             base::NumberToString(i) + std::string(".com")); | 
 |     std::string large_data(kVeryLargeSizeInBytes, data1); | 
 |     WriteToCache(url, origin_lock, large_data, base::Time()); | 
 |   } | 
 |  | 
 |   // Fill the cache with new data. The old entries should be purged to make | 
 |   // room for the new ones. | 
 |   char data2 = -128; | 
 |   for (size_t i = 0; i < kMaxSizeInBytes / kVeryLargeSizeInBytes - 1; | 
 |        ++i, ++data2) { | 
 |     GURL origin_lock = GURL(std::string("http://example") + | 
 |                             base::NumberToString(i) + std::string(".com")); | 
 |     std::string large_data(kVeryLargeSizeInBytes, data2); | 
 |     WriteToCache(url, origin_lock, large_data, base::Time()); | 
 |   } | 
 |  | 
 |   data2 = -128; | 
 |   for (size_t i = 0; i < kMaxSizeInBytes / kVeryLargeSizeInBytes - 1; | 
 |        ++i, ++data2) { | 
 |     GURL origin_lock = GURL(std::string("http://example") + | 
 |                             base::NumberToString(i) + std::string(".com")); | 
 |     FetchFromCache(url, origin_lock); | 
 |     task_environment_.RunUntilIdle(); | 
 |     // We can't depend too strongly on the disk cache storage heuristic. Verify | 
 |     // that if we received data, it's what we wrote. | 
 |     if (!received_null_) { | 
 |       std::string large_data(kVeryLargeSizeInBytes, data2); | 
 |       EXPECT_EQ(large_data, received_data_); | 
 |       received_ = false; | 
 |       received_data_ = std::string(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchSucceedsEmptyOriginLock) { | 
 |   GURL url("http://example.com/script.js"); | 
 |   GURL origin_lock = GURL(""); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string data = "SerializedCodeForEmptyOrigin"; | 
 |   WriteToCache(url, origin_lock, data, base::Time()); | 
 |  | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(data, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, FetchEmptyOriginVsValidOriginLocks) { | 
 |   GURL url("http://example.com/script.js"); | 
 |   GURL empty_origin_lock = GURL(""); | 
 |   GURL origin_lock = GURL("http://example.com"); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   std::string empty_origin_data = "SerializedCodeForEmptyOrigin"; | 
 |   WriteToCache(url, empty_origin_lock, empty_origin_data, base::Time()); | 
 |  | 
 |   std::string valid_origin_data = "SerializedCodeForValidOrigin"; | 
 |   WriteToCache(url, origin_lock, valid_origin_data, base::Time()); | 
 |  | 
 |   FetchFromCache(url, empty_origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(empty_origin_data, received_data_); | 
 |  | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(valid_origin_data, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, WasmCache) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kWebAssembly); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   ASSERT_TRUE(received_); | 
 |   EXPECT_EQ(kInitialData, received_data_); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheTest, TestFailedBackendOpening) { | 
 |   GURL url(kInitialUrl); | 
 |   GURL origin_lock = GURL(kInitialOrigin); | 
 |  | 
 |   // Clear cache_path_ so the backend initialization fails. | 
 |   cache_path_.clear(); | 
 |   InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType::kJavaScript); | 
 |   FetchFromCache(url, origin_lock); | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   // We should still receive a callback. | 
 |   ASSERT_TRUE(received_); | 
 |   // We shouldn't receive any data. | 
 |   ASSERT_TRUE(received_null_); | 
 | } | 
 |  | 
 | // Tests the embedder specifying custom WebUI hostnames for metrics reporting. | 
 | class TestBrowserClient : public ContentBrowserClient { | 
 |  public: | 
 |   TestBrowserClient() = default; | 
 |   TestBrowserClient(const TestBrowserClient&) = delete; | 
 |   TestBrowserClient& operator=(const TestBrowserClient&) = delete; | 
 |   ~TestBrowserClient() override = default; | 
 |  | 
 |   // ContentBrowserClient: | 
 |   std::string GetWebUIHostnameForCodeCacheMetrics( | 
 |       const GURL& webui_url) const override { | 
 |     if (webui_url.host() == "foo") { | 
 |       return "Foo"; | 
 |     } | 
 |     if (webui_url.host() == "bar") { | 
 |       return "Bar"; | 
 |     } | 
 |     return std::string(); | 
 |   } | 
 | }; | 
 |  | 
 | class GeneratedCodeCacheMetricsTest : public GeneratedCodeCacheTest { | 
 |  public: | 
 |   GeneratedCodeCacheMetricsTest() { | 
 |     content::SetBrowserClientForTesting(&browser_client_); | 
 |   } | 
 |   ~GeneratedCodeCacheMetricsTest() override { | 
 |     content::SetBrowserClientForTesting(nullptr); | 
 |   } | 
 |  | 
 |  private: | 
 |   TestBrowserClient browser_client_; | 
 | }; | 
 |  | 
 | TEST_P(GeneratedCodeCacheMetricsTest, JSWebUIMetricsWithEmbedderHostnames) { | 
 |   constexpr char kResource1Url[] = "chrome://foo/resource.js"; | 
 |   constexpr char kResource2Url[] = "chrome://bar/resource.js"; | 
 |   constexpr char kOriginUrl[] = "chrome://foo/"; | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kWebUIJavaScript, | 
 |                   kResource1Url, kOriginUrl); | 
 |   base::HistogramTester histogram_tester; | 
 |  | 
 |   // Simulate a request for two resources against different data sources for a | 
 |   // single origin. | 
 |   generated_code_cache_->CollectStatisticsForTest( | 
 |       GURL(kResource1Url), GURL(kOriginUrl), | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kUpdate); | 
 |   generated_code_cache_->CollectStatisticsForTest( | 
 |       GURL(kResource2Url), GURL(kOriginUrl), | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit); | 
 |  | 
 |   // WebUI scripts report both the generic and WebUI specific metrics. | 
 |   histogram_tester.ExpectBucketCount( | 
 |       "SiteIsolatedCodeCache.JS.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1); | 
 |   histogram_tester.ExpectBucketCount( | 
 |       "SiteIsolatedCodeCache.JS.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit, 1); | 
 |   histogram_tester.ExpectBucketCount( | 
 |       "SiteIsolatedCodeCache.JS.WebUI.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1); | 
 |   histogram_tester.ExpectBucketCount( | 
 |       "SiteIsolatedCodeCache.JS.WebUI.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit, 1); | 
 |  | 
 |   // There should be two source specific resource hits. | 
 |   histogram_tester.ExpectUniqueSample( | 
 |       "SiteIsolatedCodeCache.JS.WebUI.Foo.Resource.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1); | 
 |   histogram_tester.ExpectUniqueSample( | 
 |       "SiteIsolatedCodeCache.JS.WebUI.Bar.Resource.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit, 1); | 
 |  | 
 |   // Both resource requests should be reported against the same origin. | 
 |   histogram_tester.ExpectBucketCount( | 
 |       "SiteIsolatedCodeCache.JS.WebUI.Foo.Origin.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1); | 
 |   histogram_tester.ExpectBucketCount( | 
 |       "SiteIsolatedCodeCache.JS.WebUI.Foo.Origin.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit, 1); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheMetricsTest, JavaScriptMetrics) { | 
 |   constexpr char kResourceUrl[] = "https://foo/resource.js"; | 
 |   constexpr char kOriginUrl[] = "https://foo/"; | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript, kResourceUrl, | 
 |                   kOriginUrl); | 
 |   base::HistogramTester histogram_tester; | 
 |  | 
 |   generated_code_cache_->CollectStatisticsForTest( | 
 |       GURL(kResourceUrl), GURL(kOriginUrl), | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit); | 
 |   histogram_tester.ExpectUniqueSample( | 
 |       "SiteIsolatedCodeCache.JS.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit, 1); | 
 | } | 
 |  | 
 | TEST_P(GeneratedCodeCacheMetricsTest, WebAssemblyMetrics) { | 
 |   constexpr char kResourceUrl[] = "https://foo/resource.wasm"; | 
 |   constexpr char kOriginUrl[] = "https://foo/"; | 
 |   InitializeCache(GeneratedCodeCache::CodeCacheType::kWebAssembly, kResourceUrl, | 
 |                   kOriginUrl); | 
 |   base::HistogramTester histogram_tester; | 
 |  | 
 |   generated_code_cache_->CollectStatisticsForTest( | 
 |       GURL(kResourceUrl), GURL(kOriginUrl), | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit); | 
 |   histogram_tester.ExpectUniqueSample( | 
 |       "SiteIsolatedCodeCache.WASM.Behaviour", | 
 |       content::GeneratedCodeCache::CacheEntryStatus::kHit, 1); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(GeneratedCodeCacheTest, | 
 |                          GeneratedCodeCacheTest, | 
 |                          testing::Bool()); | 
 | INSTANTIATE_TEST_SUITE_P(GeneratedCodeCacheMetricsTest, | 
 |                          GeneratedCodeCacheMetricsTest, | 
 |                          testing::Bool()); | 
 |  | 
 | }  // namespace content |