Commit 7b2877dc authored by Matt Falkenhagen's avatar Matt Falkenhagen Committed by Commit Bot

Loading Predictor: Initial support for prefetching.

This adds initial support for doing full prefetching of subresources,
beyond preconnecting to their hosts.

The code is gated behind a feature flag kLoadingPredictorPrefetch.

The PrefetchManager logic is strongly based on the PreconnectManager.
But splitting them into two separate classes seemed cleaner overall.

Bug: 1092329
Change-Id: I26b244105d59e8765fc24c109ec165b7def39f89
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2235192
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: default avatarTarun Bansal <tbansal@chromium.org>
Reviewed-by: default avatarRamin Halavati <rhalavati@chromium.org>
Reviewed-by: default avatarSophie Chang <sophiechang@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780204}
parent 0b7c1975
......@@ -1319,6 +1319,8 @@ static_library("browser") {
"predictors/predictors_enums.h",
"predictors/predictors_features.cc",
"predictors/predictors_features.h",
"predictors/prefetch_manager.cc",
"predictors/prefetch_manager.h",
"predictors/proxy_lookup_client_impl.cc",
"predictors/proxy_lookup_client_impl.h",
"predictors/resolve_host_client_impl.cc",
......
......@@ -108,14 +108,14 @@ bool LoadingPredictor::PrepareForPageLoad(
AddInitialUrlToPreconnectPrediction(url, &prediction);
}
// Return early if we do not have any preconnect requests.
if (prediction.requests.empty())
// Return early if we do not have any requests.
if (prediction.requests.empty() && prediction.prefetch_requests.empty())
return false;
++total_hints_activated_;
active_hints_.emplace(url, base::TimeTicks::Now());
if (IsPreconnectAllowed(profile_))
MaybeAddPreconnect(url, std::move(prediction.requests), origin);
MaybeAddPreconnect(url, std::move(prediction), origin);
return has_local_preconnect_prediction || preconnect_prediction;
}
......@@ -153,6 +153,21 @@ PreconnectManager* LoadingPredictor::preconnect_manager() {
return preconnect_manager_.get();
}
PrefetchManager* LoadingPredictor::prefetch_manager() {
if (!base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch))
return nullptr;
if (shutdown_ || !IsPreconnectFeatureEnabled())
return nullptr;
if (!prefetch_manager_) {
prefetch_manager_ =
std::make_unique<PrefetchManager>(GetWeakPtr(), profile_);
}
return prefetch_manager_.get();
}
void LoadingPredictor::Shutdown() {
DCHECK(!shutdown_);
resource_prefetch_predictor_->Shutdown();
......@@ -224,20 +239,22 @@ void LoadingPredictor::CleanupAbandonedHintsAndNavigations(
}
}
void LoadingPredictor::MaybeAddPreconnect(
const GURL& url,
std::vector<PreconnectRequest> requests,
HintOrigin origin) {
void LoadingPredictor::MaybeAddPreconnect(const GURL& url,
PreconnectPrediction prediction,
HintOrigin origin) {
DCHECK(!shutdown_);
preconnect_manager()->Start(url, std::move(requests));
if (!prediction.prefetch_requests.empty()) {
DCHECK(base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch));
prefetch_manager()->Start(url, std::move(prediction.prefetch_requests));
return;
}
preconnect_manager()->Start(url, std::move(prediction.requests));
}
void LoadingPredictor::MaybeRemovePreconnect(const GURL& url) {
DCHECK(!shutdown_);
if (!preconnect_manager_)
return;
preconnect_manager_->Stop(url);
if (preconnect_manager_)
preconnect_manager_->Stop(url);
}
void LoadingPredictor::HandleOmniboxHint(const GURL& url, bool preconnectable) {
......@@ -277,6 +294,14 @@ void LoadingPredictor::PreconnectFinished(
stats_collector_->RecordPreconnectStats(std::move(stats));
}
void LoadingPredictor::PrefetchFinished(std::unique_ptr<PrefetchStats> stats) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (shutdown_)
return;
active_hints_.erase(stats->url);
}
void LoadingPredictor::PreconnectURLIfAllowed(
const GURL& url,
bool allow_credentials,
......
......@@ -19,6 +19,7 @@
#include "chrome/browser/predictors/loading_data_collector.h"
#include "chrome/browser/predictors/navigation_id.h"
#include "chrome/browser/predictors/preconnect_manager.h"
#include "chrome/browser/predictors/prefetch_manager.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/core/keyed_service.h"
......@@ -44,7 +45,8 @@ class LoadingStatsCollector;
//
// All methods must be called from the UI thread.
class LoadingPredictor : public KeyedService,
public PreconnectManager::Delegate {
public PreconnectManager::Delegate,
public PrefetchManager::Delegate {
public:
LoadingPredictor(const LoadingPredictorConfig& config, Profile* profile);
~LoadingPredictor() override;
......@@ -70,6 +72,7 @@ class LoadingPredictor : public KeyedService,
ResourcePrefetchPredictor* resource_prefetch_predictor();
LoadingDataCollector* loading_data_collector();
PreconnectManager* preconnect_manager();
PrefetchManager* prefetch_manager();
// KeyedService:
void Shutdown() override;
......@@ -89,6 +92,9 @@ class LoadingPredictor : public KeyedService,
// PreconnectManager::Delegate:
void PreconnectFinished(std::unique_ptr<PreconnectStats> stats) override;
// PrefetchManager::Delegate:
void PrefetchFinished(std::unique_ptr<PrefetchStats> stats) override;
size_t GetActiveHintsSizeForTesting() { return active_hints_.size(); }
size_t GetTotalHintsActivatedForTesting() { return total_hints_activated_; }
size_t GetActiveNavigationsSizeForTesting() {
......@@ -114,12 +120,15 @@ class LoadingPredictor : public KeyedService,
std::map<GURL, base::TimeTicks>::iterator hint_it);
void CleanupAbandonedHintsAndNavigations(const NavigationID& navigation_id);
// May start preconnect and preresolve jobs according to |requests| for |url|
// with a given hint |origin|.
// May start preconnect and preresolve jobs according to |prediction| for
// |url| with a given hint |origin|.
//
// When LoadingPredictorPrefetch is enabled, starts prefetch
// jobs if |prediction| has prefetch requests.
void MaybeAddPreconnect(const GURL& url,
std::vector<PreconnectRequest> requests,
PreconnectPrediction prediction,
HintOrigin origin);
// If a preconnect exists for |url|, stop it.
// If a preconnect or prefetch exists for |url|, stop it.
void MaybeRemovePreconnect(const GURL& url);
// May start a preconnect or a preresolve for |url|. |preconnectable|
......@@ -150,6 +159,7 @@ class LoadingPredictor : public KeyedService,
std::unique_ptr<LoadingStatsCollector> stats_collector_;
std::unique_ptr<LoadingDataCollector> loading_data_collector_;
std::unique_ptr<PreconnectManager> preconnect_manager_;
std::unique_ptr<PrefetchManager> prefetch_manager_;
std::map<GURL, base::TimeTicks> active_hints_;
std::set<NavigationID> active_navigations_;
bool shutdown_ = false;
......
......@@ -1833,4 +1833,104 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTestWithNoLocalPredictions,
EXPECT_EQ(0u, connection_tracker()->GetReadSocketCount());
}
// A fixture for testing prefetching with optimization guide hints.
class LoadingPredictorPrefetchBrowserTest
: public LoadingPredictorBrowserTestWithOptimizationGuide {
public:
LoadingPredictorPrefetchBrowserTest() {
feature_list_.InitAndEnableFeature(features::kLoadingPredictorPrefetch);
}
void SetUp() override {
embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
&LoadingPredictorPrefetchBrowserTest::MonitorRequest,
base::Unretained(this)));
LoadingPredictorBrowserTestWithOptimizationGuide::SetUp();
}
protected:
// Sets the requests to expect in WaitForRequests().
void SetExpectedRequests(base::flat_set<GURL> requests) {
expected_requests_ = std::move(requests);
}
// Returns once all expected requests have been received.
void WaitForRequests() {
if (expected_requests_.empty())
return;
base::RunLoop loop;
quit_ = loop.QuitClosure();
loop.Run();
}
private:
void MonitorRequest(const net::test_server::HttpRequest& request) {
// Monitor only prefetches.
if (request.headers.find("Purpose") == request.headers.end() ||
(request.headers.at("Purpose") != "prefetch")) {
return;
}
// |request.GetURL()| gives us the URL after it's already resolved to
// 127.0.0.1, so reconstruct the requested host via the Host header
// (which includes host+port).
GURL url = request.GetURL();
auto host_iter = request.headers.find("Host");
if (host_iter != request.headers.end())
url = GURL("http://" + host_iter->second + request.relative_url);
// Remove the expected request.
auto it = expected_requests_.find(url);
ASSERT_TRUE(it != expected_requests_.end())
<< "Got unexpected request: " << url;
expected_requests_.erase(it);
// Finish if done.
if (expected_requests_.empty())
std::move(quit_).Run();
}
base::test::ScopedFeatureList feature_list_;
base::flat_set<GURL> expected_requests_;
base::OnceClosure quit_;
};
// Tests that the LoadingPredictor performs prefetching
// for a navigation which it has a prediction for.
IN_PROC_BROWSER_TEST_P(LoadingPredictorPrefetchBrowserTest,
PrepareForPageLoadWithPredictionForPrefetch) {
GURL url = embedded_test_server()->GetURL(
"test.com", GetPathWithPortReplacement(kHtmlSubresourcesPath,
embedded_test_server()->port()));
// Set up optimization hints.
std::vector<std::string> hints(
{"skipsoverinvalidurl/////",
embedded_test_server()->GetURL("subresource.com", "/1").spec(),
embedded_test_server()->GetURL("subresource.com", "/2").spec(),
embedded_test_server()->GetURL("otherresource.com", "/2").spec()});
SetUpOptimizationHint(url, hints);
// Expect these prefetches.
std::vector<GURL> requests(
{embedded_test_server()->GetURL("subresource.com", "/1"),
embedded_test_server()->GetURL("subresource.com", "/2"),
embedded_test_server()->GetURL("otherresource.com", "/2")});
SetExpectedRequests(std::move(requests));
// Start a navigation and observe these prefetches.
auto observer = NavigateToURLAsync(url);
EXPECT_TRUE(observer->WaitForRequestStart());
WaitForRequests();
}
INSTANTIATE_TEST_SUITE_P(
,
LoadingPredictorPrefetchBrowserTest,
testing::Combine(
/*IsLocalPredictionEnabled()=*/testing::Values(false),
/*ShouldPreconnectUsingOptimizationGuidePredictions=*/
testing::Values(true)));
} // namespace predictors
......@@ -344,17 +344,23 @@ void LoadingPredictorTabHelper::OnOptimizationGuideDecision(
if (!subresource_url.is_valid())
continue;
predicted_subresources.push_back(subresource_url);
url::Origin subresource_origin = url::Origin::Create(subresource_url);
if (subresource_origin == main_frame_origin) {
// We are already connecting to the main frame origin by default, so don't
// include this in the prediction.
continue;
if (base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch)) {
// TODO(falken): Detect duplicates.
prediction.prefetch_requests.emplace_back(subresource_url,
network_isolation_key);
} else {
url::Origin subresource_origin = url::Origin::Create(subresource_url);
if (subresource_origin == main_frame_origin) {
// We are already connecting to the main frame origin by default, so
// don't include this in the prediction.
continue;
}
if (predicted_origins.find(subresource_origin) != predicted_origins.end())
continue;
predicted_origins.insert(subresource_origin);
prediction.requests.emplace_back(subresource_origin, 1,
network_isolation_key);
}
if (predicted_origins.find(subresource_origin) != predicted_origins.end())
continue;
predicted_origins.insert(subresource_origin);
prediction.requests.emplace_back(subresource_origin, 1,
network_isolation_key);
}
last_optimization_guide_prediction_->preconnect_prediction = prediction;
......
......@@ -37,6 +37,11 @@ const base::Feature kLoadingPredictorDisregardAlwaysAccessesNetwork{
const base::Feature kLoadingPredictorUseOptimizationGuide{
"LoadingPredictorUseOptimizationGuide", base::FEATURE_DISABLED_BY_DEFAULT};
// Modifies loading predictor so that it does prefetches of subresources instead
// of preconnects.
const base::Feature kLoadingPredictorPrefetch{
"LoadingPredictorPrefetch", base::FEATURE_DISABLED_BY_DEFAULT};
bool ShouldUseLocalPredictions() {
return base::FeatureList::IsEnabled(kLoadingPredictorUseLocalPredictions);
}
......
......@@ -24,6 +24,8 @@ extern const base::Feature kLoadingPredictorDisregardAlwaysAccessesNetwork;
extern const base::Feature kLoadingPredictorUseOptimizationGuide;
extern const base::Feature kLoadingPredictorPrefetch;
// Returns whether local predictions should be used to make preconnect
// predictions.
bool ShouldUseLocalPredictions();
......
// 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/predictors/prefetch_manager.h"
#include <utility>
#include "base/bind.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/empty_url_loader_client.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
namespace predictors {
namespace {
// TODO(crbug.com/1095842): Update the annotation once URL allowlist/blocklist
// are observed to limit the scope of the requests.
const net::NetworkTrafficAnnotationTag kPrefetchTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("predictive_prefetch",
R"(
semantics {
sender: "Loading Predictor"
description:
"This request is issued near the start of a navigation to "
"speculatively fetch resources that resulting page is predicted to "
"request."
trigger:
"Navigating Chrome (by clicking on a link, bookmark, history item, "
"using session restore, etc)."
data:
"Arbitrary site-controlled data can be included in the URL."
"Requests may include cookies and site-specific credentials."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"There are a number of ways to prevent this request:"
"A) Disable predictive operations under Settings > Advanced > "
" Privacy > Preload pages for faster browsing and searching,"
"B) Disable Lite Mode under Settings > Advanced > Lite mode, or "
"C) Disable 'Make searches and browsing better' under Settings > "
" Sync and Google services > Make searches and browsing better"
policy_exception_justification: "To be implemented"
}
comments:
"This feature can be safely disabled, but enabling it may result in "
"faster page loads."
)");
} // namespace
PrefetchStats::PrefetchStats(const GURL& url)
: url(url), start_time(base::TimeTicks::Now()) {}
PrefetchStats::~PrefetchStats() = default;
PrefetchInfo::PrefetchInfo(const GURL& url, size_t count)
: url(url),
queued_count(count),
stats(std::make_unique<PrefetchStats>(url)) {
DCHECK(url.is_valid());
DCHECK(url.SchemeIsHTTPOrHTTPS());
}
PrefetchInfo::~PrefetchInfo() = default;
PrefetchJob::PrefetchJob(PrefetchRequest prefetch_request, PrefetchInfo* info)
: url(prefetch_request.url),
network_isolation_key(std::move(prefetch_request.network_isolation_key)),
info(info) {
DCHECK(url.is_valid());
DCHECK(url.SchemeIsHTTPOrHTTPS());
DCHECK(network_isolation_key.IsFullyPopulated());
}
PrefetchJob::~PrefetchJob() = default;
PrefetchManager::PrefetchManager(base::WeakPtr<Delegate> delegate,
Profile* profile)
: delegate_(std::move(delegate)), profile_(profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
}
PrefetchManager::~PrefetchManager() = default;
void PrefetchManager::Start(const GURL& url,
std::vector<PrefetchRequest> requests) {
DCHECK(base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch));
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PrefetchInfo* info;
if (prefetch_info_.find(url) == prefetch_info_.end()) {
auto iterator_and_whether_inserted = prefetch_info_.emplace(
url, std::make_unique<PrefetchInfo>(url, requests.size()));
info = iterator_and_whether_inserted.first->second.get();
} else {
info = prefetch_info_.find(url)->second.get();
info->queued_count += requests.size();
}
for (auto& request : requests) {
PrefetchJobId job_id =
jobs_.Add(std::make_unique<PrefetchJob>(std::move(request), info));
queued_jobs_.push_back(job_id);
}
TryToLaunchPrefetchJobs();
}
size_t PrefetchManager::inflight_jobs_count() const {
DCHECK_GE(jobs_.size(), queued_jobs_.size());
return jobs_.size() - queued_jobs_.size();
}
void PrefetchManager::PrefetchUrl(PrefetchInfo& info,
const GURL& prefetch_url,
PrefetchJobId job_id,
network::SharedURLLoaderFactory& factory) {
url::Origin top_frame_origin = url::Origin::Create(info.url);
network::ResourceRequest request;
request.method = "GET";
request.url = prefetch_url;
request.site_for_cookies = net::SiteForCookies::FromUrl(info.url);
request.request_initiator = top_frame_origin;
request.referrer = info.url;
request.headers.SetHeader("Purpose", "prefetch");
request.load_flags = net::LOAD_PREFETCH;
// TODO(falken): Get the real resource type from the hint and set
// |destination| too.
// https://source.chromium.org/chromium/chromium/src/+/master:components/optimization_guide/proto/loading_predictor_metadata.proto;l=13;drc=f59ec65870df7152bcffa34e2b3f1923de07fea8
request.resource_type =
static_cast<int>(blink::mojom::ResourceType::kSubResource);
// TODO(falken): Support CORS?
request.mode = network::mojom::RequestMode::kNoCors;
// The hints are only for requests made from the top frame,
// so frame_origin is the same as top_frame_origin.
auto frame_origin = top_frame_origin;
request.trusted_params = network::ResourceRequest::TrustedParams();
request.trusted_params->isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RedirectMode::kUpdateNothing, top_frame_origin,
frame_origin, net::SiteForCookies::FromUrl(info.url));
mojo::PendingRemote<network::mojom::URLLoaderClient> url_loader_client;
auto client_receiver = url_loader_client.InitWithNewPipeAndPassReceiver();
mojo::PendingRemote<network::mojom::URLLoader> loader;
// TODO(crbug.com/1095842): Use throttles. Call
// content::CreateContentBrowserURLLoaderThrottles() to get necessary
// throttles and use blink::ThrottlingURLLoader::CreateLoaderAndStart().
// TODO(crbug.com/1092329): Ensure the request is seen by extensions.
factory.CreateLoaderAndStart(
loader.InitWithNewPipeAndPassReceiver(), /*routing_id is not needed*/ -1,
content::GlobalRequestID::MakeBrowserInitiated().request_id,
network::mojom::kURLLoadOptionNone, request, std::move(url_loader_client),
net::MutableNetworkTrafficAnnotationTag(kPrefetchTrafficAnnotation));
++info.inflight_count;
// The idea of prefetching is for the network service to put the response in
// the http cache. So from the prefetching layer, nothing needs to be done
// with the response, so just drain it.
network::EmptyURLLoaderClient::DrainURLRequest(
std::move(client_receiver), std::move(loader),
base::BindOnce(&PrefetchManager::OnPrefetchFinished,
weak_factory_.GetWeakPtr(), job_id));
}
PrefetchInfo* PrefetchManager::GetJobInfo(PrefetchJobId job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PrefetchJob* job = jobs_.Lookup(job_id);
DCHECK(job);
return job->info;
}
void PrefetchManager::OnPrefetchFinished(PrefetchJobId job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Get the info before destroying the job.
PrefetchInfo* info = GetJobInfo(job_id);
jobs_.Remove(job_id);
// Clean up the job and start more if needed.
DCHECK_GT(info->inflight_count, 0u);
--info->inflight_count;
if (info->is_done())
AllPrefetchJobsForUrlFinished(info);
TryToLaunchPrefetchJobs();
}
void PrefetchManager::TryToLaunchPrefetchJobs() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(falken): Is it ok to assume the default partition? Try to plumb the
// partition here, e.g., from WebContentsObserver. And make a similar change
// in PreconnectManager.
content::StoragePartition* storage_partition =
content::BrowserContext::GetDefaultStoragePartition(profile_);
scoped_refptr<network::SharedURLLoaderFactory> factory =
storage_partition->GetURLLoaderFactoryForBrowserProcess();
while (!queued_jobs_.empty() && inflight_jobs_count() < kMaxInflightJobs) {
auto job_id = queued_jobs_.front();
queued_jobs_.pop_front();
PrefetchJob* job = jobs_.Lookup(job_id);
DCHECK(job);
PrefetchInfo* info = job->info;
if (job->url.is_valid() && factory)
PrefetchUrl(*info, job->url, job_id, *factory);
DCHECK_LE(1u, info->queued_count);
--info->queued_count;
if (info->is_done())
AllPrefetchJobsForUrlFinished(info);
}
}
void PrefetchManager::AllPrefetchJobsForUrlFinished(PrefetchInfo* info) {
DCHECK(info);
DCHECK(info->is_done());
auto it = prefetch_info_.find(info->url);
DCHECK(it != prefetch_info_.end());
DCHECK(info == it->second.get());
if (delegate_)
delegate_->PrefetchFinished(std::move(info->stats));
prefetch_info_.erase(it);
}
} // namespace predictors
// 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_PREDICTORS_PREFETCH_MANAGER_H_
#define CHROME_BROWSER_PREDICTORS_PREFETCH_MANAGER_H_
#include <list>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/id_map.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "net/base/network_isolation_key.h"
#include "url/gurl.h"
class Profile;
namespace network {
class SharedURLLoaderFactory;
}
namespace predictors {
struct PrefetchRequest;
struct PrefetchStats {
explicit PrefetchStats(const GURL& url);
~PrefetchStats();
PrefetchStats(const PrefetchStats&) = delete;
PrefetchStats& operator=(const PrefetchStats&) = delete;
GURL url;
base::TimeTicks start_time;
// TODO(falken): Add stats about what was requested to measure
// the accuracy.
};
// Stores the status of all prefetches associated with a given |url|.
struct PrefetchInfo {
PrefetchInfo(const GURL& url, size_t count);
~PrefetchInfo();
PrefetchInfo(const PrefetchInfo&) = delete;
PrefetchInfo& operator=(const PrefetchInfo&) = delete;
bool is_done() const { return queued_count == 0 && inflight_count == 0; }
GURL url;
size_t queued_count = 0;
size_t inflight_count = 0;
std::unique_ptr<PrefetchStats> stats;
};
// Stores all data need for running a prefetch to a |url|.
struct PrefetchJob {
PrefetchJob(PrefetchRequest prefetch_request, PrefetchInfo* info);
~PrefetchJob();
PrefetchJob(const PrefetchJob&) = delete;
PrefetchJob& operator=(const PrefetchJob&) = delete;
GURL url;
net::NetworkIsolationKey network_isolation_key;
// Danger: this is a raw pointer that PrefetchJob can outlive. It must be
// accessed from PrefetchManager only, which owns PrefetchInfo.
PrefetchInfo* info;
};
// PrefetchManager prefetches input lists of URLs.
// - The input list of URLs is associated with a main frame url that can be
// used for cancelling.
// - Limits the total number of prefetches in flight.
// - All methods of the class must be called on the UI thread.
//
// This class is very similar to PreconnectManager, which does
// preresolve/preconnect instead of prefetching. It is only
// usable when LoadingPredictorPrefetch is enabled.
class PrefetchManager {
public:
class Delegate {
public:
virtual ~Delegate() = default;
// Called when all prefetch jobs for the |stats->url| are finished.
// Called on the UI thread.
virtual void PrefetchFinished(std::unique_ptr<PrefetchStats> stats) = 0;
};
static const size_t kMaxInflightJobs = 3;
PrefetchManager(base::WeakPtr<Delegate> delegate, Profile* profile);
virtual ~PrefetchManager();
PrefetchManager(const PrefetchManager&) = delete;
PrefetchManager& operator=(const PrefetchManager&) = delete;
// Starts prefetch jobs keyed by |url|.
virtual void Start(const GURL& url, std::vector<PrefetchRequest> requests);
// TODO(falken): Add a Stop() method like PreconnectManager.
// virtual void Stop(const GURL& url);
base::WeakPtr<PrefetchManager> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
using PrefetchJobMap = base::IDMap<std::unique_ptr<PrefetchJob>>;
using PrefetchJobId = PrefetchJobMap::KeyType;
friend class PrefetchManagerTest;
// The total number of prefetches that have started and not yet finished,
// across all main frame URLs.
size_t inflight_jobs_count() const;
void PrefetchUrl(PrefetchInfo& info,
const GURL& prefetch_url,
PrefetchJobId job_id,
network::SharedURLLoaderFactory& factory);
PrefetchInfo* GetJobInfo(PrefetchJobId job_id);
void OnPrefetchFinished(PrefetchJobId job_id);
void TryToLaunchPrefetchJobs();
void AllPrefetchJobsForUrlFinished(PrefetchInfo* info);
base::WeakPtr<Delegate> delegate_;
Profile* const profile_;
// All the jobs that haven't yet started. A job is removed once it starts.
std::list<PrefetchJobId> queued_jobs_;
// All the jobs that haven't yet finished (including queued jobs). A job is
// removed once it finishes.
PrefetchJobMap jobs_;
std::map<GURL, std::unique_ptr<PrefetchInfo>> prefetch_info_;
base::WeakPtrFactory<PrefetchManager> weak_factory_{this};
};
} // namespace predictors
#endif // CHROME_BROWSER_PREDICTORS_PREFETCH_MANAGER_H_
// 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/predictors/prefetch_manager.h"
#include <map>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/format_macros.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/predictors/loading_test_util.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/url_loader_interceptor.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/load_flags.h"
#include "net/base/network_isolation_key.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace predictors {
namespace {
class FakePrefetchManagerDelegate
: public PrefetchManager::Delegate,
public base::SupportsWeakPtr<FakePrefetchManagerDelegate> {
public:
void PrefetchFinished(std::unique_ptr<PrefetchStats> stats) override {
finished_urls_.insert(stats->url);
auto iter = done_callbacks_.find(stats->url);
if (iter == done_callbacks_.end())
return;
auto callback = std::move(iter->second);
done_callbacks_.erase(iter);
std::move(callback).Run();
}
void WaitForPrefetchFinished(const GURL& url) {
if (finished_urls_.find(url) != finished_urls_.end())
return;
base::RunLoop loop;
DCHECK(done_callbacks_.find(url) == done_callbacks_.end());
done_callbacks_[url] = loop.QuitClosure();
loop.Run();
}
private:
base::flat_set<GURL> finished_urls_;
base::flat_map<GURL, base::OnceClosure> done_callbacks_;
};
// Creates a NetworkIsolationKey for a main frame navigation to URL.
net::NetworkIsolationKey CreateNetworkIsolationKey(const GURL& main_frame_url) {
url::Origin origin = url::Origin::Create(main_frame_url);
return net::NetworkIsolationKey(origin, origin);
}
} // namespace
// A test fixture for the PrefetchManager.
class PrefetchManagerTest : public testing::Test {
public:
PrefetchManagerTest();
~PrefetchManagerTest() override = default;
PrefetchManagerTest(const PrefetchManagerTest&) = delete;
PrefetchManagerTest& operator=(const PrefetchManagerTest&) = delete;
protected:
size_t GetQueuedJobsCount() const {
return prefetch_manager_->queued_jobs_.size();
}
base::test::ScopedFeatureList features_;
// IO_MAINLOOP is needed for the EmbeddedTestServer.
content::BrowserTaskEnvironment task_environment_{
content::BrowserTaskEnvironment::IO_MAINLOOP};
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<FakePrefetchManagerDelegate> fake_delegate_;
std::unique_ptr<PrefetchManager> prefetch_manager_;
};
PrefetchManagerTest::PrefetchManagerTest()
: profile_(std::make_unique<TestingProfile>()),
fake_delegate_(std::make_unique<FakePrefetchManagerDelegate>()),
prefetch_manager_(
std::make_unique<PrefetchManager>(fake_delegate_->AsWeakPtr(),
profile_.get())) {
features_.InitAndEnableFeature(features::kLoadingPredictorPrefetch);
}
// Tests prefetching a single URL.
TEST_F(PrefetchManagerTest, OneMainFrameUrlOnePrefetch) {
GURL main_frame_url("https://abc.invalid");
GURL subresource_url("https://xyz.invalid/script.js");
PrefetchRequest request(subresource_url,
CreateNetworkIsolationKey(main_frame_url));
base::RunLoop loop;
content::URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&](content::URLLoaderInterceptor::RequestParams* params) -> bool {
network::ResourceRequest& request = params->url_request;
EXPECT_EQ(request.url, subresource_url);
EXPECT_TRUE(request.load_flags & net::LOAD_PREFETCH);
std::string purpose;
EXPECT_TRUE(request.headers.GetHeader("Purpose", &purpose));
EXPECT_EQ(purpose, "prefetch");
loop.Quit();
return false;
}));
prefetch_manager_->Start(main_frame_url, {request});
loop.Run();
fake_delegate_->WaitForPrefetchFinished(main_frame_url);
}
// Tests prefetching multiple URLs.
TEST_F(PrefetchManagerTest, OneMainFrameUrlMultiplePrefetch) {
net::test_server::EmbeddedTestServer test_server;
std::vector<std::string> paths;
std::vector<PrefetchRequest> requests;
std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>>
responses;
GURL main_frame_url("https://abc.invalid");
// Set up prefetches one more than the inflight limit.
// The ControllableHttpResponses must be made before the test server
// is started.
for (size_t i = 0; i < PrefetchManager::kMaxInflightJobs + 1; i++) {
std::string path = base::StringPrintf("/script%" PRIuS ".js", i);
paths.push_back(path);
responses.push_back(
std::make_unique<net::test_server::ControllableHttpResponse>(
&test_server, path));
}
// Start the server.
auto test_server_handle = test_server.StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
// The request URLs can only be constructed after the server is started.
for (size_t i = 0; i < responses.size(); i++) {
GURL url = test_server.GetURL(paths[i]);
requests.emplace_back(url, CreateNetworkIsolationKey(main_frame_url));
}
// Start the prefetching.
prefetch_manager_->Start(main_frame_url, std::move(requests));
// Wait for requests up to the inflight limit.
for (size_t i = 0; i < responses.size() - 1; i++)
responses[i]->WaitForRequest();
// Verify there is a queued job. Pump the run loop just to give the manager a
// chance to incorrectly start the queued job and fail the expectation.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetQueuedJobsCount(), 1u);
// Finish one request.
responses.front()->Send("hi");
responses.front()->Done();
// Wait for the queued job to start.
responses.back()->WaitForRequest();
EXPECT_EQ(GetQueuedJobsCount(), 0u);
// Finish all requests.
for (size_t i = 1; i < responses.size(); i++) {
responses[i]->Send("hi");
responses[i]->Done();
}
fake_delegate_->WaitForPrefetchFinished(main_frame_url);
}
// Tests prefetching multiple URLs for multiple main frames.
TEST_F(PrefetchManagerTest, MultipleMainFrameUrlMultiplePrefetch) {
net::test_server::EmbeddedTestServer test_server;
std::vector<std::string> paths;
std::vector<PrefetchRequest> requests;
std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>>
responses;
GURL main_frame_url("https://abc.invalid");
GURL main_frame_url2("https://def.invalid");
// Set up prefetches one more than the inflight limit.
size_t count = PrefetchManager::kMaxInflightJobs;
// The ControllableHttpResponses must be made before the test server
// is started.
for (size_t i = 0; i < count + 1; i++) {
std::string path = base::StringPrintf("/script%" PRIuS ".js", i);
paths.push_back(path);
responses.push_back(
std::make_unique<net::test_server::ControllableHttpResponse>(
&test_server, path));
}
// Start the server.
auto test_server_handle = test_server.StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
// The request URLs can only be constructed after the server is started.
for (size_t i = 0; i < count; i++) {
GURL url = test_server.GetURL(paths[i]);
requests.emplace_back(url, CreateNetworkIsolationKey(main_frame_url));
}
{
GURL url = test_server.GetURL(paths[count]);
requests.emplace_back(url, CreateNetworkIsolationKey(main_frame_url2));
}
// Start the prefetching.
prefetch_manager_->Start(main_frame_url,
std::vector<PrefetchRequest>(
requests.begin(), requests.begin() + count - 1));
prefetch_manager_->Start(main_frame_url2,
std::vector<PrefetchRequest>(
requests.begin() + count - 1, requests.end()));
// Wait for requests up to the inflight limit.
for (size_t i = 0; i < responses.size() - 1; i++)
responses[i]->WaitForRequest();
// Verify there is a queued job. Pump the run loop just to give the manager a
// chance to incorrectly start the queued job and fail the expectation.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetQueuedJobsCount(), 1u);
// Finish one request.
responses.front()->Send("hi");
responses.front()->Done();
// Wait for the queued job to start.
responses.back()->WaitForRequest();
EXPECT_EQ(GetQueuedJobsCount(), 0u);
// Finish all requests.
for (size_t i = 1; i < responses.size(); i++) {
responses[i]->Send("hi");
responses[i]->Done();
}
fake_delegate_->WaitForPrefetchFinished(main_frame_url);
fake_delegate_->WaitForPrefetchFinished(main_frame_url2);
}
} // namespace predictors
......@@ -76,11 +76,25 @@ PreconnectRequest::PreconnectRequest(
DCHECK_GE(num_sockets, 0);
}
PrefetchRequest::PrefetchRequest(
const GURL& url,
const net::NetworkIsolationKey& network_isolation_key)
: url(url), network_isolation_key(network_isolation_key) {
DCHECK(base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch));
}
PreconnectPrediction::PreconnectPrediction() = default;
PreconnectPrediction::PreconnectPrediction(
const PreconnectPrediction& prediction) = default;
PreconnectPrediction::PreconnectPrediction(PreconnectPrediction&& other) =
default;
PreconnectPrediction::~PreconnectPrediction() = default;
PreconnectPrediction& PreconnectPrediction::operator=(
const PreconnectPrediction& other) = default;
PreconnectPrediction& PreconnectPrediction::operator=(
PreconnectPrediction&& other) = default;
OptimizationGuidePrediction::OptimizationGuidePrediction() = default;
OptimizationGuidePrediction::OptimizationGuidePrediction(
const OptimizationGuidePrediction& prediction) = default;
......
......@@ -65,6 +65,10 @@ struct PreconnectRequest {
PreconnectRequest(const url::Origin& origin,
int num_sockets,
const net::NetworkIsolationKey& network_isolation_key);
PreconnectRequest(const PreconnectRequest&) = default;
PreconnectRequest(PreconnectRequest&&) = default;
PreconnectRequest& operator=(const PreconnectRequest&) = default;
PreconnectRequest& operator=(PreconnectRequest&&) = default;
url::Origin origin;
// A zero-value means that we need to preresolve a host only.
......@@ -73,16 +77,39 @@ struct PreconnectRequest {
net::NetworkIsolationKey network_isolation_key;
};
struct PrefetchRequest {
PrefetchRequest(const GURL& url,
const net::NetworkIsolationKey& network_isolation_key);
PrefetchRequest(const PrefetchRequest&) = default;
PrefetchRequest(PrefetchRequest&&) = default;
PrefetchRequest& operator=(const PrefetchRequest&) = default;
PrefetchRequest& operator=(PrefetchRequest&&) = default;
GURL url;
net::NetworkIsolationKey network_isolation_key;
// TODO(falken): Add resource type.
};
// Stores a result of preconnect prediction. The |requests| vector is the main
// result of prediction and other fields are used for histograms reporting.
struct PreconnectPrediction {
PreconnectPrediction();
PreconnectPrediction(const PreconnectPrediction& other);
PreconnectPrediction(PreconnectPrediction&& other);
PreconnectPrediction& operator=(const PreconnectPrediction& other);
PreconnectPrediction& operator=(PreconnectPrediction&& other);
~PreconnectPrediction();
bool is_redirected = false;
std::string host;
std::vector<PreconnectRequest> requests;
// For LoadingPredictorPrefetch. When |prefetch_requests| is non-empty, it is
// used instead of |requests|.
std::vector<PrefetchRequest> prefetch_requests;
};
// Stores a result of a prediction from the optimization guide.
......
......@@ -3336,6 +3336,7 @@ test("unit_tests") {
"../browser/predictors/loading_predictor_unittest.cc",
"../browser/predictors/loading_stats_collector_unittest.cc",
"../browser/predictors/preconnect_manager_unittest.cc",
"../browser/predictors/prefetch_manager_unittest.cc",
"../browser/predictors/resource_prefetch_predictor_tables_unittest.cc",
"../browser/predictors/resource_prefetch_predictor_unittest.cc",
"../browser/prefs/chrome_command_line_pref_store_proxy_unittest.cc",
......
......@@ -36,8 +36,6 @@ jumbo_component("network_service") {
"data_pipe_element_reader.h",
"dns_config_change_manager.cc",
"dns_config_change_manager.h",
"empty_url_loader_client.cc",
"empty_url_loader_client.h",
"host_resolver.cc",
"host_resolver.h",
"host_resolver_mdns_listener.cc",
......
......@@ -49,6 +49,8 @@ jumbo_component("cpp") {
"cross_thread_pending_shared_url_loader_factory.h",
"data_pipe_to_source_stream.cc",
"data_pipe_to_source_stream.h",
"empty_url_loader_client.cc",
"empty_url_loader_client.h",
"features.cc",
"features.h",
"header_util.cc",
......
......@@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/empty_url_loader_client.h"
#include "services/network/public/cpp/empty_url_loader_client.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace network {
......@@ -14,16 +15,21 @@ namespace network {
// static
void EmptyURLLoaderClient::DrainURLRequest(
mojo::PendingReceiver<mojom::URLLoaderClient> client_receiver,
mojo::PendingRemote<mojom::URLLoader> url_loader) {
mojo::PendingRemote<mojom::URLLoader> url_loader,
base::OnceClosure callback) {
// Raw |new| is okay, because the newly constructed EmptyURLLoaderClient will
// delete itself after consuming all the data/callbacks.
new EmptyURLLoaderClient(std::move(client_receiver), std::move(url_loader));
new EmptyURLLoaderClient(std::move(client_receiver), std::move(url_loader),
std::move(callback));
}
EmptyURLLoaderClient::EmptyURLLoaderClient(
mojo::PendingReceiver<mojom::URLLoaderClient> receiver,
mojo::PendingRemote<mojom::URLLoader> url_loader)
: receiver_(this, std::move(receiver)), url_loader_(std::move(url_loader)) {
mojo::PendingRemote<mojom::URLLoader> url_loader,
base::OnceClosure callback)
: receiver_(this, std::move(receiver)),
url_loader_(std::move(url_loader)),
callback_(std::move(callback)) {
receiver_.set_disconnect_handler(base::BindOnce(
&EmptyURLLoaderClient::DeleteSelf, base::Unretained(this)));
}
......@@ -31,6 +37,8 @@ EmptyURLLoaderClient::EmptyURLLoaderClient(
EmptyURLLoaderClient::~EmptyURLLoaderClient() {}
void EmptyURLLoaderClient::DeleteSelf() {
if (callback_)
std::move(callback_).Run();
delete this;
}
......
......@@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_NETWORK_EMPTY_URL_LOADER_CLIENT_H_
#define SERVICES_NETWORK_EMPTY_URL_LOADER_CLIENT_H_
#ifndef SERVICES_NETWORK_PUBLIC_CPP_EMPTY_URL_LOADER_CLIENT_H_
#define SERVICES_NETWORK_PUBLIC_CPP_EMPTY_URL_LOADER_CLIENT_H_
#include <memory>
#include "base/callback_forward.h"
#include "base/macros.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
......@@ -18,21 +19,25 @@
namespace network {
// Helper for draining/discarding data and callbacks that go to URLLoaderClient.
class EmptyURLLoaderClient : public mojom::URLLoaderClient,
public mojo::DataPipeDrainer::Client {
class COMPONENT_EXPORT(NETWORK_CPP) EmptyURLLoaderClient
: public mojom::URLLoaderClient,
public mojo::DataPipeDrainer::Client {
public:
// Binds |client_receiver| to a newly constructed EmptyURLLoaderClient which
// will drain/discard all callbacks/data. Takes ownership of |url_loader| and
// discards it (together with EmptyURLLoaderClient) when the URL request has
// been completed.
// been completed. If |callback| is provided, it is called once
// the request has completed.
static void DrainURLRequest(
mojo::PendingReceiver<mojom::URLLoaderClient> client_receiver,
mojo::PendingRemote<mojom::URLLoader> url_loader);
mojo::PendingRemote<mojom::URLLoader> url_loader,
base::OnceClosure callback);
private:
EmptyURLLoaderClient(
mojo::PendingReceiver<mojom::URLLoaderClient> client_receiver,
mojo::PendingRemote<mojom::URLLoader> url_loader);
mojo::PendingRemote<mojom::URLLoader> url_loader,
base::OnceClosure callback);
~EmptyURLLoaderClient() override;
void DeleteSelf();
......@@ -60,9 +65,11 @@ class EmptyURLLoaderClient : public mojom::URLLoaderClient,
mojo::Remote<mojom::URLLoader> url_loader_;
base::OnceClosure callback_;
DISALLOW_COPY_AND_ASSIGN(EmptyURLLoaderClient);
};
} // namespace network
#endif // SERVICES_NETWORK_EMPTY_URL_LOADER_CLIENT_H_
#endif // SERVICES_NETWORK_PUBLIC_CPP_EMPTY_URL_LOADER_CLIENT_H_
......@@ -45,12 +45,12 @@
#include "net/url_request/url_request_context_getter.h"
#include "services/network/chunked_data_pipe_upload_data_stream.h"
#include "services/network/data_pipe_element_reader.h"
#include "services/network/empty_url_loader_client.h"
#include "services/network/network_usage_accumulator.h"
#include "services/network/origin_policy/origin_policy_constants.h"
#include "services/network/origin_policy/origin_policy_manager.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/cross_origin_resource_policy.h"
#include "services/network/public/cpp/empty_url_loader_client.h"
#include "services/network/public/cpp/header_util.h"
#include "services/network/public/cpp/net_adapters.h"
#include "services/network/public/cpp/network_switches.h"
......@@ -1908,7 +1908,7 @@ URLLoader::BlockResponseForCorbResult URLLoader::BlockResponseForCorb() {
receiver_.reset();
EmptyURLLoaderClient::DrainURLRequest(
url_loader_client_.BindNewPipeAndPassReceiver(),
receiver_.BindNewPipeAndPassRemote());
receiver_.BindNewPipeAndPassRemote(), base::OnceClosure());
receiver_.set_disconnect_handler(
base::BindOnce(&URLLoader::OnMojoDisconnect, base::Unretained(this)));
......
......@@ -216,6 +216,7 @@ Refer to README.md for content description and update process.
<item id="popular_sites_fetch" hash_code="50755044" type="0" content_hash_code="6910083" os_list="linux,windows" file_path="components/ntp_tiles/popular_sites_impl.cc"/>
<item id="port_forwarding_controller_socket" hash_code="95075845" type="0" content_hash_code="122163428" os_list="linux,windows" file_path="chrome/browser/devtools/device/port_forwarding_controller.cc"/>
<item id="ppapi_download_request" hash_code="135967426" type="0" content_hash_code="110461402" os_list="linux,windows" file_path="chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc"/>
<item id="predictive_prefetch" hash_code="72157052" type="0" content_hash_code="69511160" os_list="linux,windows" file_path="chrome/browser/predictors/prefetch_manager.cc"/>
<item id="prefetch_download" hash_code="44583172" type="0" content_hash_code="21424542" os_list="linux,windows" file_path="components/offline_pages/core/prefetch/prefetch_downloader_impl.cc"/>
<item id="prefetch_visuals" hash_code="91068704" type="0" content_hash_code="90439946" os_list="linux,windows" file_path="components/offline_pages/core/prefetch/visuals_fetch_by_url.cc"/>
<item id="previews_litepage_origin_prober" hash_code="33703614" type="0" deprecated="2020-04-23" content_hash_code="116235355" file_path="chrome/browser/previews/previews_lite_page_redirect_url_loader.cc"/>
......
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