1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/loader/async_resource_handler.h"
10 #include "base/command_line.h"
11 #include "base/containers/hash_tables.h"
12 #include "base/debug/alias.h"
13 #include "base/logging.h"
14 #include "base/memory/shared_memory.h"
15 #include "base/metrics/histogram.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/time/time.h"
18 #include "content/browser/devtools/devtools_netlog_observer.h"
19 #include "content/browser/host_zoom_map_impl.h"
20 #include "content/browser/loader/resource_buffer.h"
21 #include "content/browser/loader/resource_dispatcher_host_impl.h"
22 #include "content/browser/loader/resource_message_filter.h"
23 #include "content/browser/loader/resource_request_info_impl.h"
24 #include "content/browser/resource_context_impl.h"
25 #include "content/common/resource_messages.h"
26 #include "content/common/view_messages.h"
27 #include "content/public/browser/resource_dispatcher_host_delegate.h"
28 #include "content/public/common/resource_response.h"
29 #include "net/base/io_buffer.h"
30 #include "net/base/load_flags.h"
31 #include "net/base/net_log.h"
32 #include "net/base/net_util.h"
33 #include "net/url_request/redirect_info.h"
35 using base::TimeTicks
;
40 static int kBufferSize
= 1024 * 512;
41 static int kMinAllocationSize
= 1024 * 4;
42 static int kMaxAllocationSize
= 1024 * 32;
44 void GetNumericArg(const std::string
& name
, int* result
) {
45 const std::string
& value
=
46 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name
);
48 base::StringToInt(value
, result
);
51 void InitializeResourceBufferConstants() {
52 static bool did_init
= false;
57 GetNumericArg("resource-buffer-size", &kBufferSize
);
58 GetNumericArg("resource-buffer-min-allocation-size", &kMinAllocationSize
);
59 GetNumericArg("resource-buffer-max-allocation-size", &kMaxAllocationSize
);
62 int CalcUsedPercentage(int bytes_read
, int buffer_size
) {
63 double ratio
= static_cast<double>(bytes_read
) / buffer_size
;
64 return static_cast<int>(ratio
* 100.0 + 0.5); // Round to nearest integer.
69 class DependentIOBuffer
: public net::WrappedIOBuffer
{
71 DependentIOBuffer(ResourceBuffer
* backing
, char* memory
)
72 : net::WrappedIOBuffer(memory
),
76 ~DependentIOBuffer() override
{}
77 scoped_refptr
<ResourceBuffer
> backing_
;
80 AsyncResourceHandler::AsyncResourceHandler(
81 net::URLRequest
* request
,
82 ResourceDispatcherHostImpl
* rdh
)
83 : ResourceHandler(request
),
84 ResourceMessageDelegate(request
),
86 pending_data_count_(0),
89 has_checked_for_sufficient_resources_(false),
90 sent_received_response_msg_(false),
91 sent_first_data_msg_(false),
92 reported_transfer_size_(0) {
93 InitializeResourceBufferConstants();
96 AsyncResourceHandler::~AsyncResourceHandler() {
97 if (has_checked_for_sufficient_resources_
)
98 rdh_
->FinishedWithResourcesForRequest(request());
101 bool AsyncResourceHandler::OnMessageReceived(const IPC::Message
& message
) {
103 IPC_BEGIN_MESSAGE_MAP(AsyncResourceHandler
, message
)
104 IPC_MESSAGE_HANDLER(ResourceHostMsg_FollowRedirect
, OnFollowRedirect
)
105 IPC_MESSAGE_HANDLER(ResourceHostMsg_DataReceived_ACK
, OnDataReceivedACK
)
106 IPC_MESSAGE_UNHANDLED(handled
= false)
107 IPC_END_MESSAGE_MAP()
111 void AsyncResourceHandler::OnFollowRedirect(int request_id
) {
112 if (!request()->status().is_success()) {
113 DVLOG(1) << "OnFollowRedirect for invalid request";
117 if (!redirect_start_time_
.is_null()) {
118 UMA_HISTOGRAM_TIMES("Net.AsyncResourceHandler_RedirectHopTime",
119 TimeTicks::Now() - redirect_start_time_
);
121 redirect_start_time_
= TimeTicks();
127 void AsyncResourceHandler::OnDataReceivedACK(int request_id
) {
128 if (pending_data_count_
) {
129 --pending_data_count_
;
131 buffer_
->RecycleLeastRecentlyAllocated();
132 if (buffer_
->CanAllocate())
137 bool AsyncResourceHandler::OnUploadProgress(uint64 position
,
139 ResourceMessageFilter
* filter
= GetFilter();
143 new ResourceMsg_UploadProgress(GetRequestID(), position
, size
));
146 bool AsyncResourceHandler::OnRequestRedirected(
147 const net::RedirectInfo
& redirect_info
,
148 ResourceResponse
* response
,
150 const ResourceRequestInfoImpl
* info
= GetRequestInfo();
154 redirect_start_time_
= TimeTicks::Now();
156 *defer
= did_defer_
= true;
159 if (rdh_
->delegate()) {
160 rdh_
->delegate()->OnRequestRedirected(
161 redirect_info
.new_url
, request(), info
->GetContext(), response
);
164 DevToolsNetLogObserver::PopulateResponseInfo(request(), response
);
165 response
->head
.encoded_data_length
= request()->GetTotalReceivedBytes();
166 reported_transfer_size_
= 0;
167 response
->head
.request_start
= request()->creation_time();
168 response
->head
.response_start
= TimeTicks::Now();
169 // TODO(davidben): Is it necessary to pass the new first party URL for
170 // cookies? The only case where it can change is top-level navigation requests
171 // and hopefully those will eventually all be owned by the browser. It's
172 // possible this is still needed while renderer-owned ones exist.
173 return info
->filter()->Send(new ResourceMsg_ReceivedRedirect(
174 GetRequestID(), redirect_info
, response
->head
));
177 bool AsyncResourceHandler::OnResponseStarted(ResourceResponse
* response
,
179 // For changes to the main frame, inform the renderer of the new URL's
180 // per-host settings before the request actually commits. This way the
181 // renderer will be able to set these precisely at the time the
182 // request commits, avoiding the possibility of e.g. zooming the old content
183 // or of having to layout the new content twice.
185 const ResourceRequestInfoImpl
* info
= GetRequestInfo();
189 if (rdh_
->delegate()) {
190 rdh_
->delegate()->OnResponseStarted(
191 request(), info
->GetContext(), response
, info
->filter());
194 DevToolsNetLogObserver::PopulateResponseInfo(request(), response
);
196 HostZoomMap
* host_zoom_map
=
197 GetHostZoomMapForResourceContext(info
->GetContext());
199 if (info
->GetResourceType() == RESOURCE_TYPE_MAIN_FRAME
&& host_zoom_map
) {
200 const GURL
& request_url
= request()->url();
201 info
->filter()->Send(new ViewMsg_SetZoomLevelForLoadingURL(
203 request_url
, host_zoom_map
->GetZoomLevelForHostAndScheme(
204 request_url
.scheme(),
205 net::GetHostOrSpecFromURL(request_url
))));
208 // If the parent handler downloaded the resource to a file, grant the child
209 // read permissions on it.
210 if (!response
->head
.download_file_path
.empty()) {
211 rdh_
->RegisterDownloadedTempFile(
212 info
->GetChildID(), info
->GetRequestID(),
213 response
->head
.download_file_path
);
216 response
->head
.request_start
= request()->creation_time();
217 response
->head
.response_start
= TimeTicks::Now();
218 info
->filter()->Send(new ResourceMsg_ReceivedResponse(GetRequestID(),
220 sent_received_response_msg_
= true;
222 if (request()->response_info().metadata
.get()) {
223 std::vector
<char> copy(request()->response_info().metadata
->data(),
224 request()->response_info().metadata
->data() +
225 request()->response_info().metadata
->size());
226 info
->filter()->Send(new ResourceMsg_ReceivedCachedMetadata(GetRequestID(),
233 bool AsyncResourceHandler::OnWillStart(const GURL
& url
, bool* defer
) {
237 bool AsyncResourceHandler::OnBeforeNetworkStart(const GURL
& url
, bool* defer
) {
241 bool AsyncResourceHandler::OnWillRead(scoped_refptr
<net::IOBuffer
>* buf
,
244 DCHECK_EQ(-1, min_size
);
246 if (!EnsureResourceBufferIsInitialized())
249 DCHECK(buffer_
->CanAllocate());
250 char* memory
= buffer_
->Allocate(&allocation_size_
);
253 *buf
= new DependentIOBuffer(buffer_
.get(), memory
);
254 *buf_size
= allocation_size_
;
256 UMA_HISTOGRAM_CUSTOM_COUNTS(
257 "Net.AsyncResourceHandler_SharedIOBuffer_Alloc",
258 *buf_size
, 0, kMaxAllocationSize
, 100);
262 bool AsyncResourceHandler::OnReadCompleted(int bytes_read
, bool* defer
) {
263 DCHECK_GE(bytes_read
, 0);
268 ResourceMessageFilter
* filter
= GetFilter();
272 buffer_
->ShrinkLastAllocation(bytes_read
);
274 UMA_HISTOGRAM_CUSTOM_COUNTS(
275 "Net.AsyncResourceHandler_SharedIOBuffer_Used",
276 bytes_read
, 0, kMaxAllocationSize
, 100);
277 UMA_HISTOGRAM_PERCENTAGE(
278 "Net.AsyncResourceHandler_SharedIOBuffer_UsedPercentage",
279 CalcUsedPercentage(bytes_read
, allocation_size_
));
281 if (!sent_first_data_msg_
) {
282 base::SharedMemoryHandle handle
;
284 if (!buffer_
->ShareToProcess(filter
->PeerHandle(), &handle
, &size
))
286 filter
->Send(new ResourceMsg_SetDataBuffer(
287 GetRequestID(), handle
, size
, filter
->peer_pid()));
288 sent_first_data_msg_
= true;
291 int data_offset
= buffer_
->GetLastAllocationOffset();
293 int64_t current_transfer_size
= request()->GetTotalReceivedBytes();
294 int encoded_data_length
= current_transfer_size
- reported_transfer_size_
;
295 reported_transfer_size_
= current_transfer_size
;
297 filter
->Send(new ResourceMsg_DataReceived(
298 GetRequestID(), data_offset
, bytes_read
, encoded_data_length
));
299 ++pending_data_count_
;
300 UMA_HISTOGRAM_CUSTOM_COUNTS(
301 "Net.AsyncResourceHandler_PendingDataCount",
302 pending_data_count_
, 0, 100, 100);
304 if (!buffer_
->CanAllocate()) {
305 UMA_HISTOGRAM_CUSTOM_COUNTS(
306 "Net.AsyncResourceHandler_PendingDataCount_WhenFull",
307 pending_data_count_
, 0, 100, 100);
308 *defer
= did_defer_
= true;
315 void AsyncResourceHandler::OnDataDownloaded(int bytes_downloaded
) {
316 int64_t current_transfer_size
= request()->GetTotalReceivedBytes();
317 int encoded_data_length
= current_transfer_size
- reported_transfer_size_
;
318 reported_transfer_size_
= current_transfer_size
;
320 ResourceMessageFilter
* filter
= GetFilter();
322 filter
->Send(new ResourceMsg_DataDownloaded(
323 GetRequestID(), bytes_downloaded
, encoded_data_length
));
327 void AsyncResourceHandler::OnResponseCompleted(
328 const net::URLRequestStatus
& status
,
329 const std::string
& security_info
,
331 const ResourceRequestInfoImpl
* info
= GetRequestInfo();
335 // If we crash here, figure out what URL the renderer was requesting.
336 // http://crbug.com/107692
338 base::strlcpy(url_buf
, request()->url().spec().c_str(), arraysize(url_buf
));
339 base::debug::Alias(url_buf
);
341 // TODO(gavinp): Remove this CHECK when we figure out the cause of
342 // http://crbug.com/124680 . This check mirrors closely check in
343 // WebURLLoaderImpl::OnCompletedRequest that routes this message to a WebCore
344 // ResourceHandleInternal which asserts on its state and crashes. By crashing
345 // when the message is sent, we should get better crash reports.
346 CHECK(status
.status() != net::URLRequestStatus::SUCCESS
||
347 sent_received_response_msg_
);
349 int error_code
= status
.error();
350 bool was_ignored_by_handler
= info
->WasIgnoredByHandler();
352 DCHECK(status
.status() != net::URLRequestStatus::IO_PENDING
);
353 // If this check fails, then we're in an inconsistent state because all
354 // requests ignored by the handler should be canceled (which should result in
355 // the ERR_ABORTED error code).
356 DCHECK(!was_ignored_by_handler
|| error_code
== net::ERR_ABORTED
);
358 // TODO(mkosiba): Fix up cases where we create a URLRequestStatus
359 // with a status() != SUCCESS and an error_code() == net::OK.
360 if (status
.status() == net::URLRequestStatus::CANCELED
&&
361 error_code
== net::OK
) {
362 error_code
= net::ERR_ABORTED
;
363 } else if (status
.status() == net::URLRequestStatus::FAILED
&&
364 error_code
== net::OK
) {
365 error_code
= net::ERR_FAILED
;
368 ResourceMsg_RequestCompleteData request_complete_data
;
369 request_complete_data
.error_code
= error_code
;
370 request_complete_data
.was_ignored_by_handler
= was_ignored_by_handler
;
371 request_complete_data
.exists_in_cache
= request()->response_info().was_cached
;
372 request_complete_data
.security_info
= security_info
;
373 request_complete_data
.completion_time
= TimeTicks::Now();
374 request_complete_data
.encoded_data_length
=
375 request()->GetTotalReceivedBytes();
376 info
->filter()->Send(
377 new ResourceMsg_RequestComplete(GetRequestID(), request_complete_data
));
380 bool AsyncResourceHandler::EnsureResourceBufferIsInitialized() {
381 if (buffer_
.get() && buffer_
->IsInitialized())
384 if (!has_checked_for_sufficient_resources_
) {
385 has_checked_for_sufficient_resources_
= true;
386 if (!rdh_
->HasSufficientResourcesForRequest(request())) {
387 controller()->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES
);
392 buffer_
= new ResourceBuffer();
393 return buffer_
->Initialize(kBufferSize
,
398 void AsyncResourceHandler::ResumeIfDeferred() {
401 request()->LogUnblocked();
402 controller()->Resume();
406 void AsyncResourceHandler::OnDefer() {
407 request()->LogBlockedBy("AsyncResourceHandler");
410 } // namespace content