blob: df66bde52c66056a43039c906d258dbab0d14eb7 [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/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/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_resource_throttle.h"
#include "content/browser/loader/navigation_url_loader_delegate.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/throttling_url_loader.h"
#include "content/common/url_loader_factory.mojom.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/ssl_status.h"
#include "content/public/browser/stream_handle.h"
#include "content/public/common/referrer.h"
#include "content/public/common/url_constants.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_request_context.h"
#include "services/service_manager/public/cpp/connector.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;
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);
}
} // 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.
class NavigationURLLoaderNetworkService::URLLoaderRequestController
: public mojom::URLLoaderClient {
public:
URLLoaderRequestController(
std::unique_ptr<ResourceRequest> resource_request,
ResourceContext* resource_context,
scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter,
const base::WeakPtr<NavigationURLLoaderNetworkService>& owner)
: resource_request_(std::move(resource_request)),
resource_context_(resource_context),
url_loader_factory_getter_(url_loader_factory_getter),
owner_(owner) {}
~URLLoaderRequestController() override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
void Start(
ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
AppCacheNavigationHandleCore* appcache_handle_core,
std::unique_ptr<NavigationRequestInfo> request_info,
mojom::URLLoaderFactoryPtrInfo factory_for_webui,
const base::Callback<WebContents*(void)>& web_contents_getter,
std::unique_ptr<service_manager::Connector> connector) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
web_contents_getter_ = web_contents_getter;
const ResourceType resource_type = request_info->is_main_frame
? RESOURCE_TYPE_MAIN_FRAME
: RESOURCE_TYPE_SUB_FRAME;
if (resource_request_->request_body) {
AttachRequestBodyBlobDataHandles(resource_request_->request_body.get(),
resource_context_);
}
// 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_),
0 /* routing_id? */, 0 /* request_id? */,
mojom::kURLLoadOptionSendSSLInfo, *resource_request_, this,
net::MutableNetworkTrafficAnnotationTag(NO_TRAFFIC_ANNOTATION_YET));
return;
}
DCHECK(handlers_.empty());
if (service_worker_navigation_handle_core) {
RequestContextFrameType frame_type =
request_info->is_main_frame ? REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL
: REQUEST_CONTEXT_FRAME_TYPE_NESTED;
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);
if (appcache_handler)
handlers_.push_back(std::move(appcache_handler));
}
Restart();
}
// This could be called multiple times.
void Restart() {
handler_index_ = 0;
MaybeStartLoader(StartLoaderCallback());
}
void MaybeStartLoader(StartLoaderCallback start_loader_callback) {
if (start_loader_callback) {
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
std::move(start_loader_callback),
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_),
*resource_request_, this);
return;
}
if (handler_index_ < handlers_.size()) {
handlers_[handler_index_++]->MaybeCreateLoader(
*resource_request_, resource_context_,
base::BindOnce(&URLLoaderRequestController::MaybeStartLoader,
base::Unretained(this)));
return;
}
mojom::URLLoaderFactory* factory = nullptr;
DCHECK_EQ(handlers_.size(), handler_index_);
if (resource_request_->url.SchemeIs(url::kBlobScheme)) {
factory = url_loader_factory_getter_->GetBlobFactory()->get();
} else {
factory = url_loader_factory_getter_->GetNetworkFactory()->get();
}
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
factory,
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_),
0 /* routing_id? */, 0 /* request_id? */,
mojom::kURLLoadOptionSendSSLInfo, *resource_request_, this,
net::MutableNetworkTrafficAnnotationTag(NO_TRAFFIC_ANNOTATION_YET));
}
void FollowRedirect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(url_loader_);
url_loader_->FollowRedirect();
}
private:
// mojom::URLLoaderClient implementation:
void OnReceiveResponse(
const ResourceResponseHead& head,
const base::Optional<net::SSLInfo>& ssl_info,
mojom::DownloadedTempFilePtr downloaded_file) override {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NavigationURLLoaderNetworkService::OnReceiveResponse,
owner_, head, ssl_info, base::Passed(&downloaded_file)));
}
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
const ResourceResponseHead& head) override {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NavigationURLLoaderNetworkService::OnReceiveRedirect,
owner_, redirect_info, head));
}
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 body) override {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&NavigationURLLoaderNetworkService::OnStartLoadingResponseBody,
owner_, base::Passed(&body)));
}
void OnComplete(
const ResourceRequestCompletionStatus& completion_status) override {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NavigationURLLoaderNetworkService::OnComplete, owner_,
completion_status));
}
std::vector<std::unique_ptr<URLLoaderRequestHandler>> handlers_;
size_t handler_index_ = 0;
std::unique_ptr<ResourceRequest> resource_request_;
ResourceContext* resource_context_;
base::Callback<WebContents*()> web_contents_getter_;
scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter_;
mojom::URLLoaderFactoryPtr webui_factory_ptr_;
std::unique_ptr<ThrottlingURLLoader> url_loader_;
// This is referenced only on the UI thread.
base::WeakPtr<NavigationURLLoaderNetworkService> owner_;
DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController);
};
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)
: delegate_(delegate), weak_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP1(
"navigation", "Navigation timeToResponseStarted", this,
request_info->common_params.navigation_start, "FrameTreeNode id",
request_info->frame_tree_node_id);
// TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here.
auto new_request = base::MakeUnique<ResourceRequest>();
new_request->method = request_info->common_params.method;
new_request->url = request_info->common_params.url;
new_request->first_party_for_cookies = request_info->first_party_for_cookies;
new_request->priority = net::HIGHEST;
// 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 = request_info->common_params.referrer.policy;
new_request->headers = request_info->begin_params.headers;
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();
int frame_tree_node_id = request_info->frame_tree_node_id;
// Check if a web UI scheme wants to handle this request.
mojom::URLLoaderFactoryPtrInfo factory_for_webui;
const auto& schemes = URLDataManagerBackend::GetWebUISchemes();
if (std::find(schemes.begin(), schemes.end(), new_request->url.scheme()) !=
schemes.end()) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
factory_for_webui = CreateWebUIURLLoader(frame_tree_node).PassInterface();
}
g_next_request_id--;
DCHECK(!request_controller_);
request_controller_ = base::MakeUnique<URLLoaderRequestController>(
std::move(new_request), resource_context,
static_cast<StoragePartitionImpl*>(storage_partition)
->url_loader_factory_getter(),
weak_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(
&URLLoaderRequestController::Start,
base::Unretained(request_controller_.get()),
service_worker_navigation_handle
? service_worker_navigation_handle->core()
: nullptr,
appcache_handle ? appcache_handle->core() : nullptr,
base::Passed(std::move(request_info)),
base::Passed(std::move(factory_for_webui)),
base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id),
base::Passed(ServiceManagerConnection::GetForProcess()
->GetConnector()
->Clone())));
}
NavigationURLLoaderNetworkService::~NavigationURLLoaderNetworkService() {
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
request_controller_.release());
}
void NavigationURLLoaderNetworkService::FollowRedirect() {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&URLLoaderRequestController::FollowRedirect,
base::Unretained(request_controller_.get())));
}
void NavigationURLLoaderNetworkService::ProceedWithResponse() {}
void NavigationURLLoaderNetworkService::OnReceiveResponse(
const ResourceResponseHead& head,
const base::Optional<net::SSLInfo>& ssl_info,
mojom::DownloadedTempFilePtr downloaded_file) {
// TODO(scottmg): This needs to do more of what
// NavigationResourceHandler::OnReponseStarted() does. Or maybe in
// OnStartLoadingResponseBody().
if (ssl_info && ssl_info->cert)
NavigationResourceHandler::GetSSLStatusForRequest(*ssl_info, &ssl_status_);
response_ = base::MakeRefCounted<ResourceResponse>();
response_->head = head;
}
void NavigationURLLoaderNetworkService::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const ResourceResponseHead& head) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(kinuko): Perform the necessary check and call
// URLLoaderRequestController::Restart with the new URL??
scoped_refptr<ResourceResponse> response(new ResourceResponse());
response->head = head;
delegate_->OnRequestRedirected(redirect_info, response);
}
void NavigationURLLoaderNetworkService::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
DCHECK(response_);
TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", this,
"&NavigationURLLoaderNetworkService", this, "success",
true);
// Temporarily, we pass both a stream (null) and the data pipe to the
// delegate until PlzNavigate has shipped and we can be comfortable fully
// switching to the data pipe.
delegate_->OnResponseStarted(response_, nullptr, std::move(body), ssl_status_,
std::unique_ptr<NavigationData>(),
GlobalRequestID(-1, g_next_request_id),
false /* is_download? */, false /* is_stream */);
}
void NavigationURLLoaderNetworkService::OnComplete(
const ResourceRequestCompletionStatus& completion_status) {
if (completion_status.error_code != net::OK) {
TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted",
this, "&NavigationURLLoaderNetworkService", this,
"success", false);
delegate_->OnRequestFailed(completion_status.exists_in_cache,
completion_status.error_code);
}
}
} // namespace content