blob: 1721167718b88a02276832e4ce914107c66d7b7f [file] [log] [blame]
// Copyright 2013 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/prerender/prerender_resource_throttle.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/safe_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/prerender/prerender_final_status.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_util.h"
#include "chrome/common/prerender_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request.h"
using content::BrowserThread;
using content::ResourceRequestInfo;
using content::ResourceType;
namespace prerender {
namespace {
PrerenderContents* g_prerender_contents_for_testing;
// Returns true if the response has a "no-store" cache control header.
bool IsNoStoreResponse(const net::URLRequest& request) {
const net::HttpResponseInfo& response_info = request.response_info();
return response_info.headers.get() &&
response_info.headers->HasHeaderValue("cache-control", "no-store");
}
} // namespace
// Used to pass information between different UI thread tasks of the same
// throttle. This is reference counted as the throttler may be destroyed before
// the UI thread task has a chance to run.
//
// This class is created on the IO thread, and destroyed on the UI thread. Its
// members should only be accessed on the UI thread.
class PrerenderThrottleInfo
: public base::RefCountedThreadSafe<PrerenderThrottleInfo,
BrowserThread::DeleteOnUIThread> {
public:
PrerenderThrottleInfo()
: mode_(NO_PRERENDER), origin_(ORIGIN_NONE), manager_(nullptr) {}
void Set(PrerenderMode mode, Origin origin, PrerenderManager* manager) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
mode_ = mode;
origin_ = origin;
manager_ = manager->AsWeakPtr();
}
PrerenderMode mode() const { return mode_; }
Origin origin() const { return origin_; }
base::WeakPtr<PrerenderManager> manager() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return manager_;
}
private:
friend class base::RefCountedThreadSafe<PrerenderThrottleInfo>;
friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
friend class base::DeleteHelper<PrerenderThrottleInfo>;
~PrerenderThrottleInfo() {}
PrerenderMode mode_;
Origin origin_;
base::WeakPtr<PrerenderManager> manager_;
};
void PrerenderResourceThrottle::OverridePrerenderContentsForTesting(
PrerenderContents* contents) {
g_prerender_contents_for_testing = contents;
}
PrerenderResourceThrottle::PrerenderResourceThrottle(net::URLRequest* request)
: request_(request),
prerender_throttle_info_(new PrerenderThrottleInfo()) {
// Priorities for prerendering requests are lowered, to avoid competing with
// other page loads, except on Android where this is less likely to be a
// problem. In some cases, this may negatively impact the performance of
// prerendering, see https://crbug.com/652746 for details.
#if !defined(OS_ANDROID)
// Requests with the IGNORE_LIMITS flag set (i.e., sync XHRs)
// should remain at MAXIMUM_PRIORITY.
if (request_->load_flags() & net::LOAD_IGNORE_LIMITS) {
DCHECK_EQ(request_->priority(), net::MAXIMUM_PRIORITY);
} else if (request_->priority() != net::IDLE) {
original_request_priority_ = request_->priority();
// In practice, the resource scheduler does not know about the request yet,
// and it falls back to calling request_->SetPriority(), so it would be
// possible to do just that here. It is cleaner and more robust to go
// through the resource dispatcher host though.
if (content::ResourceDispatcherHost::Get()) {
content::ResourceDispatcherHost::Get()->ReprioritizeRequest(request_,
net::IDLE);
}
}
#endif // OS_ANDROID
}
PrerenderResourceThrottle::~PrerenderResourceThrottle() {}
void PrerenderResourceThrottle::WillStartRequest(bool* defer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_);
*defer = true;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&PrerenderResourceThrottle::WillStartRequestOnUI,
AsWeakPtr(), request_->method(), info->GetResourceType(),
request_->url(), info->GetWebContentsGetterForRequest(),
prerender_throttle_info_));
}
void PrerenderResourceThrottle::WillRedirectRequest(
const net::RedirectInfo& redirect_info,
bool* defer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_);
*defer = true;
std::string header;
request_->GetResponseHeaderByName(kFollowOnlyWhenPrerenderShown, &header);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&PrerenderResourceThrottle::WillRedirectRequestOnUI,
AsWeakPtr(), header, info->GetResourceType(),
info->IsAsync(), IsNoStoreResponse(*request_),
redirect_info.new_url,
info->GetWebContentsGetterForRequest()));
}
void PrerenderResourceThrottle::WillProcessResponse(bool* defer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_);
if (!info)
return;
DCHECK_GT(request_->url_chain().size(), 0u);
int redirect_count =
base::saturated_cast<int>(request_->url_chain().size()) - 1;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&PrerenderResourceThrottle::WillProcessResponseOnUI,
content::IsResourceTypeFrame(info->GetResourceType()),
IsNoStoreResponse(*request_), redirect_count,
prerender_throttle_info_));
}
const char* PrerenderResourceThrottle::GetNameForLogging() const {
return "PrerenderResourceThrottle";
}
void PrerenderResourceThrottle::ResumeHandler() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
Resume();
}
void PrerenderResourceThrottle::ResetResourcePriority() {
if (!original_request_priority_)
return;
if (content::ResourceDispatcherHost::Get()) {
content::ResourceDispatcherHost::Get()->ReprioritizeRequest(
request_, original_request_priority_.value());
}
}
// static
void PrerenderResourceThrottle::WillStartRequestOnUI(
const base::WeakPtr<PrerenderResourceThrottle>& throttle,
const std::string& method,
ResourceType resource_type,
const GURL& url,
const ResourceRequestInfo::WebContentsGetter& web_contents_getter,
scoped_refptr<PrerenderThrottleInfo> prerender_throttle_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
bool cancel = false;
PrerenderContents* prerender_contents =
PrerenderContentsFromGetter(web_contents_getter);
if (prerender_contents) {
DCHECK(prerender_throttle_info);
prerender_throttle_info->Set(prerender_contents->prerender_mode(),
prerender_contents->origin(),
prerender_contents->prerender_manager());
// Abort any prerenders that spawn requests that use unsupported HTTP
// methods or schemes.
if (!IsValidHttpMethod(prerender_contents->prerender_mode(), method)) {
// If this is a full prerender, cancel the prerender in response to
// invalid requests. For prefetches, cancel invalid requests but keep the
// prefetch going, unless it's the main frame that's invalid.
if (prerender_contents->prerender_mode() == FULL_PRERENDER ||
resource_type == content::RESOURCE_TYPE_MAIN_FRAME) {
prerender_contents->Destroy(FINAL_STATUS_INVALID_HTTP_METHOD);
}
cancel = true;
} else if (!DoesSubresourceURLHaveValidScheme(url) &&
resource_type != content::RESOURCE_TYPE_MAIN_FRAME) {
// Destroying the prerender for unsupported scheme only for non-main
// resource to allow chrome://crash to actually crash in the
// *RendererCrash tests instead of being intercepted here. The
// unsupported
// scheme for the main resource is checked in WillRedirectRequestOnUI()
// and PrerenderContents::CheckURL(). See http://crbug.com/673771.
prerender_contents->Destroy(FINAL_STATUS_UNSUPPORTED_SCHEME);
ReportUnsupportedPrerenderScheme(url);
cancel = true;
#if defined(OS_ANDROID)
} else if (resource_type == content::RESOURCE_TYPE_FAVICON) {
// Delay icon fetching until the contents are getting swapped in
// to conserve network usage in mobile devices.
prerender_contents->AddResourceThrottle(throttle);
// No need to call AddIdleResource() on Android.
return;
#endif
}
#if !defined(OS_ANDROID)
if (!cancel)
prerender_contents->AddIdleResource(throttle);
#endif
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(cancel ? &PrerenderResourceThrottle::Cancel
: &PrerenderResourceThrottle::ResumeHandler,
throttle));
}
// static
void PrerenderResourceThrottle::WillRedirectRequestOnUI(
const base::WeakPtr<PrerenderResourceThrottle>& throttle,
const std::string& follow_only_when_prerender_shown_header,
ResourceType resource_type,
bool async,
bool is_no_store,
const GURL& new_url,
const ResourceRequestInfo::WebContentsGetter& web_contents_getter) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
bool cancel = false;
PrerenderContents* prerender_contents =
PrerenderContentsFromGetter(web_contents_getter);
if (prerender_contents) {
RecordPrefetchResponseReceived(
PrerenderHistograms::GetHistogramPrefix(prerender_contents->origin()),
content::IsResourceTypeFrame(resource_type), true /* is_redirect */,
is_no_store);
// Abort any prerenders with requests which redirect to invalid schemes.
if (!DoesURLHaveValidScheme(new_url)) {
prerender_contents->Destroy(FINAL_STATUS_UNSUPPORTED_SCHEME);
ReportUnsupportedPrerenderScheme(new_url);
cancel = true;
} else if (follow_only_when_prerender_shown_header == "1" &&
resource_type != content::RESOURCE_TYPE_MAIN_FRAME) {
// Only defer redirects with the Follow-Only-When-Prerender-Shown
// header. Do not defer redirects on main frame loads.
if (!async) {
// Cancel on deferred synchronous requests. Those will
// indefinitely hang up a renderer process.
prerender_contents->Destroy(FINAL_STATUS_BAD_DEFERRED_REDIRECT);
cancel = true;
} else {
// Defer the redirect until the prerender is used or canceled.
prerender_contents->AddResourceThrottle(throttle);
return;
}
}
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(cancel ? &PrerenderResourceThrottle::Cancel
: &PrerenderResourceThrottle::ResumeHandler,
throttle));
}
// static
void PrerenderResourceThrottle::WillProcessResponseOnUI(
bool is_main_resource,
bool is_no_store,
int redirect_count,
scoped_refptr<PrerenderThrottleInfo> prerender_throttle_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(prerender_throttle_info);
if (!prerender_throttle_info->manager())
return;
if (prerender_throttle_info->mode() != PREFETCH_ONLY)
return;
auto histogram_prefix = PrerenderHistograms::GetHistogramPrefix(
prerender_throttle_info->origin());
RecordPrefetchResponseReceived(histogram_prefix, is_main_resource,
false /* is_redirect */, is_no_store);
RecordPrefetchRedirectCount(histogram_prefix, is_main_resource,
redirect_count);
}
// static
PrerenderContents* PrerenderResourceThrottle::PrerenderContentsFromGetter(
const ResourceRequestInfo::WebContentsGetter& web_contents_getter) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (g_prerender_contents_for_testing)
return g_prerender_contents_for_testing;
return PrerenderContents::FromWebContents(web_contents_getter.Run());
}
} // namespace prerender