Commit a1ac016e authored by Ryan Sturm's avatar Ryan Sturm Committed by Chromium LUCI CQ

Allowing Search Prefetch responses to be served from cache on BF

BF and restore navigations are allowed to use cache even when the
cache-control headers would otherwise require revalidation. When this
occurs on a previously served Search Prefetch URL, Chrome should attempt
to serve the prefetch response from cache, and then fallback to the
navigation URL on the network.

Bug: 1162121
Change-Id: I5ec8c82fe772e57acce812987efed2baa3301ed2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2605414Reviewed-by: default avatarTarun Bansal <tbansal@chromium.org>
Commit-Queue: Ryan Sturm <ryansturm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#840219}
parent bbc4035b
......@@ -1365,6 +1365,8 @@ static_library("browser") {
"prefetch/prefetch_proxy/prefetch_proxy_url_loader_interceptor.h",
"prefetch/prefetch_proxy/prefetched_mainframe_response_container.cc",
"prefetch/prefetch_proxy/prefetched_mainframe_response_container.h",
"prefetch/search_prefetch/back_forward_search_prefetch_url_loader.cc",
"prefetch/search_prefetch/back_forward_search_prefetch_url_loader.h",
"prefetch/search_prefetch/base_search_prefetch_request.cc",
"prefetch/search_prefetch/base_search_prefetch_request.h",
"prefetch/search_prefetch/field_trial_settings.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/prefetch/search_prefetch/back_forward_search_prefetch_url_loader.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/prefetch/search_prefetch/search_prefetch_service.h"
#include "chrome/browser/prefetch/search_prefetch/search_prefetch_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"
BackForwardSearchPrefetchURLLoader::BackForwardSearchPrefetchURLLoader(
Profile* profile,
const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
const GURL& prefetch_url)
: profile_(profile),
network_traffic_annotation_(network_traffic_annotation),
prefetch_url_(prefetch_url) {}
BackForwardSearchPrefetchURLLoader::~BackForwardSearchPrefetchURLLoader() =
default;
SearchPrefetchURLLoader::RequestHandler
BackForwardSearchPrefetchURLLoader::ServingResponseHandler(
std::unique_ptr<SearchPrefetchURLLoader> loader) {
return base::BindOnce(
&BackForwardSearchPrefetchURLLoader::SetUpForwardingClient,
weak_factory_.GetWeakPtr(), std::move(loader));
}
void BackForwardSearchPrefetchURLLoader::SetUpForwardingClient(
std::unique_ptr<SearchPrefetchURLLoader> loader,
const network::ResourceRequest& resource_request,
mojo::PendingReceiver<network::mojom::URLLoader> receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client) {
resource_request_ =
std::make_unique<network::ResourceRequest>(resource_request);
network::ResourceRequest prefetch_request = *resource_request_;
prefetch_request.load_flags |= net::LOAD_ONLY_FROM_CACHE;
prefetch_request.url = prefetch_url_;
auto url_loader_factory =
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetURLLoaderFactoryForBrowserProcess();
// Create a network service URL loader with passed in params.
url_loader_factory->CreateLoaderAndStart(
network_url_loader_.BindNewPipeAndPassReceiver(), 0, 0,
network::mojom::kURLLoadOptionNone, prefetch_request,
url_loader_receiver_.BindNewPipeAndPassRemote(
base::ThreadTaskRunnerHandle::Get()),
net::MutableNetworkTrafficAnnotationTag(network_traffic_annotation_));
url_loader_receiver_.set_disconnect_handler(base::BindOnce(
&BackForwardSearchPrefetchURLLoader::MojoDisconnectForPrefetch,
base::Unretained(this)));
// Bind to the content/ navigation code.
DCHECK(!receiver_.is_bound());
// At this point, we are bound to the mojo receiver, so we can release
// |loader|, which points to |this|.
receiver_.Bind(std::move(receiver));
loader.release();
receiver_.set_disconnect_handler(base::BindOnce(
&BackForwardSearchPrefetchURLLoader::MojoDisconnectWithNoFallback,
weak_factory_.GetWeakPtr()));
forwarding_client_.Bind(std::move(forwarding_client));
}
void BackForwardSearchPrefetchURLLoader::RestartDirect() {
network_url_loader_.reset();
url_loader_receiver_.reset();
can_fallback_ = false;
SearchPrefetchService* service =
SearchPrefetchServiceFactory::GetForProfile(profile_);
if (service)
service->ClearCacheEntry(resource_request_->url);
auto url_loader_factory =
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetURLLoaderFactoryForBrowserProcess();
// Create a network service URL loader with passed in params.
url_loader_factory->CreateLoaderAndStart(
network_url_loader_.BindNewPipeAndPassReceiver(), 0, 0,
network::mojom::kURLLoadOptionNone, *resource_request_,
url_loader_receiver_.BindNewPipeAndPassRemote(
base::ThreadTaskRunnerHandle::Get()),
net::MutableNetworkTrafficAnnotationTag(network_traffic_annotation_));
url_loader_receiver_.set_disconnect_handler(base::BindOnce(
&BackForwardSearchPrefetchURLLoader::MojoDisconnectWithNoFallback,
base::Unretained(this)));
if (paused_) {
network_url_loader_->PauseReadingBodyFromNet();
}
}
void BackForwardSearchPrefetchURLLoader::OnReceiveResponse(
network::mojom::URLResponseHeadPtr head) {
DCHECK(forwarding_client_);
if (can_fallback_) {
if (!head->headers) {
RestartDirect();
return;
}
// Any 200 response can be served.
if (head->headers->response_code() < net::HTTP_OK ||
head->headers->response_code() >= net::HTTP_MULTIPLE_CHOICES) {
RestartDirect();
return;
}
url_loader_receiver_.set_disconnect_handler(base::BindOnce(
&BackForwardSearchPrefetchURLLoader::MojoDisconnectWithNoFallback,
weak_factory_.GetWeakPtr()));
SearchPrefetchService* service =
SearchPrefetchServiceFactory::GetForProfile(profile_);
if (service)
service->UpdateServeTime(resource_request_->url);
}
can_fallback_ = false;
forwarding_client_->OnReceiveResponse(std::move(head));
}
void BackForwardSearchPrefetchURLLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) {
DCHECK(forwarding_client_);
if (can_fallback_) {
RestartDirect();
return;
}
forwarding_client_->OnReceiveRedirect(redirect_info, std::move(head));
}
void BackForwardSearchPrefetchURLLoader::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
// We only handle GETs.
NOTREACHED();
}
void BackForwardSearchPrefetchURLLoader::OnReceiveCachedMetadata(
mojo_base::BigBuffer data) {
// Do nothing. This is not supported for navigation loader.
}
void BackForwardSearchPrefetchURLLoader::OnTransferSizeUpdated(
int32_t transfer_size_diff) {
DCHECK(forwarding_client_);
forwarding_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void BackForwardSearchPrefetchURLLoader::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
DCHECK(forwarding_client_);
forwarding_client_->OnStartLoadingResponseBody(std::move(body));
return;
}
void BackForwardSearchPrefetchURLLoader::OnComplete(
const network::URLLoaderCompletionStatus& status) {
if (status.error_code != net::OK && can_fallback_) {
RestartDirect();
return;
}
DCHECK(forwarding_client_);
can_fallback_ = false;
forwarding_client_->OnComplete(status);
network_url_loader_.reset();
}
void BackForwardSearchPrefetchURLLoader::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const base::Optional<GURL>& new_url) {
// This should never be called for a non-network service URLLoader.
NOTREACHED();
}
void BackForwardSearchPrefetchURLLoader::SetPriority(
net::RequestPriority priority,
int32_t intra_priority_value) {
// Pass through.
if (network_url_loader_)
network_url_loader_->SetPriority(priority, intra_priority_value);
resource_request_->priority = priority;
}
void BackForwardSearchPrefetchURLLoader::PauseReadingBodyFromNet() {
// Pass through.
if (network_url_loader_)
network_url_loader_->PauseReadingBodyFromNet();
paused_ = true;
}
void BackForwardSearchPrefetchURLLoader::ResumeReadingBodyFromNet() {
// Pass through.
if (network_url_loader_)
network_url_loader_->ResumeReadingBodyFromNet();
paused_ = false;
}
void BackForwardSearchPrefetchURLLoader::MojoDisconnectForPrefetch() {
if (can_fallback_)
RestartDirect();
}
void BackForwardSearchPrefetchURLLoader::MojoDisconnectWithNoFallback() {
delete this;
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_BACK_FORWARD_SEARCH_PREFETCH_URL_LOADER_H_
#define CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_BACK_FORWARD_SEARCH_PREFETCH_URL_LOADER_H_
#include <vector>
#include "base/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "chrome/browser/prefetch/search_prefetch/search_prefetch_url_loader.h"
#include "chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h"
#include "content/public/browser/url_loader_request_interceptor.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/url_loader.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom-forward.h"
// This class tries to fetch a prefetch response from cache, and if one is not
// available, it fetches the non-prefetch URL directly. This case is only
// triggered when cache doesn't need to be revalidated (i.e., back/forward).
class BackForwardSearchPrefetchURLLoader
: public network::mojom::URLLoader,
public network::mojom::URLLoaderClient,
public SearchPrefetchURLLoader {
public:
// Creates and stores state needed to do the cache lookup.
BackForwardSearchPrefetchURLLoader(
Profile* profile,
const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
const GURL& prefetch_url);
~BackForwardSearchPrefetchURLLoader() override;
private:
// SearchPrefetchURLLoader:
SearchPrefetchURLLoader::RequestHandler ServingResponseHandler(
std::unique_ptr<SearchPrefetchURLLoader> loader) override;
// network::mojom::URLLoader:
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const base::Optional<GURL>& new_url) override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override;
void PauseReadingBodyFromNet() override;
void ResumeReadingBodyFromNet() override;
// network::mojom::URLLoaderClient
void OnReceiveResponse(network::mojom::URLResponseHeadPtr head) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override;
void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
// Restarts the request to go directly to |resource_request_|.
void RestartDirect();
// The disconnect handler that is used for the fetch of the cached prefetch
// response. This handler is not used once a fallback is started or serving is
// started.
void MojoDisconnectForPrefetch();
// This handler is used for forwarding client errors and errors after a
// fallback can not occur.
void MojoDisconnectWithNoFallback();
// Sets up mojo forwarding to the navigation path. Resumes
// |network_url_loader_| calls. Serves the start of the response to the
// navigation path. After this method is called, |this| manages its own
// lifetime; |loader| points to |this| and can be released once the mojo
// connection is set up.
void SetUpForwardingClient(
std::unique_ptr<SearchPrefetchURLLoader> loader,
const network::ResourceRequest&,
mojo::PendingReceiver<network::mojom::URLLoader> receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client);
// The network URLLoader that fetches the prefetch URL and its receiver.
mojo::Remote<network::mojom::URLLoader> network_url_loader_;
mojo::Receiver<network::mojom::URLLoaderClient> url_loader_receiver_{this};
// The request that is being prefetched.
std::unique_ptr<network::ResourceRequest> resource_request_;
// Whether we are serving from |bdoy_content_|.
bool can_fallback_ = true;
// If the owner paused network activity, we need to propagate that if a
// fallback occurs.
bool paused_ = false;
Profile* profile_;
net::NetworkTrafficAnnotationTag network_traffic_annotation_;
// The URL for the prefetch response stored in cache.
GURL prefetch_url_;
// Forwarding client receiver.
mojo::Receiver<network::mojom::URLLoader> receiver_{this};
mojo::Remote<network::mojom::URLLoaderClient> forwarding_client_;
base::WeakPtrFactory<BackForwardSearchPrefetchURLLoader> weak_factory_{this};
};
#endif // CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_BACK_FORWARD_SEARCH_PREFETCH_URL_LOADER_H_
......@@ -94,9 +94,10 @@ BaseSearchPrefetchRequest::BaseSearchPrefetchRequest(
BaseSearchPrefetchRequest::~BaseSearchPrefetchRequest() = default;
bool BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
net::NetworkTrafficAnnotationTag network_traffic_annotation =
net::DefineNetworkTrafficAnnotation("search_prefetch_service", R"(
// static
net::NetworkTrafficAnnotationTag
BaseSearchPrefetchRequest::NetworkAnnotationForPrefetch() {
return net::DefineNetworkTrafficAnnotation("search_prefetch_service", R"(
semantics {
sender: "Search Prefetch Service"
description:
......@@ -129,6 +130,11 @@ bool BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
}
}
})");
}
bool BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
net::NetworkTrafficAnnotationTag network_traffic_annotation =
NetworkAnnotationForPrefetch();
url::Origin prefetch_origin = url::Origin::Create(prefetch_url_);
......@@ -137,7 +143,6 @@ bool BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
// navigation speeding and relatively high likelihood of being served to a
// navigation, the request is relatively high priority.
resource_request->priority = net::MEDIUM;
resource_request->load_flags |= net::LOAD_PREFETCH;
resource_request->url = prefetch_url_;
// Search prefetch URL Loaders should check |report_raw_headers| on the
// intercepted request to clear out the raw headers when |report_raw_headers|
......
......@@ -60,6 +60,9 @@ class BaseSearchPrefetchRequest {
BaseSearchPrefetchRequest& operator=(const BaseSearchPrefetchRequest&) =
delete;
// The NTA for any search prefetch request.
static net::NetworkTrafficAnnotationTag NetworkAnnotationForPrefetch();
// Starts the network request to prefetch |prefetch_url_|. Sets various fields
// on a resource request and calls |StartPrefetchRequestInternal()|. Returns
// |false| if the request is not started (i.e., it would be deferred by
......@@ -81,6 +84,9 @@ class BaseSearchPrefetchRequest {
// Update the status when the relevant search item is clicked in omnibox.
void MarkPrefetchAsClicked();
// The URL for the prefetch request.
const GURL& prefetch_url() { return prefetch_url_; }
// Whether the prefetch should be served based on |headers|.
bool CanServePrefetchRequest(
const scoped_refptr<net::HttpResponseHeaders> headers);
......
......@@ -73,3 +73,8 @@ bool StreamSearchPrefetchResponses() {
return base::GetFieldTrialParamByFeatureAsBool(
kSearchPrefetchServicePrefetching, "stream_responses", true);
}
size_t SearchPrefetchMaxCacheEntries() {
return base::GetFieldTrialParamByFeatureAsInt(
kSearchPrefetchServicePrefetching, "cache_size", 10);
}
......@@ -43,4 +43,8 @@ bool SearchPrefetchShouldCancelUneededInflightRequests();
// requests.
bool StreamSearchPrefetchResponses();
// The max number of stored cached prefetch responses. This is stored as a list
// of navigation URLs to prefetch URLs.
size_t SearchPrefetchMaxCacheEntries();
#endif // CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_FIELD_TRIAL_SETTINGS_H_
......@@ -10,6 +10,7 @@
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/net/prediction_options.h"
#include "chrome/browser/prefetch/search_prefetch/back_forward_search_prefetch_url_loader.h"
#include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
#include "chrome/browser/prefetch/search_prefetch/full_body_search_prefetch_request.h"
#include "chrome/browser/prefetch/search_prefetch/search_prefetch_url_loader.h"
......@@ -227,7 +228,8 @@ SearchPrefetchService::GetSearchPrefetchStatusForTesting(
}
std::unique_ptr<SearchPrefetchURLLoader>
SearchPrefetchService::TakePrefetchResponse(const GURL& url) {
SearchPrefetchService::TakePrefetchResponseFromMemoryCache(
const GURL& navigation_url) {
SearchPrefetchServingReasonRecorder recorder;
auto* template_url_service =
......@@ -248,15 +250,16 @@ SearchPrefetchService::TakePrefetchResponse(const GURL& url) {
auto* content_settings =
HostContentSettingsMapFactory::GetForProfile(profile_);
if (!content_settings ||
content_settings->GetContentSetting(
url, url, ContentSettingsType::JAVASCRIPT) == CONTENT_SETTING_BLOCK) {
content_settings->GetContentSetting(navigation_url, navigation_url,
ContentSettingsType::JAVASCRIPT) ==
CONTENT_SETTING_BLOCK) {
recorder.reason_ = SearchPrefetchServingReason::kJavascriptDisabled;
return nullptr;
}
base::string16 search_terms;
template_url_service->GetDefaultSearchProvider()->ExtractSearchTermsFromURL(
url, template_url_service->search_terms_data(), &search_terms);
navigation_url, template_url_service->search_terms_data(), &search_terms);
if (search_terms.length() == 0) {
recorder.reason_ = SearchPrefetchServingReason::kNotDefaultSearchWithTerms;
......@@ -274,7 +277,7 @@ SearchPrefetchService::TakePrefetchResponse(const GURL& url) {
// checks should address this by clearing prefetches on user changes to
// default search, it is paramount to never serve content from one origin to
// another.
if (url::Origin::Create(url) !=
if (url::Origin::Create(navigation_url) !=
url::Origin::Create(iter->second->prefetch_url())) {
recorder.reason_ =
SearchPrefetchServingReason::kPrefetchWasForDifferentOrigin;
......@@ -302,14 +305,29 @@ SearchPrefetchService::TakePrefetchResponse(const GURL& url) {
std::unique_ptr<SearchPrefetchURLLoader> response =
iter->second->TakeSearchPrefetchURLLoader();
AddCacheEntry(navigation_url, iter->second->prefetch_url());
DeletePrefetch(search_terms);
return response;
}
std::unique_ptr<SearchPrefetchURLLoader>
SearchPrefetchService::TakePrefetchResponseFromDiskCache(
const GURL& navigation_url) {
if (prefetch_cache_.find(navigation_url) == prefetch_cache_.end()) {
return nullptr;
}
return std::make_unique<BackForwardSearchPrefetchURLLoader>(
profile_, BaseSearchPrefetchRequest::NetworkAnnotationForPrefetch(),
prefetch_cache_[navigation_url].first);
}
void SearchPrefetchService::ClearPrefetches() {
prefetches_.clear();
prefetch_expiry_timers_.clear();
prefetch_cache_.clear();
}
void SearchPrefetchService::DeletePrefetch(base::string16 search_terms) {
......@@ -418,3 +436,36 @@ void SearchPrefetchService::OnTemplateURLServiceChanged() {
template_url_service_data_ = template_url_service_data;
ClearPrefetches();
}
void SearchPrefetchService::ClearCacheEntry(const GURL& navigation_url) {
prefetch_cache_.erase(navigation_url);
}
void SearchPrefetchService::UpdateServeTime(const GURL& navigation_url) {
if (prefetch_cache_.find(navigation_url) == prefetch_cache_.end())
return;
prefetch_cache_[navigation_url].second = base::Time::Now();
}
void SearchPrefetchService::AddCacheEntry(const GURL& navigation_url,
const GURL& prefetch_url) {
// TODO(ryansturm): Add prefs support to handle session restore.
// https://crbug.com/1162121.
prefetch_cache_.emplace(navigation_url,
std::make_pair(prefetch_url, base::Time::Now()));
if (prefetch_cache_.size() <= SearchPrefetchMaxCacheEntries()) {
return;
}
GURL url_to_remove;
base::Time earliest_time = base::Time::Now();
for (const auto& entry : prefetch_cache_) {
base::Time last_used_time = entry.second.second;
if (last_used_time < earliest_time) {
earliest_time = last_used_time;
url_to_remove = entry.first;
}
}
ClearCacheEntry(url_to_remove);
}
......@@ -97,15 +97,30 @@ class SearchPrefetchService : public KeyedService,
// Clear all prefetches from the service.
void ClearPrefetches();
// Clear the disk cache entry for |url|.
void ClearCacheEntry(const GURL& navigation_url);
// Update the last serving time of |url|, so it's eviction priority is
// lowered.
void UpdateServeTime(const GURL& navigation_url);
// Takes the response from this object if |url| matches a prefetched URL.
std::unique_ptr<SearchPrefetchURLLoader> TakePrefetchResponse(
const GURL& url);
std::unique_ptr<SearchPrefetchURLLoader> TakePrefetchResponseFromMemoryCache(
const GURL& navigation_url);
// Creates a cache loader to serve a cache only response with fallback to
// network fetch.
std::unique_ptr<SearchPrefetchURLLoader> TakePrefetchResponseFromDiskCache(
const GURL& navigation_url);
// Reports the status of a prefetch for a given search term.
base::Optional<SearchPrefetchStatus> GetSearchPrefetchStatusForTesting(
base::string16 search_terms);
private:
// Records a cache entry for a navigation that is being served.
void AddCacheEntry(const GURL& navigation_url, const GURL& prefetch_url);
// Removes the prefetch and prefetch timers associated with |search_terms|.
void DeletePrefetch(base::string16 search_terms);
......@@ -141,6 +156,11 @@ class SearchPrefetchService : public KeyedService,
observer_{this};
Profile* profile_;
// A map of previously handled URLs that allows certain navigations to be
// served from cache. The value is the prefetch URL in cache and the latest
// serving time of the response.
std::map<GURL, std::pair<GURL, base::Time>> prefetch_cache_;
};
#endif // CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_SEARCH_PREFETCH_SERVICE_H_
......@@ -40,10 +40,13 @@
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/browsing_data_remover_test_util.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/default_handlers.h"
......@@ -273,13 +276,24 @@ class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
SetDSEWithURL(
GetSearchServerQueryURL("{searchTerms}&{google:prefetchSource}"));
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
}
void SetUpInProcessBrowserTestFixture() override {
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
void SetUpCommandLine(base::CommandLine* cmd) override {
InProcessBrowserTest::SetUpCommandLine(cmd);
// For the proxy.
cmd->AppendSwitch("ignore-certificate-errors");
cmd->AppendSwitch("force-enable-metrics-reporting");
mock_cert_verifier_.SetUpCommandLine(cmd);
}
size_t search_server_request_count() const {
......@@ -451,6 +465,7 @@ class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
resp->set_code(net::HTTP_OK);
resp->set_content(static_files_[request.relative_url].first);
resp->set_content_type(static_files_[request.relative_url].second);
resp->AddCustomHeader("cache-control", "private, max-age=0");
return resp;
}
......@@ -468,6 +483,7 @@ class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
resp->set_code(is_prefetch ? net::HTTP_BAD_GATEWAY : net::HTTP_OK);
resp->set_content_type("text/html");
resp->set_content(content);
resp->AddCustomHeader("cache-control", "private, max-age=0");
return resp;
}
......@@ -488,6 +504,7 @@ class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
content.append(is_prefetch ? "prefetch" : "regular");
content.append(" </body></html>");
resp->set_content(content);
resp->AddCustomHeader("cache-control", "private, max-age=0");
return resp;
}
......@@ -561,6 +578,8 @@ class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
return resp;
}
content::ContentMockCertVerifier mock_cert_verifier_;
std::vector<net::test_server::HttpRequest> search_server_requests_;
std::unique_ptr<net::EmbeddedTestServer> search_server_;
......@@ -658,7 +677,8 @@ class SearchPrefetchServiceEnabledBrowserTest
SearchPrefetchServiceEnabledBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{kSearchPrefetchServicePrefetching,
{{"stream_responses", GetParam() ? "true" : "false"}}},
{{"stream_responses", GetParam() ? "true" : "false"},
{"cache_size", "1"}}},
{{kSearchPrefetchService}, {}}},
{});
}
......@@ -1176,6 +1196,129 @@ IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
SearchPrefetchStatus::kComplete, 1);
}
IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
BackPrefetchServed) {
// This test prefetches and serves two SRP responses. It then navigates back
// then forward, the back navigation should not be cached, due to cache limit
// size of 1, the second navigation should be cached.
base::HistogramTester histogram_tester;
auto* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
EXPECT_NE(nullptr, search_prefetch_service);
std::string search_terms = "prefetch_content";
GURL prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
SearchPrefetchStatus::kComplete);
ui_test_utils::NavigateToURL(browser(),
GetSearchServerQueryURL(search_terms));
// The prefetch should be served, and only 1 request should be issued.
EXPECT_EQ(1u, search_server_requests().size());
auto inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
search_terms = "prefetch_content_2";
prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
SearchPrefetchStatus::kComplete);
ui_test_utils::NavigateToURL(browser(),
GetSearchServerQueryURL(search_terms));
// The prefetch should be served, and only 1 request (now the second total
// request) should be issued.
EXPECT_EQ(2u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
content::TestNavigationObserver back_load_observer(GetWebContents());
GetWebContents()->GetController().GoBack();
back_load_observer.Wait();
// There should not be a cached prefetch request, so there should be a network
// request.
EXPECT_EQ(3u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_TRUE(base::Contains(inner_html, "regular"));
EXPECT_FALSE(base::Contains(inner_html, "prefetch"));
content::TestNavigationObserver forward_load_observer(GetWebContents());
GetWebContents()->GetController().GoForward();
forward_load_observer.Wait();
// There should be a cached prefetch request, so there should not be a new
// network request.
EXPECT_EQ(3u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
}
IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
EvictedCacheFallsback) {
// This test prefetches and serves a SRP responses. It then navigates to a
// different URL. Then it clears cache as if it was evicted. Then it navigates
// back to the prefetched SRP. As a result, the back forward cache should
// attempt to use the prefetch, but fall back to network on the original URL.
base::HistogramTester histogram_tester;
auto* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
EXPECT_NE(nullptr, search_prefetch_service);
std::string search_terms = "prefetch_content";
GURL prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
SearchPrefetchStatus::kComplete);
ui_test_utils::NavigateToURL(browser(),
GetSearchServerQueryURL(search_terms));
// The prefetch should be served, and only 1 request should be issued.
ASSERT_EQ(1u, search_server_requests().size());
EXPECT_TRUE(
base::Contains(search_server_requests()[0].GetURL().spec(), "pf=cs"));
auto inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
ui_test_utils::NavigateToURL(browser(), GetSearchServerQueryURL("search"));
// The prefetch should be served, and only 1 request (now the second total
// request) should be issued.
EXPECT_EQ(2u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_TRUE(base::Contains(inner_html, "regular"));
EXPECT_FALSE(base::Contains(inner_html, "prefetch"));
// Clearing cache should cause the back forward loader to fail over to the
// regular URL.
base::RunLoop run_loop;
content::BrowserContext::GetDefaultStoragePartition(browser()->profile())
->GetNetworkContext()
->ClearHttpCache(base::Time(), base::Time(), nullptr,
run_loop.QuitClosure());
run_loop.Run();
content::TestNavigationObserver back_load_observer(GetWebContents());
GetWebContents()->GetController().GoBack();
back_load_observer.Wait();
// There should not be a cached prefetch request, so there should be a network
// request.
ASSERT_EQ(3u, search_server_requests().size());
EXPECT_FALSE(
base::Contains(search_server_requests()[2].GetURL().spec(), "pf=cs"));
inner_html = GetDocumentInnerHTML();
EXPECT_TRUE(base::Contains(inner_html, "regular"));
EXPECT_FALSE(base::Contains(inner_html, "prefetch"));
}
IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
RegularSearchQueryWhenNoPrefetch) {
base::HistogramTester histogram_tester;
......@@ -1987,6 +2130,87 @@ INSTANTIATE_TEST_SUITE_P(All,
SearchPrefetchServiceEnabledBrowserTest,
testing::Bool());
class SearchPrefetchServiceBFCacheTest : public SearchPrefetchBaseBrowserTest {
public:
SearchPrefetchServiceBFCacheTest() {
feature_list_.InitWithFeaturesAndParameters(
{{kSearchPrefetchServicePrefetching,
{{"stream_responses", "true"}, {"cache_size", "1"}}},
{{kSearchPrefetchService}, {}},
{{features::kBackForwardCache}, {{"enable_same_site", "true"}}}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceBFCacheTest,
BackForwardPrefetchServedFromBFCache) {
// This test prefetches and serves two SRP responses. It then navigates back
// then forward, the back navigation should not be cached, due to cache limit
// size of 1, the second navigation should be cached.
base::HistogramTester histogram_tester;
auto* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
EXPECT_NE(nullptr, search_prefetch_service);
std::string search_terms = "prefetch_content";
GURL prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
SearchPrefetchStatus::kComplete);
ui_test_utils::NavigateToURL(browser(),
GetSearchServerQueryURL(search_terms));
// The prefetch should be served, and only 1 request should be issued.
EXPECT_EQ(1u, search_server_requests().size());
auto inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
search_terms = "prefetch_content_2";
prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
SearchPrefetchStatus::kComplete);
ui_test_utils::NavigateToURL(browser(),
GetSearchServerQueryURL(search_terms));
// The page should be restored from BF cache.
EXPECT_EQ(2u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
histogram_tester.ExpectTotalCount("BackForwardCache.HistoryNavigationOutcome",
0);
content::TestNavigationObserver back_load_observer(GetWebContents());
GetWebContents()->GetController().GoBack();
back_load_observer.Wait();
// The page should be restored from BF cache.
EXPECT_EQ(2u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
histogram_tester.ExpectUniqueSample(
"BackForwardCache.HistoryNavigationOutcome", 0 /* Restored */, 1);
content::TestNavigationObserver forward_load_observer(GetWebContents());
GetWebContents()->GetController().GoForward();
forward_load_observer.Wait();
// The page should be restored from BF cache.
EXPECT_EQ(2u, search_server_requests().size());
inner_html = GetDocumentInnerHTML();
EXPECT_FALSE(base::Contains(inner_html, "regular"));
EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
histogram_tester.ExpectUniqueSample(
"BackForwardCache.HistoryNavigationOutcome", 0 /* Restored */, 2);
}
class SearchPrefetchServiceZeroCacheTimeBrowserTest
: public SearchPrefetchBaseBrowserTest {
public:
......
......@@ -20,6 +20,7 @@
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "net/base/load_flags.h"
SearchPrefetchURLLoaderInterceptor::SearchPrefetchURLLoaderInterceptor(
int frame_tree_node_id)
......@@ -56,7 +57,15 @@ SearchPrefetchURLLoaderInterceptor::MaybeCreateLoaderForRequest(
if (!service)
return nullptr;
return service->TakePrefetchResponse(tentative_resource_request.url);
auto loader = service->TakePrefetchResponseFromMemoryCache(
tentative_resource_request.url);
if (loader)
return loader;
if (tentative_resource_request.load_flags & net::LOAD_SKIP_CACHE_VALIDATION) {
return service->TakePrefetchResponseFromDiskCache(
tentative_resource_request.url);
}
return nullptr;
}
void SearchPrefetchURLLoaderInterceptor::MaybeCreateLoader(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment