blob: 5f589e74f6b03bc656bb5fb38b71cf7720f3f165 [file] [log] [blame]
// 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 <jni.h>
#include <optional>
#include "android_webview/browser/prefetch/aw_preloading_utils.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/trace_event/trace_event.h"
#include "content/public/browser/browser_context.h"
#include "content/public/common/content_features.h"
// Has to come after all the FromJniType() / ToJniType() headers.
#include "android_webview/browser_jni_headers/AwPrefetchManager_jni.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
using content::BrowserThread;
namespace android_webview {
BASE_FEATURE(WebViewPrefetchDisableBlockUntilHeadTimeout,
base::FEATURE_ENABLED_BY_DEFAULT);
class AwPrefetchRequestStatusListener
: public content::PrefetchRequestStatusListener {
public:
AwPrefetchRequestStatusListener(
const base::android::ScopedJavaGlobalRef<jobject>
prefetch_manager_java_object,
const base::android::JavaRef<jobject>& callback,
const base::android::JavaRef<jobject>& callback_executor)
: prefetch_manager_java_object_(prefetch_manager_java_object),
prefetch_java_callback_(callback),
prefetch_java_callback_executor_(callback_executor) {}
~AwPrefetchRequestStatusListener() override = default;
void OnPrefetchStartFailedGeneric() override {
JNIEnv* env = base::android::AttachCurrentThread();
Java_AwPrefetchManager_onPrefetchStartFailedGeneric(
env, prefetch_manager_java_object_, prefetch_java_callback_,
prefetch_java_callback_executor_);
}
void OnPrefetchStartFailedDuplicate() override {
JNIEnv* env = base::android::AttachCurrentThread();
Java_AwPrefetchManager_onPrefetchStartFailedDuplicate(
env, prefetch_manager_java_object_, prefetch_java_callback_,
prefetch_java_callback_executor_);
}
void OnPrefetchResponseCompleted() override {
JNIEnv* env = base::android::AttachCurrentThread();
Java_AwPrefetchManager_onPrefetchResponseCompleted(
env, prefetch_manager_java_object_, prefetch_java_callback_,
prefetch_java_callback_executor_);
}
void OnPrefetchResponseError() override {
JNIEnv* env = base::android::AttachCurrentThread();
Java_AwPrefetchManager_onPrefetchResponseError(
env, prefetch_manager_java_object_, prefetch_java_callback_,
prefetch_java_callback_executor_);
}
void OnPrefetchResponseServerError(int response_code) override {
JNIEnv* env = base::android::AttachCurrentThread();
Java_AwPrefetchManager_onPrefetchResponseServerError(
env, prefetch_manager_java_object_, prefetch_java_callback_,
prefetch_java_callback_executor_, response_code);
}
private:
base::android::ScopedJavaGlobalRef<jobject> prefetch_manager_java_object_;
base::android::ScopedJavaGlobalRef<jobject> prefetch_java_callback_;
base::android::ScopedJavaGlobalRef<jobject> prefetch_java_callback_executor_;
};
AwPrefetchManager::AwPrefetchManager(content::BrowserContext* browser_context)
: browser_context_(*browser_context) {
TRACE_EVENT_INSTANT("android_webview",
"AwPrefetchManager::AwPrefetchManager");
}
AwPrefetchManager::~AwPrefetchManager() = default;
bool AwPrefetchManager::IsPrefetchRequest(
const network::ResourceRequest& resource_request) {
return AwPrefetchManager::IsSecPurposeForPrefetch(
resource_request.headers.GetHeader(blink::kSecPurposeHeaderName));
}
bool AwPrefetchManager::IsPrerenderRequest(
const network::ResourceRequest& resource_request) {
return blink::IsSecPurposeForPrerender(
resource_request.headers.GetHeader(blink::kSecPurposeHeaderName));
}
bool AwPrefetchManager::IsSecPurposeForPrefetch(
std::optional<std::string> sec_purpose_header_value) {
return blink::IsSecPurposeForPrefetch(sec_purpose_header_value);
}
int AwPrefetchManager::StartPrefetchRequest(
JNIEnv* env,
const std::string& url,
const base::android::JavaParamRef<jobject>& prefetch_params,
const base::android::JavaParamRef<jobject>& callback,
const base::android::JavaParamRef<jobject>& callback_executor) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT0("android_webview", "AwPrefetchManager::StartPrefetchRequest");
GURL pf_url = GURL(url);
net::HttpRequestHeaders additional_headers =
GetAdditionalHeadersFromPrefetchParameters(env, prefetch_params);
std::optional<net::HttpNoVarySearchData> expected_no_vary_search =
GetExpectedNoVarySearchFromPrefetchParameters(env, prefetch_params);
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<AwPrefetchRequestStatusListener>(java_obj_, callback,
callback_executor);
// For WebView we will check if there is already a duplicate
// prefetch request based on the URL and the No-Vary-Search hint. This is for
// the purpose of deduping prefetch requests on the application's behalf.
// TODO(crbug.com/393344309): Apply deduping to all prefetch requests (not
// just WebView).
if (!browser_context_->IsPrefetchDuplicate(pf_url, expected_no_vary_search)) {
// Make room for the new prefetch request by evicting the older ones.
if (all_prefetches_map_.size() >= max_prefetches_) {
int num_prefetches_to_evict =
all_prefetches_map_.size() - max_prefetches_ + 1;
auto it = all_prefetches_map_.begin();
while (num_prefetches_to_evict > 0 && it != all_prefetches_map_.end()) {
// Because the keys should be sequential based on when the prefetch
// associated with it was added, a standard iteration should always
// prioritize removing the oldest entry.
it = all_prefetches_map_.erase(it);
num_prefetches_to_evict--;
}
}
std::unique_ptr<content::PrefetchHandle> prefetch_handle =
browser_context_->StartBrowserPrefetchRequest(
pf_url, AW_PREFETCH_METRICS_SUFFIX,
GetIsJavaScriptEnabledFromPrefetchParameters(env, prefetch_params),
expected_no_vary_search,
base::FeatureList::IsEnabled(
features::kWebViewPrefetchHighestPrefetchPriority)
? std::optional(content::PrefetchPriority::kHighest)
: std::nullopt,
additional_headers, std::move(request_status_listener),
base::Seconds(ttl_in_sec_),
/*should_append_variations_header=*/false,
base::FeatureList::IsEnabled(
kWebViewPrefetchDisableBlockUntilHeadTimeout));
if (prefetch_handle) {
return AddPrefetchHandle(std::move(prefetch_handle));
}
} else {
request_status_listener->OnPrefetchStartFailedDuplicate();
}
return NO_PREFETCH_KEY;
}
void AwPrefetchManager::CancelPrefetch(JNIEnv* env, jint prefetch_key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT0("android_webview", "AwPrefetchManager::CancelPrefetch");
if (prefetch_key == NO_PREFETCH_KEY) {
// no-op.
return;
}
auto it = all_prefetches_map_.find(prefetch_key);
if (it != all_prefetches_map_.end()) {
all_prefetches_map_.erase(it);
}
}
bool AwPrefetchManager::GetIsPrefetchInCacheForTesting(JNIEnv* env,
jint prefetch_key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return all_prefetches_map_.find(prefetch_key) != all_prefetches_map_.end();
}
jint JNI_AwPrefetchManager_GetNoPrefetchKey(JNIEnv* env) {
return NO_PREFETCH_KEY;
}
jboolean JNI_AwPrefetchManager_IsSecPurposeForPrefetch(
JNIEnv* env,
std::string& sec_purpose_header_value) {
return AwPrefetchManager::IsSecPurposeForPrefetch(sec_purpose_header_value);
}
base::android::ScopedJavaLocalRef<jobject>
AwPrefetchManager::GetJavaPrefetchManager() {
if (!java_obj_) {
JNIEnv* env = base::android::AttachCurrentThread();
java_obj_ =
Java_AwPrefetchManager_create(env, reinterpret_cast<intptr_t>(this));
}
return base::android::ScopedJavaLocalRef<jobject>(java_obj_);
}
} // namespace android_webview