blob: 3ee1eff3006bd13ae0b5c96f33d07aab8b3d55ca [file] [log] [blame]
// Copyright 2017 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 "content/browser/loader/navigation_url_loader_network_service.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/task_scheduler/post_task.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/appcache/appcache_navigation_handle.h"
#include "content/browser/appcache/appcache_request_handler.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/download/download_stats.h"
#include "content/browser/file_url_loader_factory.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/navigation_request_info.h"
#include "content/browser/loader/navigation_resource_handler.h"
#include "content/browser/loader/navigation_url_loader_delegate.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/browser/loader/url_loader_request_handler.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/service_worker/service_worker_navigation_handle.h"
#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
#include "content/browser/service_worker/service_worker_request_handler.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webui/url_data_manager_backend.h"
#include "content/browser/webui/web_ui_url_loader_factory.h"
#include "content/common/navigation_subresource_loader_params.h"
#include "content/common/throttling_url_loader.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/navigation_data.h"
#include "content/public/browser/navigation_ui_data.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/stream_handle.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_loader_factory.mojom.h"
#include "content/public/common/url_utils.h"
#include "net/base/load_flags.h"
#include "net/http/http_content_disposition.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/redirect_util.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "services/network/public/interfaces/request_context_frame_type.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/WebKit/common/mime_util/mime_util.h"
namespace content {
namespace {
// Request ID for browser initiated requests. We start at -2 on the same lines
// as ResourceDispatcherHostImpl.
int g_next_request_id = -2;
GlobalRequestID MakeGlobalRequestID() {
return GlobalRequestID(-1, g_next_request_id--);
}
size_t GetCertificateChainsSizeInKB(const net::SSLInfo& ssl_info) {
base::Pickle cert_pickle;
ssl_info.cert->Persist(&cert_pickle);
base::Pickle unverified_cert_pickle;
ssl_info.unverified_cert->Persist(&unverified_cert_pickle);
return (cert_pickle.size() + unverified_cert_pickle.size()) / 1000;
}
WebContents* GetWebContentsFromFrameTreeNodeID(int frame_tree_node_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!frame_tree_node)
return nullptr;
return WebContentsImpl::FromFrameTreeNode(frame_tree_node);
}
const net::NetworkTrafficAnnotationTag kNavigationUrlLoaderTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("navigation_url_loader", R"(
semantics {
sender: "Navigation URL Loader"
description:
"This request is issued by a main frame navigation to fetch the "
"content of the page that is being navigated to."
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, HTTP "
"headers, and request body. Requests may include cookies and "
"site-specific credentials."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature cannot be disabled."
chrome_policy {
URLBlacklist {
URLBlacklist: { entries: '*' }
}
}
chrome_policy {
URLWhitelist {
URLWhitelist { }
}
}
}
comments:
"Chrome would be unable to navigate to websites without this type of "
"request. Using either URLBlacklist or URLWhitelist policies (or a "
"combination of both) limits the scope of these requests."
)");
// TODO(arthursonzogni): IsDownload can't be determined only by the response's
// headers. The response's body might contain information to guess it.
// See MimeSniffingResourceHandler.
bool IsDownload(const ResourceResponse& response,
const GURL& url,
const std::vector<GURL>& url_chain,
const base::Optional<url::Origin>& initiator_origin,
const base::Optional<std::string>& suggested_filename) {
if (response.head.headers) {
GURL url_chain_back = url_chain.empty() ? url : url_chain.back();
bool is_cross_origin =
(initiator_origin.has_value() && !url_chain_back.SchemeIsBlob() &&
!url_chain_back.SchemeIs(url::kAboutScheme) &&
!url_chain_back.SchemeIs(url::kDataScheme) &&
initiator_origin->GetURL() != url_chain_back.GetOrigin());
std::string disposition;
if (response.head.headers->GetNormalizedHeader("content-disposition",
&disposition) &&
!disposition.empty() &&
net::HttpContentDisposition(disposition, std::string())
.is_attachment()) {
return true;
} else if (suggested_filename.has_value() && !is_cross_origin) {
return true;
} else if (GetContentClient()->browser()->ShouldForceDownloadResource(
url, response.head.mime_type)) {
return true;
} else if (response.head.mime_type == "multipart/related") {
// TODO(https://crbug.com/790734): retrieve the new NavigationUIData from
// the request and and pass it to AllowRenderingMhtmlOverHttp().
return !GetContentClient()->browser()->AllowRenderingMhtmlOverHttp(
nullptr);
}
// TODO(qinmin): Check whether this is special-case user script that needs
// to be downloaded.
}
if (blink::IsSupportedMimeType(response.head.mime_type))
return false;
// TODO(qinmin): Check whether there is a plugin handler.
if (suggested_filename.has_value())
RecordDownloadCount(CROSS_ORIGIN_DOWNLOAD_WITHOUT_CONTENT_DISPOSITION);
return (!response.head.headers ||
response.head.headers->response_code() / 100 == 2);
}
} // namespace
// Kept around during the lifetime of the navigation request, and is
// responsible for dispatching a ResourceRequest to the appropriate
// URLLoader. In order to get the right URLLoader it builds a vector
// of URLLoaderRequestHandler's and successively calls MaybeCreateLoader
// on each until the request is successfully handled. The same sequence
// may be performed multiple times when redirects happen.
// TODO(michaeln): Expose this class and add more unittests.
class NavigationURLLoaderNetworkService::URLLoaderRequestController
: public mojom::URLLoaderClient {
public:
URLLoaderRequestController(
std::vector<std::unique_ptr<URLLoaderRequestHandler>> initial_handlers,
std::unique_ptr<network::ResourceRequest> resource_request,
ResourceContext* resource_context,
scoped_refptr<URLLoaderFactoryGetter> default_url_loader_factory_getter,
const GURL& url,
base::Optional<url::Origin> initiator_origin,
base::Optional<std::string> suggested_filename,
const base::WeakPtr<NavigationURLLoaderNetworkService>& owner)
: handlers_(std::move(initial_handlers)),
resource_request_(std::move(resource_request)),
resource_context_(resource_context),
default_url_loader_factory_getter_(default_url_loader_factory_getter),
url_(url),
initiator_origin_(initiator_origin),
suggested_filename_(suggested_filename),
owner_(owner),
response_loader_binding_(this),
weak_factory_(this) {}
~URLLoaderRequestController() override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
static uint32_t GetURLLoaderOptions(bool is_main_frame) {
uint32_t options = mojom::kURLLoadOptionSendSSLInfoWithResponse;
if (is_main_frame)
options |= mojom::kURLLoadOptionSendSSLInfoForCertificateError;
if (base::FeatureList::IsEnabled(features::kNetworkService)) {
options |= mojom::kURLLoadOptionSniffMimeType;
} else {
// TODO(arthursonzogni): This is a temporary option. Remove this as soon
// as the InterceptingResourceHandler is removed.
// See https://crbug.com/791049.
options |= mojom::kURLLoadOptionPauseOnResponseStarted;
}
return options;
}
void CreateNonNetworkServiceURLLoader(
net::URLRequestContextGetter* url_request_context_getter,
storage::FileSystemContext* upload_file_system_context,
std::unique_ptr<NavigationRequestInfo> request_info,
std::unique_ptr<NavigationUIData> navigation_ui_data,
ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
AppCacheNavigationHandleCore* appcache_handle_core,
mojom::URLLoaderRequest url_loader,
mojom::URLLoaderClientPtr url_loader_client) {
DCHECK(!base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// The ResourceDispatcherHostImpl can be null in unit tests.
if (ResourceDispatcherHostImpl::Get()) {
ResourceDispatcherHostImpl::Get()->BeginNavigationRequest(
resource_context_, url_request_context_getter->GetURLRequestContext(),
upload_file_system_context, *request_info,
std::move(navigation_ui_data), nullptr, std::move(url_loader_client),
std::move(url_loader), service_worker_navigation_handle_core,
appcache_handle_core,
GetURLLoaderOptions(request_info->is_main_frame),
&global_request_id_);
}
// TODO(arthursonzogni): Detect when the ResourceDispatcherHost didn't
// create a URLLoader. When it doesn't, do not send OnRequestStarted().
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NavigationURLLoaderNetworkService::OnRequestStarted,
owner_, base::TimeTicks::Now()));
}
// TODO(arthursonzogni): See if this could eventually be unified with Start().
void StartWithoutNetworkService(
net::URLRequestContextGetter* url_request_context_getter,
storage::FileSystemContext* upload_file_system_context,
ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
AppCacheNavigationHandleCore* appcache_handle_core,
std::unique_ptr<NavigationRequestInfo> request_info,
std::unique_ptr<NavigationUIData> navigation_ui_data) {
DCHECK(!base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!started_);
started_ = true;
StartLoaderCallback create_url_loader = base::BindOnce(
&URLLoaderRequestController::CreateNonNetworkServiceURLLoader,
weak_factory_.GetWeakPtr(),
base::Unretained(url_request_context_getter),
base::Unretained(upload_file_system_context), std::move(request_info),
std::move(navigation_ui_data),
base::Unretained(service_worker_navigation_handle_core),
base::Unretained(appcache_handle_core));
network::ResourceRequest resource_request;
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
std::move(create_url_loader),
std::vector<std::unique_ptr<content::URLLoaderThrottle>>(),
/* routing_id = */ -1, &resource_request,
/* client = */ this, kNavigationUrlLoaderTrafficAnnotation,
base::ThreadTaskRunnerHandle::Get());
}
void Start(
ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
AppCacheNavigationHandleCore* appcache_handle_core,
std::unique_ptr<NavigationRequestInfo> request_info,
std::unique_ptr<NavigationUIData> navigation_ui_data,
mojom::URLLoaderFactoryPtrInfo factory_for_webui,
int frame_tree_node_id,
std::unique_ptr<service_manager::Connector> connector) {
DCHECK(base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!started_);
global_request_id_ = MakeGlobalRequestID();
frame_tree_node_id_ = frame_tree_node_id;
started_ = true;
web_contents_getter_ =
base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id);
navigation_ui_data_ = std::move(navigation_ui_data);
const ResourceType resource_type = request_info->is_main_frame
? RESOURCE_TYPE_MAIN_FRAME
: RESOURCE_TYPE_SUB_FRAME;
if (resource_request_->request_body) {
GetBodyBlobDataHandles(resource_request_->request_body.get(),
resource_context_, &blob_handles_);
}
// Requests to WebUI scheme won't get redirected to/from other schemes
// or be intercepted, so we just let it go here.
if (factory_for_webui.is_valid()) {
webui_factory_ptr_.Bind(std::move(factory_for_webui));
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
webui_factory_ptr_.get(),
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_, navigation_ui_data_.get()),
0 /* routing_id */, 0 /* request_id? */, mojom::kURLLoadOptionNone,
resource_request_.get(), this, kNavigationUrlLoaderTrafficAnnotation,
base::ThreadTaskRunnerHandle::Get());
return;
}
if (service_worker_navigation_handle_core) {
network::mojom::RequestContextFrameType frame_type =
request_info->is_main_frame
? network::mojom::RequestContextFrameType::kTopLevel
: network::mojom::RequestContextFrameType::kNested;
storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext(
GetChromeBlobStorageContextForResourceContext(resource_context_));
std::unique_ptr<URLLoaderRequestHandler> service_worker_handler =
ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
*resource_request_, resource_context_,
service_worker_navigation_handle_core, blob_storage_context,
request_info->begin_params->skip_service_worker, resource_type,
request_info->begin_params->request_context_type, frame_type,
request_info->are_ancestors_secure,
request_info->common_params.post_data, web_contents_getter_);
if (service_worker_handler)
handlers_.push_back(std::move(service_worker_handler));
}
if (appcache_handle_core) {
std::unique_ptr<URLLoaderRequestHandler> appcache_handler =
AppCacheRequestHandler::InitializeForNavigationNetworkService(
*resource_request_, appcache_handle_core,
default_url_loader_factory_getter_.get());
if (appcache_handler)
handlers_.push_back(std::move(appcache_handler));
}
Restart();
}
// This could be called multiple times to follow a chain of redirects.
void Restart() {
DCHECK(base::FeatureList::IsEnabled(features::kNetworkService));
// Clear |url_loader_| if it's not the default one (network). This allows
// the restarted request to use a new loader, instead of, e.g., reusing the
// AppCache or service worker loader. For an optimization, we keep and reuse
// the default url loader if the all |handlers_| doesn't handle the
// redirected request.
if (!default_loader_used_)
url_loader_.reset();
handler_index_ = 0;
received_response_ = false;
MaybeStartLoader(nullptr /* handler */, StartLoaderCallback());
}
// |handler| is the one who called this method (as a LoaderCallback), nullptr
// if this method is not called by a handler.
// |start_loader_callback| is the callback given by the |handler|, non-null
// if the |handler| wants to handle the request.
void MaybeStartLoader(URLLoaderRequestHandler* handler,
StartLoaderCallback start_loader_callback) {
DCHECK(base::FeatureList::IsEnabled(features::kNetworkService));
if (start_loader_callback) {
// |handler| wants to handle the request.
DCHECK(handler);
default_loader_used_ = false;
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
std::move(start_loader_callback),
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_, navigation_ui_data_.get()),
frame_tree_node_id_, resource_request_.get(), this,
kNavigationUrlLoaderTrafficAnnotation,
base::ThreadTaskRunnerHandle::Get());
subresource_loader_params_ =
handler->MaybeCreateSubresourceLoaderParams();
return;
}
// Before falling back to the next handler, see if |handler| still wants
// to give additional info to the frame for subresource loading.
// In that case we will just fall back to the default loader (i.e.
// won't go on to the next handlers) but send the subresource_loader_params
// to the child process. This is necessary for correctness in the cases
// where, e.g. there's a controlling ServiceWorker that doesn't handle main
// resource loading, but may still want to control the page and/or handle
// subresource loading. In that case we want to skip APpCache.
if (handler) {
subresource_loader_params_ =
handler->MaybeCreateSubresourceLoaderParams();
// If non-null |subresource_loader_params_| is returned, make sure
// we skip the next handlers.
if (subresource_loader_params_)
handler_index_ = handlers_.size();
}
// See if the next handler wants to handle the request.
if (handler_index_ < handlers_.size()) {
auto* next_handler = handlers_[handler_index_++].get();
next_handler->MaybeCreateLoader(
*resource_request_, resource_context_,
base::BindOnce(&URLLoaderRequestController::MaybeStartLoader,
base::Unretained(this), next_handler));
return;
}
if (url_loader_) {
DCHECK(!redirect_info_.new_url.is_empty());
url_loader_->FollowRedirect();
return;
}
mojom::URLLoaderFactory* factory = nullptr;
DCHECK_EQ(handlers_.size(), handler_index_);
if (resource_request_->url.SchemeIs(url::kBlobScheme)) {
factory = default_url_loader_factory_getter_->GetBlobFactory();
} else if (!IsURLHandledByNetworkService(resource_request_->url) &&
!resource_request_->url.SchemeIs(url::kDataScheme)) {
mojom::URLLoaderFactoryPtr& non_network_factory =
non_network_url_loader_factories_[resource_request_->url.scheme()];
if (!non_network_factory.is_bound()) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NavigationURLLoaderNetworkService ::
BindNonNetworkURLLoaderFactoryRequest,
owner_, resource_request_->url,
mojo::MakeRequest(&non_network_factory)));
}
factory = non_network_factory.get();
} else {
factory = default_url_loader_factory_getter_->GetNetworkFactory();
default_loader_used_ = true;
}
url_chain_.push_back(resource_request_->url);
uint32_t options = GetURLLoaderOptions(resource_request_->resource_type ==
RESOURCE_TYPE_MAIN_FRAME);
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
factory,
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_, navigation_ui_data_.get()),
frame_tree_node_id_, 0 /* request_id? */, options,
resource_request_.get(), this, kNavigationUrlLoaderTrafficAnnotation,
base::ThreadTaskRunnerHandle::Get());
}
void FollowRedirect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!redirect_info_.new_url.is_empty());
DCHECK(!response_url_loader_);
DCHECK(url_loader_);
// TODO(arthursonzogni): We might need to go through the rest of the
// function once there are several types of URLLoader handling the
// navigation, even in non network-service mode.
if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
url_loader_->FollowRedirect();
return;
}
// Update resource_request_ and call Restart to give our handlers_ a chance
// at handling the new location. If no handler wants to take over, we'll
// use the existing url_loader to follow the redirect, see MaybeStartLoader.
// TODO(michaeln): This is still WIP and is based on URLRequest::Redirect,
// there likely remains more to be done.
// a. For subframe navigations, the Origin header may need to be modified
// differently?
// b. How should redirect_info_.referred_token_binding_host be handled?
bool should_clear_upload = false;
net::RedirectUtil::UpdateHttpRequest(
resource_request_->url, resource_request_->method, redirect_info_,
&resource_request_->headers, &should_clear_upload);
if (should_clear_upload) {
// The request body is no longer applicable.
resource_request_->request_body = nullptr;
blob_handles_.clear();
}
resource_request_->url = redirect_info_.new_url;
resource_request_->method = redirect_info_.new_method;
resource_request_->site_for_cookies = redirect_info_.new_site_for_cookies;
resource_request_->referrer = GURL(redirect_info_.new_referrer);
resource_request_->referrer_policy = redirect_info_.new_referrer_policy;
url_chain_.push_back(redirect_info_.new_url);
Restart();
}
base::Optional<SubresourceLoaderParams> TakeSubresourceLoaderParams() {
return std::move(subresource_loader_params_);
}
private:
// mojom::URLLoaderClient implementation:
void OnReceiveResponse(
const ResourceResponseHead& head,
const base::Optional<net::SSLInfo>& ssl_info,
mojom::DownloadedTempFilePtr downloaded_file) override {
received_response_ = true;
// If the default loader (network) was used to handle the URL load request
// we need to see if the handlers want to potentially create a new loader
// for the response. e.g. AppCache.
if (MaybeCreateLoaderForResponse(head))
return;
mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints;
if (url_loader_) {
url_loader_client_endpoints = url_loader_->Unbind();
} else {
url_loader_client_endpoints = mojom::URLLoaderClientEndpoints::New(
response_url_loader_.PassInterface(),
response_loader_binding_.Unbind());
}
scoped_refptr<ResourceResponse> response(new ResourceResponse());
response->head = head;
bool is_download;
bool is_stream;
std::unique_ptr<NavigationData> cloned_navigation_data;
if (base::FeatureList::IsEnabled(features::kNetworkService)) {
is_download = IsDownload(*response.get(), url_, url_chain_,
initiator_origin_, suggested_filename_);
is_stream = false;
} else {
ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get();
net::URLRequest* url_request = rdh->GetURLRequest(global_request_id_);
ResourceRequestInfoImpl* info =
ResourceRequestInfoImpl::ForRequest(url_request);
is_download = info->IsDownload();
is_stream = info->is_stream();
if (rdh->delegate()) {
NavigationData* navigation_data =
rdh->delegate()->GetNavigationData(url_request);
// Clone the embedder's NavigationData before moving it to the UI
// thread.
if (navigation_data)
cloned_navigation_data = navigation_data->Clone();
}
}
// Make a copy of the ResourceResponse before it is passed to another
// thread.
//
// TODO(davidben): This copy could be avoided if ResourceResponse weren't
// reference counted and the loader stack passed unique ownership of the
// response. https://crbug.com/416050
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NavigationURLLoaderNetworkService::OnReceiveResponse,
owner_, response->DeepCopy(),
std::move(url_loader_client_endpoints),
std::move(ssl_info), std::move(cloned_navigation_data),
global_request_id_, is_download, is_stream,
base::Passed(&downloaded_file)));
}
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
const ResourceResponseHead& head) override {
if (--redirect_limit_ == 0) {
OnComplete(
network::URLLoaderCompletionStatus(net::ERR_TOO_MANY_REDIRECTS));
return;
}
// Store the redirect_info for later use in FollowRedirect where we give
// our handlers_ a chance to intercept the request for the new location.
redirect_info_ = redirect_info;
scoped_refptr<ResourceResponse> response(new ResourceResponse());
response->head = head;
url_ = redirect_info.new_url;
// Make a copy of the ResourceResponse before it is passed to another
// thread.
//
// TODO(davidben): This copy could be avoided if ResourceResponse weren't
// reference counted and the loader stack passed unique ownership of the
// response. https://crbug.com/416050
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NavigationURLLoaderNetworkService::OnReceiveRedirect,
owner_, redirect_info, response->DeepCopy()));
}
void OnDataDownloaded(int64_t data_length, int64_t encoded_length) override {}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override {}
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {}
void OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle) override {
// Not reached. At this point, the loader and client endpoints must have
// been unbound and forwarded to the renderer.
CHECK(false);
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
UMA_HISTOGRAM_BOOLEAN(
"Navigation.URLLoaderNetworkService.OnCompleteHadSSLInfo",
status.ssl_info.has_value());
if (status.ssl_info.has_value()) {
UMA_HISTOGRAM_MEMORY_KB(
"Navigation.URLLoaderNetworkService.OnCompleteCertificateChainsSize",
GetCertificateChainsSizeInKB(status.ssl_info.value()));
}
if (status.error_code != net::OK && !received_response_) {
// If the default loader (network) was used to handle the URL load
// request we need to see if the handlers want to potentially create a
// new loader for the response. e.g. AppCache.
if (MaybeCreateLoaderForResponse(ResourceResponseHead()))
return;
}
status_ = status;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NavigationURLLoaderNetworkService::OnComplete, owner_,
status));
}
// Returns true if a handler wants to handle the response, i.e. return a
// different response. For e.g. AppCache may have fallback content.
bool MaybeCreateLoaderForResponse(const ResourceResponseHead& response) {
if (!base::FeatureList::IsEnabled(features::kNetworkService))
return false;
if (!default_loader_used_)
return false;
for (auto& handler : handlers_) {
mojom::URLLoaderClientRequest response_client_request;
if (handler->MaybeCreateLoaderForResponse(response, &response_url_loader_,
&response_client_request)) {
response_loader_binding_.Bind(std::move(response_client_request));
default_loader_used_ = false;
url_loader_.reset();
return true;
}
}
return false;
}
std::vector<std::unique_ptr<URLLoaderRequestHandler>> handlers_;
size_t handler_index_ = 0;
std::unique_ptr<network::ResourceRequest> resource_request_;
int frame_tree_node_id_ = 0;
GlobalRequestID global_request_id_;
net::RedirectInfo redirect_info_;
int redirect_limit_ = net::URLRequest::kMaxRedirects;
ResourceContext* resource_context_;
base::Callback<WebContents*()> web_contents_getter_;
std::unique_ptr<NavigationUIData> navigation_ui_data_;
scoped_refptr<URLLoaderFactoryGetter> default_url_loader_factory_getter_;
mojom::URLLoaderFactoryPtr webui_factory_ptr_;
std::unique_ptr<ThrottlingURLLoader> url_loader_;
BlobHandles blob_handles_;
std::vector<GURL> url_chain_;
// Current URL that is being navigated, updated after redirection.
GURL url_;
base::Optional<url::Origin> initiator_origin_;
// If this request was triggered by an anchor tag with a download attribute,
// the |suggested_filename_| will be the (possibly empty) value of said
// attribute.
base::Optional<std::string> suggested_filename_;
// Currently used by the AppCache loader to pass its factory to the
// renderer which enables it to handle subresources.
base::Optional<SubresourceLoaderParams> subresource_loader_params_;
// This is referenced only on the UI thread.
base::WeakPtr<NavigationURLLoaderNetworkService> owner_;
// Set to true if the default URLLoader (network service) was used for the
// current navigation.
bool default_loader_used_ = false;
// URLLoaderClient binding for loaders created for responses received from the
// network loader.
mojo::Binding<mojom::URLLoaderClient> response_loader_binding_;
// URLLoader instance for response loaders, i.e loaders created for handing
// responses received from the network URLLoader.
mojom::URLLoaderPtr response_url_loader_;
// Set to true if we receive a valid response from a URLLoader, i.e.
// URLLoaderClient::OnReceivedResponse() is called.
bool received_response_ = false;
bool started_ = false;
// Lazily initialized and used in the case of non-network resource
// navigations. Keyed by URL scheme.
std::map<std::string, mojom::URLLoaderFactoryPtr>
non_network_url_loader_factories_;
// The completion status if it has been received. This is needed to handle
// the case that the response is intercepted by download, and OnComplete() is
// already called while we are transferring the |url_loader_| and response
// body to download code.
base::Optional<network::URLLoaderCompletionStatus> status_;
base::WeakPtrFactory<URLLoaderRequestController> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController);
};
// TODO(https://crbug.com/790734): pass |navigation_ui_data| along with the
// request so that it could be modified.
NavigationURLLoaderNetworkService::NavigationURLLoaderNetworkService(
ResourceContext* resource_context,
StoragePartition* storage_partition,
std::unique_ptr<NavigationRequestInfo> request_info,
std::unique_ptr<NavigationUIData> navigation_ui_data,
ServiceWorkerNavigationHandle* service_worker_navigation_handle,
AppCacheNavigationHandle* appcache_handle,
NavigationURLLoaderDelegate* delegate,
std::vector<std::unique_ptr<URLLoaderRequestHandler>> initial_handlers)
: delegate_(delegate),
allow_download_(request_info->common_params.allow_download),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
int frame_tree_node_id = request_info->frame_tree_node_id;
TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP1(
"navigation", "Navigation timeToResponseStarted", this,
request_info->common_params.navigation_start, "FrameTreeNode id",
frame_tree_node_id);
ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core =
service_worker_navigation_handle
? service_worker_navigation_handle->core()
: nullptr;
AppCacheNavigationHandleCore* appcache_handle_core =
appcache_handle ? appcache_handle->core() : nullptr;
if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
DCHECK(!request_controller_);
request_controller_ = std::make_unique<URLLoaderRequestController>(
/* initial_handlers = */
std::vector<std::unique_ptr<URLLoaderRequestHandler>>(),
/* resource_request = */ nullptr, resource_context,
/* default_url_factory_getter = */ nullptr,
request_info->common_params.url,
request_info->begin_params->initiator_origin,
request_info->begin_params->suggested_filename,
weak_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(
&URLLoaderRequestController::StartWithoutNetworkService,
base::Unretained(request_controller_.get()),
base::Unretained(storage_partition->GetURLRequestContext()),
base::Unretained(storage_partition->GetFileSystemContext()),
base::Unretained(service_worker_navigation_handle_core),
base::Unretained(appcache_handle_core),
base::Passed(std::move(request_info)),
base::Passed(std::move(navigation_ui_data))));
return;
}
// TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here.
auto new_request = std::make_unique<network::ResourceRequest>();
new_request->method = request_info->common_params.method;
new_request->url = request_info->common_params.url;
new_request->site_for_cookies = request_info->site_for_cookies;
new_request->priority = net::HIGHEST;
new_request->render_frame_id = frame_tree_node_id;
// The code below to set fields like request_initiator, referrer, etc has
// been copied from ResourceDispatcherHostImpl. We did not refactor the
// common code into a function, because RDHI uses accessor functions on the
// URLRequest class to set these fields. whereas we use ResourceRequest here.
new_request->request_initiator = request_info->begin_params->initiator_origin;
new_request->referrer = request_info->common_params.referrer.url;
new_request->referrer_policy = Referrer::ReferrerPolicyForUrlRequest(
request_info->common_params.referrer.policy);
new_request->headers.AddHeadersFromString(
request_info->begin_params->headers);
new_request->resource_type = request_info->is_main_frame
? RESOURCE_TYPE_MAIN_FRAME
: RESOURCE_TYPE_SUB_FRAME;
int load_flags = request_info->begin_params->load_flags;
load_flags |= net::LOAD_VERIFY_EV_CERT;
if (request_info->is_main_frame)
load_flags |= net::LOAD_MAIN_FRAME_DEPRECATED;
// Sync loads should have maximum priority and should be the only
// requests that have the ignore limits flag set.
DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS));
new_request->load_flags = load_flags;
new_request->request_body = request_info->common_params.post_data.get();
new_request->report_raw_headers = request_info->report_raw_headers;
new_request->allow_download = allow_download_;
new_request->enable_load_timing = true;
new_request->fetch_request_mode = network::mojom::FetchRequestMode::kNavigate;
new_request->fetch_credentials_mode =
network::mojom::FetchCredentialsMode::kInclude;
new_request->fetch_redirect_mode =
static_cast<int>(FetchRedirectMode::MANUAL_MODE);
// Check if a web UI scheme wants to handle this request.
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
mojom::URLLoaderFactoryPtrInfo factory_for_webui;
const auto& schemes = URLDataManagerBackend::GetWebUISchemes();
std::string scheme = new_request->url.scheme();
if (std::find(schemes.begin(), schemes.end(), scheme) != schemes.end()) {
factory_for_webui =
CreateWebUIURLLoader(frame_tree_node->current_frame_host(), scheme)
.PassInterface();
}
auto* partition = static_cast<StoragePartitionImpl*>(storage_partition);
DCHECK(!request_controller_);
request_controller_ = std::make_unique<URLLoaderRequestController>(
std::move(initial_handlers), std::move(new_request), resource_context,
partition->url_loader_factory_getter(), request_info->common_params.url,
request_info->begin_params->initiator_origin,
request_info->begin_params->suggested_filename,
weak_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&URLLoaderRequestController::Start,
base::Unretained(request_controller_.get()),
base::Unretained(service_worker_navigation_handle_core),
base::Unretained(appcache_handle_core),
base::Passed(std::move(request_info)),
base::Passed(std::move(navigation_ui_data)),
base::Passed(std::move(factory_for_webui)),
frame_tree_node_id,
base::Passed(ServiceManagerConnection::GetForProcess()
->GetConnector()
->Clone())));
non_network_url_loader_factories_[url::kFileScheme] =
std::make_unique<FileURLLoaderFactory>(
partition->browser_context()->GetPath(),
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
if (frame_tree_node) {
// |frame_tree_node| may be null in some unit test environments.
GetContentClient()
->browser()
->RegisterNonNetworkNavigationURLLoaderFactories(
frame_tree_node->current_frame_host(),
&non_network_url_loader_factories_);
}
}
NavigationURLLoaderNetworkService::~NavigationURLLoaderNetworkService() {
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
request_controller_.release());
}
void NavigationURLLoaderNetworkService::FollowRedirect() {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&URLLoaderRequestController::FollowRedirect,
base::Unretained(request_controller_.get())));
}
void NavigationURLLoaderNetworkService::ProceedWithResponse() {}
void NavigationURLLoaderNetworkService::OnReceiveResponse(
scoped_refptr<ResourceResponse> response,
mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
const base::Optional<net::SSLInfo>& maybe_ssl_info,
std::unique_ptr<NavigationData> navigation_data,
const GlobalRequestID& global_request_id,
bool is_download,
bool is_stream,
mojom::DownloadedTempFilePtr downloaded_file) {
TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", this,
"&NavigationURLLoaderNetworkService", this, "success",
true);
// TODO(scottmg): This needs to do more of what
// NavigationResourceHandler::OnResponseStarted() does.
net::SSLInfo ssl_info;
if (maybe_ssl_info.has_value())
ssl_info = maybe_ssl_info.value();
delegate_->OnResponseStarted(
std::move(response), std::move(url_loader_client_endpoints), nullptr,
std::move(ssl_info), std::move(navigation_data), global_request_id,
allow_download_ && is_download, is_stream,
request_controller_->TakeSubresourceLoaderParams());
}
void NavigationURLLoaderNetworkService::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
scoped_refptr<ResourceResponse> response) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->OnRequestRedirected(redirect_info, std::move(response));
}
void NavigationURLLoaderNetworkService::OnComplete(
const network::URLLoaderCompletionStatus& status) {
if (status.error_code == net::OK)
return;
TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", this,
"&NavigationURLLoaderNetworkService", this, "success",
false);
delegate_->OnRequestFailed(status.exists_in_cache, status.error_code,
status.ssl_info);
}
void NavigationURLLoaderNetworkService::OnRequestStarted(
base::TimeTicks timestamp) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->OnRequestStarted(timestamp);
}
void NavigationURLLoaderNetworkService::BindNonNetworkURLLoaderFactoryRequest(
const GURL& url,
mojom::URLLoaderFactoryRequest factory) {
auto it = non_network_url_loader_factories_.find(url.scheme());
if (it == non_network_url_loader_factories_.end()) {
DVLOG(1) << "Ignoring request with unknown scheme: " << url.spec();
return;
}
it->second->Clone(std::move(factory));
}
} // namespace content