blob: bf222361ae31f7e96ab1e1a0a070f0955ed9e6d3 [file] [log] [blame]
// Copyright (c) 2012 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.
// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading
#include "content/renderer/loader/resource_dispatcher.h"
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/debug/alias.h"
#include "base/debug/stack_trace.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h"
#include "content/common/inter_process_time_ticks_converter.h"
#include "content/common/navigation_params.h"
#include "content/common/throttling_url_loader.h"
#include "content/public/common/resource_type.h"
#include "content/public/common/subresource_load_info.mojom.h"
#include "content/public/renderer/fixed_received_data.h"
#include "content/public/renderer/request_peer.h"
#include "content/public/renderer/resource_dispatcher_delegate.h"
#include "content/renderer/loader/request_extra_data.h"
#include "content/renderer/loader/sync_load_context.h"
#include "content/renderer/loader/sync_load_response.h"
#include "content/renderer/loader/url_loader_client_impl.h"
#include "content/renderer/render_frame_impl.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
namespace content {
namespace {
// Converts |time| from a remote to local TimeTicks, overwriting the original
// value.
void RemoteToLocalTimeTicks(
const InterProcessTimeTicksConverter& converter,
base::TimeTicks* time) {
RemoteTimeTicks remote_time = RemoteTimeTicks::FromTimeTicks(*time);
*time = converter.ToLocalTimeTicks(remote_time).ToTimeTicks();
}
void CheckSchemeForReferrerPolicy(const network::ResourceRequest& request) {
if ((request.referrer_policy == Referrer::GetDefaultReferrerPolicy() ||
request.referrer_policy ==
net::URLRequest::
CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE) &&
request.referrer.SchemeIsCryptographic() &&
!request.url.SchemeIsCryptographic()) {
LOG(FATAL) << "Trying to send secure referrer for insecure request "
<< "without an appropriate referrer policy.\n"
<< "URL = " << request.url << "\n"
<< "Referrer = " << request.referrer;
}
}
void NotifySubresourceStarted(
scoped_refptr<base::SingleThreadTaskRunner> thread_task_runner,
int render_frame_id,
mojom::SubresourceLoadInfoPtr subresource_load_info) {
if (!thread_task_runner)
return;
if (!thread_task_runner->BelongsToCurrentThread()) {
thread_task_runner->PostTask(
FROM_HERE,
base::BindOnce(NotifySubresourceStarted, thread_task_runner,
render_frame_id, std::move(subresource_load_info)));
return;
}
RenderFrameImpl* render_frame =
RenderFrameImpl::FromRoutingID(render_frame_id);
if (!render_frame)
return;
render_frame->GetFrameHost()->SubresourceResponseStarted(
std::move(subresource_load_info));
}
} // namespace
// static
int ResourceDispatcher::MakeRequestID() {
// NOTE: The resource_dispatcher_host also needs probably unique
// request_ids, so they count down from -2 (-1 is a special "we're
// screwed value"), while the renderer process counts up.
static base::AtomicSequenceNumber sequence;
return sequence.GetNext(); // We start at zero.
}
ResourceDispatcher::ResourceDispatcher()
: delegate_(nullptr), weak_factory_(this) {}
ResourceDispatcher::~ResourceDispatcher() {
}
ResourceDispatcher::PendingRequestInfo*
ResourceDispatcher::GetPendingRequestInfo(int request_id) {
PendingRequestMap::iterator it = pending_requests_.find(request_id);
if (it == pending_requests_.end()) {
// This might happen for kill()ed requests on the webkit end.
return nullptr;
}
return it->second.get();
}
void ResourceDispatcher::OnUploadProgress(int request_id,
int64_t position,
int64_t size) {
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
request_info->peer->OnUploadProgress(position, size);
}
void ResourceDispatcher::OnReceivedResponse(
int request_id,
const network::ResourceResponseHead& response_head) {
TRACE_EVENT0("loader", "ResourceDispatcher::OnReceivedResponse");
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
request_info->response_start = base::TimeTicks::Now();
if (delegate_) {
std::unique_ptr<RequestPeer> new_peer = delegate_->OnReceivedResponse(
std::move(request_info->peer), response_head.mime_type,
request_info->url);
DCHECK(new_peer);
request_info->peer = std::move(new_peer);
}
if (!IsResourceTypeFrame(request_info->resource_type)) {
auto subresource_load_info = mojom::SubresourceLoadInfo::New();
subresource_load_info->url = request_info->response_url;
subresource_load_info->referrer = request_info->response_referrer;
subresource_load_info->method = request_info->response_method;
subresource_load_info->resource_type = request_info->resource_type;
if (!response_head.socket_address.host().empty()) {
net::IPAddress parsed_ip;
if (parsed_ip.AssignFromIPLiteral(response_head.socket_address.host())) {
subresource_load_info->ip = parsed_ip;
}
}
subresource_load_info->cert_status = response_head.cert_status;
NotifySubresourceStarted(RenderThreadImpl::DeprecatedGetMainTaskRunner(),
request_info->render_frame_id,
std::move(subresource_load_info));
}
network::ResourceResponseInfo renderer_response_info;
ToResourceResponseInfo(*request_info, response_head, &renderer_response_info);
request_info->peer->OnReceivedResponse(renderer_response_info);
}
void ResourceDispatcher::OnReceivedCachedMetadata(
int request_id,
const std::vector<uint8_t>& data) {
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
if (data.size()) {
request_info->peer->OnReceivedCachedMetadata(
reinterpret_cast<const char*>(&data.front()), data.size());
}
}
void ResourceDispatcher::OnDownloadedData(int request_id,
int data_len,
int encoded_data_length) {
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
request_info->peer->OnDownloadedData(data_len, encoded_data_length);
}
void ResourceDispatcher::OnReceivedRedirect(
int request_id,
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
TRACE_EVENT0("loader", "ResourceDispatcher::OnReceivedRedirect");
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
request_info->response_start = base::TimeTicks::Now();
network::ResourceResponseInfo renderer_response_info;
ToResourceResponseInfo(*request_info, response_head, &renderer_response_info);
if (request_info->peer->OnReceivedRedirect(redirect_info,
renderer_response_info)) {
// Double-check if the request is still around. The call above could
// potentially remove it.
request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
// We update the response_url here for tracking/stats use later.
request_info->response_url = redirect_info.new_url;
request_info->response_method = redirect_info.new_method;
request_info->response_referrer = GURL(redirect_info.new_referrer);
request_info->has_pending_redirect = true;
if (!request_info->is_deferred) {
FollowPendingRedirect(request_info);
}
} else {
Cancel(request_id, std::move(task_runner));
}
}
void ResourceDispatcher::FollowPendingRedirect(
PendingRequestInfo* request_info) {
if (request_info->has_pending_redirect) {
request_info->has_pending_redirect = false;
request_info->url_loader->FollowRedirect();
}
}
void ResourceDispatcher::OnRequestComplete(
int request_id,
const network::URLLoaderCompletionStatus& status) {
TRACE_EVENT0("loader", "ResourceDispatcher::OnRequestComplete");
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
request_info->completion_time = base::TimeTicks::Now();
request_info->buffer.reset();
request_info->buffer_size = 0;
RequestPeer* peer = request_info->peer.get();
if (delegate_) {
std::unique_ptr<RequestPeer> new_peer = delegate_->OnRequestComplete(
std::move(request_info->peer), request_info->resource_type,
status.error_code);
DCHECK(new_peer);
request_info->peer = std::move(new_peer);
}
// The request ID will be removed from our pending list in the destructor.
// Normally, dispatching this message causes the reference-counted request to
// die immediately.
// TODO(kinuko): Revisit here. This probably needs to call request_info->peer
// but the past attempt to change it seems to have caused crashes.
// (crbug.com/547047)
network::URLLoaderCompletionStatus renderer_status(status);
renderer_status.completion_time =
ToRendererCompletionTime(*request_info, status.completion_time);
peer->OnCompletedRequest(renderer_status);
}
bool ResourceDispatcher::RemovePendingRequest(
int request_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
PendingRequestMap::iterator it = pending_requests_.find(request_id);
if (it == pending_requests_.end())
return false;
// Cancel loading.
it->second->url_loader = nullptr;
// Clear URLLoaderClient to stop receiving further Mojo IPC from the browser
// process.
it->second->url_loader_client = nullptr;
// Always delete the pending_request asyncly so that cancelling the request
// doesn't delete the request context info while its response is still being
// handled.
task_runner->DeleteSoon(FROM_HERE, it->second.release());
pending_requests_.erase(it);
return true;
}
void ResourceDispatcher::Cancel(
int request_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
PendingRequestMap::iterator it = pending_requests_.find(request_id);
if (it == pending_requests_.end()) {
DVLOG(1) << "unknown request";
return;
}
// Cancel the request if it didn't complete, and clean it up so the bridge
// will receive no more messages.
RemovePendingRequest(request_id, std::move(task_runner));
}
void ResourceDispatcher::SetDefersLoading(int request_id, bool value) {
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info) {
DLOG(ERROR) << "unknown request";
return;
}
if (value) {
request_info->is_deferred = value;
request_info->url_loader_client->SetDefersLoading();
} else if (request_info->is_deferred) {
request_info->is_deferred = false;
request_info->url_loader_client->UnsetDefersLoading();
FollowPendingRedirect(request_info);
}
}
void ResourceDispatcher::DidChangePriority(int request_id,
net::RequestPriority new_priority,
int intra_priority_value) {
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
DCHECK(request_info);
request_info->url_loader->SetPriority(new_priority, intra_priority_value);
}
void ResourceDispatcher::OnTransferSizeUpdated(int request_id,
int32_t transfer_size_diff) {
DCHECK_GT(transfer_size_diff, 0);
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
// TODO(yhirano): Consider using int64_t in
// RequestPeer::OnTransferSizeUpdated.
request_info->peer->OnTransferSizeUpdated(transfer_size_diff);
}
ResourceDispatcher::PendingRequestInfo::PendingRequestInfo(
std::unique_ptr<RequestPeer> peer,
ResourceType resource_type,
int render_frame_id,
const GURL& request_url,
const std::string& method,
const GURL& referrer,
bool download_to_file)
: peer(std::move(peer)),
resource_type(resource_type),
render_frame_id(render_frame_id),
url(request_url),
response_url(request_url),
response_method(method),
response_referrer(referrer),
download_to_file(download_to_file),
request_start(base::TimeTicks::Now()) {}
ResourceDispatcher::PendingRequestInfo::~PendingRequestInfo() {
}
void ResourceDispatcher::StartSync(
std::unique_ptr<network::ResourceRequest> request,
int routing_id,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
SyncLoadResponse* response,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::vector<std::unique_ptr<URLLoaderThrottle>> throttles) {
CheckSchemeForReferrerPolicy(*request);
std::unique_ptr<network::SharedURLLoaderFactoryInfo> factory_info =
url_loader_factory->Clone();
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
// Prepare the configured throttles for use on a separate thread.
for (const auto& throttle : throttles)
throttle->DetachFromCurrentSequence();
// A task is posted to a separate thread to execute the request so that
// this thread may block on a waitable event. It is safe to pass raw
// pointers to |sync_load_response| and |event| as this stack frame will
// survive until the request is complete.
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::CreateSingleThreadTaskRunnerWithTraits({});
task_runner->PostTask(
FROM_HERE,
base::BindOnce(&SyncLoadContext::StartAsyncWithWaitableEvent,
std::move(request), routing_id, task_runner,
traffic_annotation, std::move(factory_info),
std::move(throttles), base::Unretained(response),
base::Unretained(&event)));
event.Wait();
}
int ResourceDispatcher::StartAsync(
std::unique_ptr<network::ResourceRequest> request,
int routing_id,
scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
bool is_sync,
std::unique_ptr<RequestPeer> peer,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::vector<std::unique_ptr<URLLoaderThrottle>> throttles,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
base::OnceClosure* continue_navigation_function) {
CheckSchemeForReferrerPolicy(*request);
// Compute a unique request_id for this renderer process.
int request_id = MakeRequestID();
pending_requests_[request_id] = std::make_unique<PendingRequestInfo>(
std::move(peer), static_cast<ResourceType>(request->resource_type),
request->render_frame_id, request->url, request->method,
request->referrer, request->download_to_file);
if (url_loader_client_endpoints) {
pending_requests_[request_id]->url_loader_client =
std::make_unique<URLLoaderClientImpl>(request_id, this,
loading_task_runner);
DCHECK(continue_navigation_function);
*continue_navigation_function = base::BindOnce(
&ResourceDispatcher::ContinueForNavigation, weak_factory_.GetWeakPtr(),
request_id, std::move(url_loader_client_endpoints));
return request_id;
}
std::unique_ptr<URLLoaderClientImpl> client(
new URLLoaderClientImpl(request_id, this, loading_task_runner));
uint32_t options = network::mojom::kURLLoadOptionNone;
// TODO(jam): use this flag for ResourceDispatcherHost code path once
// MojoLoading is the only IPC code path.
if (base::FeatureList::IsEnabled(network::features::kNetworkService) &&
request->fetch_request_context_type != REQUEST_CONTEXT_TYPE_FETCH) {
// MIME sniffing should be disabled for a request initiated by fetch().
options |= network::mojom::kURLLoadOptionSniffMimeType;
}
if (is_sync) {
options |= network::mojom::kURLLoadOptionSynchronous;
request->load_flags |= net::LOAD_IGNORE_LIMITS;
}
std::unique_ptr<ThrottlingURLLoader> url_loader =
ThrottlingURLLoader::CreateLoaderAndStart(
std::move(url_loader_factory), std::move(throttles), routing_id,
request_id, options, request.get(), client.get(), traffic_annotation,
std::move(loading_task_runner));
pending_requests_[request_id]->url_loader = std::move(url_loader);
pending_requests_[request_id]->url_loader_client = std::move(client);
return request_id;
}
void ResourceDispatcher::ToResourceResponseInfo(
const PendingRequestInfo& request_info,
const network::ResourceResponseHead& browser_info,
network::ResourceResponseInfo* renderer_info) const {
*renderer_info = browser_info;
if (base::TimeTicks::IsConsistentAcrossProcesses() ||
request_info.request_start.is_null() ||
request_info.response_start.is_null() ||
browser_info.request_start.is_null() ||
browser_info.response_start.is_null() ||
browser_info.load_timing.request_start.is_null()) {
return;
}
InterProcessTimeTicksConverter converter(
LocalTimeTicks::FromTimeTicks(request_info.request_start),
LocalTimeTicks::FromTimeTicks(request_info.response_start),
RemoteTimeTicks::FromTimeTicks(browser_info.request_start),
RemoteTimeTicks::FromTimeTicks(browser_info.response_start));
net::LoadTimingInfo* load_timing = &renderer_info->load_timing;
RemoteToLocalTimeTicks(converter, &load_timing->request_start);
RemoteToLocalTimeTicks(converter, &load_timing->proxy_resolve_start);
RemoteToLocalTimeTicks(converter, &load_timing->proxy_resolve_end);
RemoteToLocalTimeTicks(converter, &load_timing->connect_timing.dns_start);
RemoteToLocalTimeTicks(converter, &load_timing->connect_timing.dns_end);
RemoteToLocalTimeTicks(converter, &load_timing->connect_timing.connect_start);
RemoteToLocalTimeTicks(converter, &load_timing->connect_timing.connect_end);
RemoteToLocalTimeTicks(converter, &load_timing->connect_timing.ssl_start);
RemoteToLocalTimeTicks(converter, &load_timing->connect_timing.ssl_end);
RemoteToLocalTimeTicks(converter, &load_timing->send_start);
RemoteToLocalTimeTicks(converter, &load_timing->send_end);
RemoteToLocalTimeTicks(converter, &load_timing->receive_headers_end);
RemoteToLocalTimeTicks(converter, &load_timing->push_start);
RemoteToLocalTimeTicks(converter, &load_timing->push_end);
RemoteToLocalTimeTicks(converter, &renderer_info->service_worker_start_time);
RemoteToLocalTimeTicks(converter, &renderer_info->service_worker_ready_time);
}
base::TimeTicks ResourceDispatcher::ToRendererCompletionTime(
const PendingRequestInfo& request_info,
const base::TimeTicks& browser_completion_time) const {
if (request_info.completion_time.is_null()) {
return browser_completion_time;
}
// TODO(simonjam): The optimal lower bound should be the most recent value of
// TimeTicks::Now() returned to WebKit. Is it worth trying to cache that?
// Until then, |response_start| is used as it is the most recent value
// returned for this request.
int64_t result = std::max(browser_completion_time.ToInternalValue(),
request_info.response_start.ToInternalValue());
result = std::min(result, request_info.completion_time.ToInternalValue());
return base::TimeTicks::FromInternalValue(result);
}
void ResourceDispatcher::ContinueForNavigation(
int request_id,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints) {
DCHECK(url_loader_client_endpoints);
PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (!request_info)
return;
URLLoaderClientImpl* client_ptr = request_info->url_loader_client.get();
// Short circuiting call to OnReceivedResponse to immediately start
// the request. ResourceResponseHead can be empty here because we
// pull the StreamOverride's one in
// WebURLLoaderImpl::Context::OnReceivedResponse.
client_ptr->OnReceiveResponse(network::ResourceResponseHead(), base::nullopt,
network::mojom::DownloadedTempFilePtr());
// TODO(clamy): Move the replaying of redirects from WebURLLoaderImpl here.
// Abort if the request is cancelled.
if (!GetPendingRequestInfo(request_id))
return;
client_ptr->Bind(std::move(url_loader_client_endpoints));
}
} // namespace content