blob: 62618a4abb4a86d463bd268628caaf980d171758 [file]
// 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/metrics/aw_metrics_test_utils.h"
#include "android_webview/browser/prefetch/aw_prefetch_manager_data.h"
#include "android_webview/common/aw_features.h"
#include "base/android/jni_android.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_browser_context.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace android_webview {
class AwPrefetchManagerTest : public AwMetricsTestBase {
public:
AwPrefetchManagerTest() = default;
template <typename... TaskEnvironmentTraits>
explicit AwPrefetchManagerTest(TaskEnvironmentTraits&&... traits)
: AwMetricsTestBase(std::forward<TaskEnvironmentTraits>(traits)...) {}
protected:
void SetUp() override {
AwMetricsTestBase::SetUp();
env_ = base::android::AttachCurrentThread();
browser_context_ = std::make_unique<content::TestBrowserContext>();
prefs_->registry()->RegisterStringPref(prefs::kAwPrefetchLatestOrigin, "");
prefs_->registry()->RegisterBooleanPref(
prefs::kAwPrefetchLatestJavascriptEnabled, false);
AwPrefetchManager::SetPrefServiceForTesting(prefs_.get());
}
void TearDown() override {
AwPrefetchManager::SetPrefServiceForTesting(nullptr);
browser_context_.reset();
AwMetricsTestBase::TearDown();
}
raw_ptr<JNIEnv> env_;
std::unique_ptr<content::TestBrowserContext> browser_context_;
};
// Tests Max Prefetches and TTL Seconds setter APIs
TEST_F(AwPrefetchManagerTest, UpdateCacheConfig) {
AwPrefetchManager prefetch_manager(browser_context_.get());
int actual_ttl_in_sec = 30 * 10;
size_t actual_max_prefetches = 5;
int default_ttl_in_sec = kDefaultTtlInSec;
size_t default_max_prefetches = kDefaultMaxPrefetches;
prefetch_manager.SetTtlInSec(env_, actual_ttl_in_sec);
prefetch_manager.SetMaxPrefetches(env_, actual_max_prefetches);
EXPECT_EQ(actual_ttl_in_sec, prefetch_manager.GetTtlInSec(env_));
EXPECT_EQ(actual_max_prefetches, prefetch_manager.GetMaxPrefetches(env_));
prefetch_manager.ClearTtl(env_);
prefetch_manager.ClearMaxPrefetches(env_);
EXPECT_EQ(default_ttl_in_sec, prefetch_manager.GetTtlInSec(env_));
EXPECT_EQ(default_max_prefetches, prefetch_manager.GetMaxPrefetches(env_));
}
TEST_F(AwPrefetchManagerTest, MaxPrefetchReachesLimit) {
AwPrefetchManager prefetch_manager(browser_context_.get());
prefetch_manager.SetTtlInSec(env_, /*ttl_in_sec=*/60 * 10);
prefetch_manager.SetMaxPrefetches(env_, /* max_prefetches=*/3);
// Add more prefetch requests than the limit.
for (int i = 0; i < 5; ++i) {
prefetch_manager.StartPrefetchRequest(
env_, "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(
env_, "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(env_, /*ttl_in_sec=*/60 * 10);
prefetch_manager.SetMaxPrefetches(env_, /* max_prefetches=*/2);
// 1. Make two requests.
prefetch_manager.StartPrefetchRequest(
env_, "https://example.com/0", /*prefetch_params=*/nullptr,
/*callback=*/nullptr, /*callback_executor=*/nullptr);
prefetch_manager.StartPrefetchRequest(
env_, "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(
env_, "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(env_, /*ttl_in_sec=*/60 * 10);
// set MaxPrefetches to a big number, 5.
prefetch_manager.SetMaxPrefetches(env_, /* max_prefetches=*/5);
// Make five requests.
for (int i = 0; i < 5; ++i) {
prefetch_manager.StartPrefetchRequest(
env_, "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(env_, /* max_prefetches=*/2);
// Adding another request.
prefetch_manager.StartPrefetchRequest(
env_, "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(env_, /*ttl_in_sec=*/60 * 10);
prefetch_manager.SetMaxPrefetches(env_, /* 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(
env_, "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;
}
}
class AwPrefetchManagerNoNetworkServiceDedicatedThreadTest
: public AwPrefetchManagerTest {
public:
AwPrefetchManagerNoNetworkServiceDedicatedThreadTest()
: AwPrefetchManagerTest(
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
content::BrowserTaskEnvironment::REAL_IO_THREAD) {
// A short-term workaround for new tests to mitigate the integration
// issue between the dedicated `NetworkService` thread and
// `BrowserTaskEnvironment` (see crbug.com/493322520). If disabled,
// `NetworkService` will use IO thread instead. This is valid for these
// tests because they do not depend on whether the network service is
// running on a dedicated thread or the IO thread.
scoped_feature_list_.InitAndDisableFeature(
content::kNetworkServiceDedicatedThread);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(AwPrefetchManagerNoNetworkServiceDedicatedThreadTest,
DeduplicationWebViewPrefetchOffTheMainThreadDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
features::kWebViewPrefetchOffTheMainThread);
const std::string prefetch_url = "https://example.com";
const int ttl_in_sec = 10;
AwPrefetchManager prefetch_manager(browser_context_.get());
prefetch_manager.SetTtlInSec(env_, ttl_in_sec);
prefetch_manager.SetMaxPrefetches(env_, /*max_prefetches=*/5);
// 1. First request should succeed.
int key1 = prefetch_manager.StartPrefetchRequest(
env_, prefetch_url, /*prefetch_params=*/nullptr, /*callback=*/nullptr,
/*callback_executor=*/nullptr);
EXPECT_NE(key1, NO_PREFETCH_KEY);
// 2. Second request for same URL should fail due to deduplication in manager.
int key2 = prefetch_manager.StartPrefetchRequest(
env_, prefetch_url, /*prefetch_params=*/nullptr, /*callback=*/nullptr,
/*callback_executor=*/nullptr);
EXPECT_EQ(key2, NO_PREFETCH_KEY);
// 3. Forward the time after TTL.
task_environment_.FastForwardBy(base::Seconds(ttl_in_sec + 1));
// 4. Third request for same URL should succeed because prefetch is expired
// in `PrefetchService`.
int key3 = prefetch_manager.StartPrefetchRequest(
env_, prefetch_url, /*prefetch_params=*/nullptr, /*callback=*/nullptr,
/*callback_executor=*/nullptr);
EXPECT_NE(key3, NO_PREFETCH_KEY);
}
TEST_F(AwPrefetchManagerNoNetworkServiceDedicatedThreadTest,
DeduplicationWebViewPrefetchOffTheMainThreadEnabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({features::kWebViewPrefetchOffTheMainThread,
::features::kPrefetchOffTheMainThread},
{});
const std::string prefetch_url = "https://example.com";
const int ttl_in_sec = 10;
AwPrefetchManager prefetch_manager(browser_context_.get());
prefetch_manager.SetTtlInSec(env_, ttl_in_sec);
prefetch_manager.SetMaxPrefetches(env_, /*max_prefetches=*/5);
// 1. First request should succeed.
int key1 = prefetch_manager.StartPrefetchRequest(
env_, prefetch_url, /*prefetch_params=*/nullptr, /*callback=*/nullptr,
/*callback_executor=*/nullptr);
EXPECT_NE(key1, NO_PREFETCH_KEY);
// 2. Second request for same URL should fail due to deduplication in manager.
int key2 = prefetch_manager.StartPrefetchRequest(
env_, prefetch_url, /*prefetch_params=*/nullptr, /*callback=*/nullptr,
/*callback_executor=*/nullptr);
EXPECT_EQ(key2, NO_PREFETCH_KEY);
// 3. Forward the time after TTL.
task_environment_.FastForwardBy(base::Seconds(ttl_in_sec + 1));
// 4. Third request for same URL should still fail because `AwPrefetchManager`
// doesn't track staleness.
int key3 = prefetch_manager.StartPrefetchRequest(
env_, prefetch_url, /*prefetch_params=*/nullptr, /*callback=*/nullptr,
/*callback_executor=*/nullptr);
EXPECT_EQ(key3, NO_PREFETCH_KEY);
}
// Tests that the latest prefetch origin and JavaScript enabled status are
// updated on a (pre)prefetch request.
TEST_F(AwPrefetchManagerNoNetworkServiceDedicatedThreadTest,
UpdatePrefsOnPrefetchRequest) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({features::kWebViewPrefetchOffTheMainThread,
::features::kPrefetchOffTheMainThread},
{});
prefs_->SetString(prefs::kAwPrefetchLatestOrigin, "");
prefs_->SetBoolean(prefs::kAwPrefetchLatestJavascriptEnabled, true);
AwPrefetchManager prefetch_manager(browser_context_.get());
ASSERT_EQ(prefs_->GetString(prefs::kAwPrefetchLatestOrigin), "");
ASSERT_TRUE(prefs_->GetBoolean(prefs::kAwPrefetchLatestJavascriptEnabled));
// Start a prefetch request.
const std::string prefetch_url = "https://example.com/foo";
prefetch_manager.StartPrefetchRequest(env_, prefetch_url,
/*prefetch_params=*/nullptr,
/*callback=*/nullptr,
/*callback_executor=*/nullptr);
// A prefetch request should update the latest prefetch origin.
// Also, the latest JavaScript enabled status should be updated to false,
// since `prefetch_params` is nullptr in this test environment.
EXPECT_EQ(prefs_->GetString(prefs::kAwPrefetchLatestOrigin),
url::Origin::Create(GURL(prefetch_url)).Serialize());
EXPECT_FALSE(prefs_->GetBoolean(prefs::kAwPrefetchLatestJavascriptEnabled));
// Start a pre-prefetch request with a different origin.
const std::string pre_prefetch_url = "https://another.example.com/foo";
base::test::TestFuture<AwPrefetchKey> prefetch_key_future;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](AwPrefetchManager* manager_ptr, JNIEnv* env,
const std::string& url) {
base::ScopedAllowBaseSyncPrimitivesForTesting allow_blocking;
return manager_ptr->StartPrePrefetchRequest(
env, url, /*prefetch_params=*/nullptr,
/*callback=*/nullptr, /*callback_executor=*/nullptr);
},
&prefetch_manager, env_.get(), pre_prefetch_url),
prefetch_key_future.GetCallback());
std::ignore = prefetch_key_future.Take();
// The latest prefetch origin should be updated to the origin of the
// PrePrefetch request.
EXPECT_EQ(prefs_->GetString(prefs::kAwPrefetchLatestOrigin),
url::Origin::Create(GURL(pre_prefetch_url)).Serialize());
EXPECT_FALSE(prefs_->GetBoolean(prefs::kAwPrefetchLatestJavascriptEnabled));
}
} // namespace android_webview