|  | // Copyright 2025 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "android_webview/browser/prefetch/aw_prefetch_manager.h" | 
|  |  | 
|  | #include "android_webview/browser/aw_browser_context.h" | 
|  | #include "android_webview/browser/aw_browser_context_store.h" | 
|  | #include "android_webview/common/aw_features.h" | 
|  | #include "base/android/jni_android.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/test/task_environment.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/test/browser_task_environment.h" | 
|  | #include "content/public/test/test_browser_context.h" | 
|  | #include "content/public/test/test_content_client_initializer.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace android_webview { | 
|  |  | 
|  | class AwPrefetchManagerTest : public testing::Test { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | test_content_client_initializer_ = | 
|  | new content::TestContentClientInitializer(); | 
|  | browser_context_ = std::make_unique<content::TestBrowserContext>(); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | // Drain the message queue before destroying | 
|  | // |test_content_client_initializer_|, otherwise a posted task may call | 
|  | // content::GetNetworkConnectionTracker() after | 
|  | // TestContentClientInitializer's destructor sets it to null. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | browser_context_.reset(); | 
|  | delete test_content_client_initializer_; | 
|  | } | 
|  |  | 
|  | // Create the TestBrowserThreads. | 
|  | content::BrowserTaskEnvironment task_environment_; | 
|  | raw_ptr<content::TestContentClientInitializer> | 
|  | test_content_client_initializer_; | 
|  | std::unique_ptr<content::TestBrowserContext> browser_context_; | 
|  | }; | 
|  |  | 
|  | // Tests that setting the CacheConfig on the PrefetchManager applies it | 
|  | // correctly. | 
|  | TEST_F(AwPrefetchManagerTest, UpdateCacheConfig) { | 
|  | AwPrefetchManager prefetch_manager(browser_context_.get()); | 
|  | prefetch_manager.SetTtlInSec(base::android::AttachCurrentThread(), | 
|  | /*ttl_in_sec=*/60 * 10); | 
|  |  | 
|  | prefetch_manager.SetMaxPrefetches(base::android::AttachCurrentThread(), | 
|  | /* max_prefetches=*/5); | 
|  |  | 
|  | EXPECT_EQ(prefetch_manager.GetTtlInSec(base::android::AttachCurrentThread()), | 
|  | 60 * 10); | 
|  | EXPECT_EQ( | 
|  | prefetch_manager.GetMaxPrefetches(base::android::AttachCurrentThread()), | 
|  | 5); | 
|  | } | 
|  |  | 
|  | TEST_F(AwPrefetchManagerTest, MaxPrefetchReachesLimit) { | 
|  | AwPrefetchManager prefetch_manager(browser_context_.get()); | 
|  |  | 
|  | prefetch_manager.SetTtlInSec(base::android::AttachCurrentThread(), | 
|  | /*ttl_in_sec=*/60 * 10); | 
|  |  | 
|  | prefetch_manager.SetMaxPrefetches(base::android::AttachCurrentThread(), | 
|  | /* max_prefetches=*/3); | 
|  |  | 
|  | // Add more prefetch requests than the limit. | 
|  | for (int i = 0; i < 5; ++i) { | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), | 
|  | "https://example.com/" + base::NumberToString(i), | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  | } | 
|  |  | 
|  | // Check the number of prefetches after exceeding the limit. | 
|  | EXPECT_EQ(prefetch_manager.GetAllPrefetchKeysForTesting().size(), 3u); | 
|  |  | 
|  | // Add one more to trigger a removal | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), "https://example.com/last", | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  | EXPECT_EQ(prefetch_manager.GetAllPrefetchKeysForTesting().size(), | 
|  | 3u);  // Should still be at the limit | 
|  | } | 
|  |  | 
|  | TEST_F(AwPrefetchManagerTest, RemoveOldestPrefetchHandle) { | 
|  | AwPrefetchManager prefetch_manager(browser_context_.get()); | 
|  |  | 
|  | prefetch_manager.SetTtlInSec(base::android::AttachCurrentThread(), | 
|  | /*ttl_in_sec=*/60 * 10); | 
|  |  | 
|  | prefetch_manager.SetMaxPrefetches(base::android::AttachCurrentThread(), | 
|  | /* max_prefetches=*/2); | 
|  |  | 
|  | // 1. Make two requests. | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), "https://example.com/0", | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  |  | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), "https://example.com/1", | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  |  | 
|  | // 2. Capture the initial prefetches. | 
|  | std::vector<int32_t> initial_prefetches = | 
|  | prefetch_manager.GetAllPrefetchKeysForTesting(); | 
|  | EXPECT_EQ(initial_prefetches.size(), 2u); | 
|  |  | 
|  | // 3. Do the third request. | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), "https://example.com/2", | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  |  | 
|  | std::vector<int32_t> current_prefetches = | 
|  | prefetch_manager.GetAllPrefetchKeysForTesting(); | 
|  | EXPECT_EQ(current_prefetches.size(), 2u); | 
|  |  | 
|  | // Verify that the oldest prefetch is removed. | 
|  | auto it0 = std::find(current_prefetches.cbegin(), current_prefetches.cend(), | 
|  | initial_prefetches.front()); | 
|  | EXPECT_EQ(it0, current_prefetches.cend()); | 
|  |  | 
|  | // Last added element isn't included in the initials | 
|  | auto it1 = std::find(initial_prefetches.cbegin(), initial_prefetches.cend(), | 
|  | current_prefetches.back()); | 
|  | EXPECT_EQ(it1, initial_prefetches.cend()); | 
|  |  | 
|  | EXPECT_EQ(current_prefetches.at(0), initial_prefetches.at(1)); | 
|  | } | 
|  |  | 
|  | TEST_F(AwPrefetchManagerTest, UpdateMaxPrefetchesIsRespected) { | 
|  | AwPrefetchManager prefetch_manager(browser_context_.get()); | 
|  |  | 
|  | prefetch_manager.SetTtlInSec(base::android::AttachCurrentThread(), | 
|  | /*ttl_in_sec=*/60 * 10); | 
|  |  | 
|  | // set MaxPrefetches to a big number, 5. | 
|  | prefetch_manager.SetMaxPrefetches(base::android::AttachCurrentThread(), | 
|  | /* max_prefetches=*/5); | 
|  |  | 
|  | // Make five requests. | 
|  | for (int i = 0; i < 5; ++i) { | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), | 
|  | "https://example.com/" + base::NumberToString(i), | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  | } | 
|  | EXPECT_EQ(prefetch_manager.GetAllPrefetchKeysForTesting().size(), 5u); | 
|  |  | 
|  | // Now, let's lower that number with more than 1. Let's say 2. | 
|  | prefetch_manager.SetMaxPrefetches(base::android::AttachCurrentThread(), | 
|  | /* max_prefetches=*/2); | 
|  |  | 
|  | // Adding another request. | 
|  | prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), "https://example.com/6", | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  |  | 
|  | // Should be on the latest setting, 2. | 
|  | EXPECT_EQ(prefetch_manager.GetAllPrefetchKeysForTesting().size(), 2u); | 
|  | } | 
|  |  | 
|  | TEST_F(AwPrefetchManagerTest, PrefetchHandleKeysAlwaysIncrement) { | 
|  | AwPrefetchManager prefetch_manager(browser_context_.get()); | 
|  |  | 
|  | prefetch_manager.SetTtlInSec(base::android::AttachCurrentThread(), | 
|  | /*ttl_in_sec=*/60 * 10); | 
|  | prefetch_manager.SetMaxPrefetches(base::android::AttachCurrentThread(), | 
|  | /* max_prefetches=*/5); | 
|  |  | 
|  | // Confirm the initial values. | 
|  | int last_prefetch_key = prefetch_manager.GetLastPrefetchKeyForTesting(); | 
|  | EXPECT_EQ(last_prefetch_key, -1); | 
|  |  | 
|  | // Add more than the max allowed prefetches (triggering evictions) while | 
|  | // ensuring that the prefetch handle keys always increment confirming that the | 
|  | // prefetches are both sorted in the order they were added and that their keys | 
|  | // are never reused. | 
|  | for (int i = 0; i < 10; ++i) { | 
|  | int prefetch_key = prefetch_manager.StartPrefetchRequest( | 
|  | base::android::AttachCurrentThread(), | 
|  | "https://example.com/" + base::NumberToString(i), | 
|  | /*prefetch_params=*/nullptr, /*callback=*/nullptr, | 
|  | /*callback_executor=*/nullptr); | 
|  | EXPECT_EQ(prefetch_key, last_prefetch_key + 1); | 
|  | last_prefetch_key = prefetch_key; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace android_webview |