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/resource_loader.h"
7 #include "base/command_line.h"
8 #include "base/location.h"
9 #include "base/metrics/histogram.h"
10 #include "base/profiler/scoped_tracker.h"
11 #include "base/single_thread_task_runner.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "base/time/time.h"
14 #include "content/browser/appcache/appcache_interceptor.h"
15 #include "content/browser/child_process_security_policy_impl.h"
16 #include "content/browser/loader/cross_site_resource_handler.h"
17 #include "content/browser/loader/detachable_resource_handler.h"
18 #include "content/browser/loader/resource_loader_delegate.h"
19 #include "content/browser/loader/resource_request_info_impl.h"
20 #include "content/browser/service_worker/service_worker_request_handler.h"
21 #include "content/browser/ssl/ssl_client_auth_handler.h"
22 #include "content/browser/ssl/ssl_manager.h"
23 #include "content/browser/ssl/ssl_policy.h"
24 #include "content/common/ssl_status_serialization.h"
25 #include "content/public/browser/cert_store.h"
26 #include "content/public/browser/resource_context.h"
27 #include "content/public/browser/resource_dispatcher_host_login_delegate.h"
28 #include "content/public/browser/signed_certificate_timestamp_store.h"
29 #include "content/public/common/content_client.h"
30 #include "content/public/common/content_switches.h"
31 #include "content/public/common/process_type.h"
32 #include "content/public/common/resource_response.h"
33 #include "content/public/common/security_style.h"
34 #include "net/base/io_buffer.h"
35 #include "net/base/load_flags.h"
36 #include "net/http/http_response_headers.h"
37 #include "net/ssl/client_cert_store.h"
38 #include "net/url_request/redirect_info.h"
39 #include "net/url_request/url_request_status.h"
41 using base::TimeDelta
;
42 using base::TimeTicks
;
47 void StoreSignedCertificateTimestamps(
48 const net::SignedCertificateTimestampAndStatusList
& sct_list
,
50 SignedCertificateTimestampIDStatusList
* sct_ids
) {
51 SignedCertificateTimestampStore
* sct_store(
52 SignedCertificateTimestampStore::GetInstance());
54 for (auto iter
= sct_list
.begin(); iter
!= sct_list
.end(); ++iter
) {
55 const int sct_id(sct_store
->Store(iter
->sct
.get(), process_id
));
57 SignedCertificateTimestampIDAndStatus(sct_id
, iter
->status
));
61 void GetSSLStatusForRequest(const GURL
& url
,
62 const net::SSLInfo
& ssl_info
,
64 SSLStatus
* ssl_status
) {
65 DCHECK(ssl_info
.cert
);
68 CertStore::GetInstance()->StoreCert(ssl_info
.cert
.get(), child_id
);
70 SignedCertificateTimestampIDStatusList signed_certificate_timestamp_ids
;
71 StoreSignedCertificateTimestamps(ssl_info
.signed_certificate_timestamps
,
72 child_id
, &signed_certificate_timestamp_ids
);
74 ssl_status
->cert_id
= cert_id
;
75 ssl_status
->cert_status
= ssl_info
.cert_status
;
76 ssl_status
->security_bits
= ssl_info
.security_bits
;
77 ssl_status
->connection_status
= ssl_info
.connection_status
;
78 ssl_status
->signed_certificate_timestamp_ids
=
79 signed_certificate_timestamp_ids
;
80 ssl_status
->security_style
=
81 SSLPolicy::GetSecurityStyleForResource(url
, *ssl_status
);
84 void PopulateResourceResponse(ResourceRequestInfoImpl
* info
,
85 net::URLRequest
* request
,
86 ResourceResponse
* response
) {
87 response
->head
.request_time
= request
->request_time();
88 response
->head
.response_time
= request
->response_time();
89 response
->head
.headers
= request
->response_headers();
90 request
->GetCharset(&response
->head
.charset
);
91 response
->head
.content_length
= request
->GetExpectedContentSize();
92 request
->GetMimeType(&response
->head
.mime_type
);
93 net::HttpResponseInfo response_info
= request
->response_info();
94 response
->head
.was_fetched_via_spdy
= response_info
.was_fetched_via_spdy
;
95 response
->head
.was_npn_negotiated
= response_info
.was_npn_negotiated
;
96 response
->head
.npn_negotiated_protocol
=
97 response_info
.npn_negotiated_protocol
;
98 response
->head
.connection_info
= response_info
.connection_info
;
99 response
->head
.was_fetched_via_proxy
= request
->was_fetched_via_proxy();
100 response
->head
.proxy_server
= response_info
.proxy_server
;
101 response
->head
.socket_address
= request
->GetSocketAddress();
102 if (ServiceWorkerRequestHandler
* handler
=
103 ServiceWorkerRequestHandler::GetHandler(request
)) {
104 handler
->GetExtraResponseInfo(&response
->head
);
106 AppCacheInterceptor::GetExtraResponseInfo(
107 request
, &response
->head
.appcache_id
,
108 &response
->head
.appcache_manifest_url
);
109 if (info
->is_load_timing_enabled())
110 request
->GetLoadTimingInfo(&response
->head
.load_timing
);
112 if (request
->ssl_info().cert
.get()) {
113 SSLStatus ssl_status
;
114 GetSSLStatusForRequest(request
->url(), request
->ssl_info(),
115 info
->GetChildID(), &ssl_status
);
116 response
->head
.security_info
= SerializeSecurityInfo(ssl_status
);
118 // We should not have any SSL state.
119 DCHECK(!request
->ssl_info().cert_status
&&
120 request
->ssl_info().security_bits
== -1 &&
121 !request
->ssl_info().connection_status
);
127 ResourceLoader::ResourceLoader(scoped_ptr
<net::URLRequest
> request
,
128 scoped_ptr
<ResourceHandler
> handler
,
129 ResourceLoaderDelegate
* delegate
)
130 : deferred_stage_(DEFERRED_NONE
),
131 request_(request
.Pass()),
132 handler_(handler
.Pass()),
134 is_transferring_(false),
135 times_cancelled_before_request_start_(0),
136 started_request_(false),
137 times_cancelled_after_request_start_(0),
138 weak_ptr_factory_(this) {
139 request_
->set_delegate(this);
140 handler_
->SetController(this);
143 ResourceLoader::~ResourceLoader() {
144 if (login_delegate_
.get())
145 login_delegate_
->OnRequestCancelled();
146 ssl_client_auth_handler_
.reset();
148 // Run ResourceHandler destructor before we tear-down the rest of our state
149 // as the ResourceHandler may want to inspect the URLRequest and other state.
153 void ResourceLoader::StartRequest() {
154 if (delegate_
->HandleExternalProtocol(this, request_
->url())) {
159 // Give the handler a chance to delay the URLRequest from being started.
160 bool defer_start
= false;
161 if (!handler_
->OnWillStart(request_
->url(), &defer_start
)) {
167 deferred_stage_
= DEFERRED_START
;
169 StartRequestInternal();
173 void ResourceLoader::CancelRequest(bool from_renderer
) {
174 CancelRequestInternal(net::ERR_ABORTED
, from_renderer
);
177 void ResourceLoader::CancelAndIgnore() {
178 ResourceRequestInfoImpl
* info
= GetRequestInfo();
179 info
->set_was_ignored_by_handler(true);
180 CancelRequest(false);
183 void ResourceLoader::CancelWithError(int error_code
) {
184 CancelRequestInternal(error_code
, false);
187 void ResourceLoader::MarkAsTransferring() {
188 CHECK(IsResourceTypeFrame(GetRequestInfo()->GetResourceType()))
189 << "Can only transfer for navigations";
190 is_transferring_
= true;
192 int child_id
= GetRequestInfo()->GetChildID();
193 AppCacheInterceptor::PrepareForCrossSiteTransfer(request(), child_id
);
194 ServiceWorkerRequestHandler
* handler
=
195 ServiceWorkerRequestHandler::GetHandler(request());
197 handler
->PrepareForCrossSiteTransfer(child_id
);
200 void ResourceLoader::CompleteTransfer() {
201 // Although CrossSiteResourceHandler defers at OnResponseStarted
202 // (DEFERRED_READ), it may be seeing a replay of events via
203 // MimeTypeResourceHandler, and so the request itself is actually deferred
204 // at a later read stage.
205 DCHECK(DEFERRED_READ
== deferred_stage_
||
206 DEFERRED_RESPONSE_COMPLETE
== deferred_stage_
);
207 DCHECK(is_transferring_
);
209 // In some cases, a process transfer doesn't really happen and the
210 // request is resumed in the original process. Real transfers to a new process
211 // are completed via ResourceDispatcherHostImpl::UpdateRequestForTransfer.
212 int child_id
= GetRequestInfo()->GetChildID();
213 AppCacheInterceptor::MaybeCompleteCrossSiteTransferInOldProcess(
214 request(), child_id
);
215 ServiceWorkerRequestHandler
* handler
=
216 ServiceWorkerRequestHandler::GetHandler(request());
218 handler
->MaybeCompleteCrossSiteTransferInOldProcess(child_id
);
220 is_transferring_
= false;
221 GetRequestInfo()->cross_site_handler()->ResumeResponse();
224 ResourceRequestInfoImpl
* ResourceLoader::GetRequestInfo() {
225 return ResourceRequestInfoImpl::ForRequest(request_
.get());
228 void ResourceLoader::ClearLoginDelegate() {
229 login_delegate_
= NULL
;
232 void ResourceLoader::OnReceivedRedirect(net::URLRequest
* unused
,
233 const net::RedirectInfo
& redirect_info
,
235 DCHECK_EQ(request_
.get(), unused
);
237 DVLOG(1) << "OnReceivedRedirect: " << request_
->url().spec();
238 DCHECK(request_
->status().is_success());
240 ResourceRequestInfoImpl
* info
= GetRequestInfo();
242 if (info
->GetProcessType() != PROCESS_TYPE_PLUGIN
&&
243 !ChildProcessSecurityPolicyImpl::GetInstance()->
244 CanRequestURL(info
->GetChildID(), redirect_info
.new_url
)) {
245 DVLOG(1) << "Denied unauthorized request for "
246 << redirect_info
.new_url
.possibly_invalid_spec();
248 // Tell the renderer that this request was disallowed.
253 delegate_
->DidReceiveRedirect(this, redirect_info
.new_url
);
255 if (delegate_
->HandleExternalProtocol(this, redirect_info
.new_url
)) {
256 // The request is complete so we can remove it.
261 scoped_refptr
<ResourceResponse
> response(new ResourceResponse());
262 PopulateResourceResponse(info
, request_
.get(), response
.get());
263 if (!handler_
->OnRequestRedirected(redirect_info
, response
.get(), defer
)) {
266 deferred_stage_
= DEFERRED_REDIRECT
; // Follow redirect when resumed.
270 void ResourceLoader::OnAuthRequired(net::URLRequest
* unused
,
271 net::AuthChallengeInfo
* auth_info
) {
272 DCHECK_EQ(request_
.get(), unused
);
274 ResourceRequestInfoImpl
* info
= GetRequestInfo();
275 if (info
->do_not_prompt_for_login()) {
276 request_
->CancelAuth();
280 // Create a login dialog on the UI thread to get authentication data, or pull
281 // from cache and continue on the IO thread.
283 DCHECK(!login_delegate_
.get())
284 << "OnAuthRequired called with login_delegate pending";
285 login_delegate_
= delegate_
->CreateLoginDelegate(this, auth_info
);
286 if (!login_delegate_
.get())
287 request_
->CancelAuth();
290 void ResourceLoader::OnCertificateRequested(
291 net::URLRequest
* unused
,
292 net::SSLCertRequestInfo
* cert_info
) {
293 DCHECK_EQ(request_
.get(), unused
);
295 if (request_
->load_flags() & net::LOAD_PREFETCH
) {
300 DCHECK(!ssl_client_auth_handler_
)
301 << "OnCertificateRequested called with ssl_client_auth_handler pending";
302 ssl_client_auth_handler_
.reset(new SSLClientAuthHandler(
303 GetRequestInfo()->GetContext()->CreateClientCertStore(), request_
.get(),
305 ssl_client_auth_handler_
->SelectCertificate();
308 void ResourceLoader::OnSSLCertificateError(net::URLRequest
* request
,
309 const net::SSLInfo
& ssl_info
,
311 ResourceRequestInfoImpl
* info
= GetRequestInfo();
313 int render_process_id
;
315 if (!info
->GetAssociatedRenderFrame(&render_process_id
, &render_frame_id
))
318 SSLManager::OnSSLCertificateError(
319 weak_ptr_factory_
.GetWeakPtr(),
320 info
->GetResourceType(),
328 void ResourceLoader::OnBeforeNetworkStart(net::URLRequest
* unused
,
330 DCHECK_EQ(request_
.get(), unused
);
332 // Give the handler a chance to delay the URLRequest from using the network.
333 if (!handler_
->OnBeforeNetworkStart(request_
->url(), defer
)) {
337 deferred_stage_
= DEFERRED_NETWORK_START
;
341 void ResourceLoader::OnResponseStarted(net::URLRequest
* unused
) {
342 DCHECK_EQ(request_
.get(), unused
);
344 DVLOG(1) << "OnResponseStarted: " << request_
->url().spec();
346 if (!request_
->status().is_success()) {
351 CompleteResponseStarted();
356 if (request_
->status().is_success())
357 StartReading(false); // Read the first chunk.
362 void ResourceLoader::OnReadCompleted(net::URLRequest
* unused
, int bytes_read
) {
363 DCHECK_EQ(request_
.get(), unused
);
364 DVLOG(1) << "OnReadCompleted: \"" << request_
->url().spec() << "\""
365 << " bytes_read = " << bytes_read
;
367 // bytes_read == -1 always implies an error.
368 if (bytes_read
== -1 || !request_
->status().is_success()) {
373 CompleteRead(bytes_read
);
375 // If the handler cancelled or deferred the request, do not continue
376 // processing the read. If cancelled, the URLRequest has already been
377 // cancelled and will schedule an erroring OnReadCompleted later. If deferred,
378 // do nothing until resumed.
380 // Note: if bytes_read is 0 (EOF) and the handler defers, resumption will call
381 // ResponseCompleted().
382 if (is_deferred() || !request_
->status().is_success())
385 if (bytes_read
> 0) {
386 StartReading(true); // Read the next chunk.
388 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
389 tracked_objects::ScopedTracker
tracking_profile(
390 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 ResponseCompleted()"));
392 // URLRequest reported an EOF. Call ResponseCompleted.
393 DCHECK_EQ(0, bytes_read
);
398 void ResourceLoader::CancelSSLRequest(int error
,
399 const net::SSLInfo
* ssl_info
) {
400 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
402 // The request can be NULL if it was cancelled by the renderer (as the
403 // request of the user navigating to a new page from the location bar).
404 if (!request_
->is_pending())
406 DVLOG(1) << "CancelSSLRequest() url: " << request_
->url().spec();
409 request_
->CancelWithSSLError(error
, *ssl_info
);
411 request_
->CancelWithError(error
);
415 void ResourceLoader::ContinueSSLRequest() {
416 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
418 DVLOG(1) << "ContinueSSLRequest() url: " << request_
->url().spec();
420 request_
->ContinueDespiteLastError();
423 void ResourceLoader::ContinueWithCertificate(net::X509Certificate
* cert
) {
424 DCHECK(ssl_client_auth_handler_
);
425 ssl_client_auth_handler_
.reset();
426 request_
->ContinueWithCertificate(cert
);
429 void ResourceLoader::CancelCertificateSelection() {
430 DCHECK(ssl_client_auth_handler_
);
431 ssl_client_auth_handler_
.reset();
432 request_
->CancelWithError(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED
);
435 void ResourceLoader::Resume() {
436 DCHECK(!is_transferring_
);
438 DeferredStage stage
= deferred_stage_
;
439 deferred_stage_
= DEFERRED_NONE
;
445 StartRequestInternal();
447 case DEFERRED_NETWORK_START
:
448 request_
->ResumeNetworkStart();
450 case DEFERRED_REDIRECT
:
451 request_
->FollowDeferredRedirect();
454 base::ThreadTaskRunnerHandle::Get()->PostTask(
455 FROM_HERE
, base::Bind(&ResourceLoader::ResumeReading
,
456 weak_ptr_factory_
.GetWeakPtr()));
458 case DEFERRED_RESPONSE_COMPLETE
:
459 base::ThreadTaskRunnerHandle::Get()->PostTask(
460 FROM_HERE
, base::Bind(&ResourceLoader::ResponseCompleted
,
461 weak_ptr_factory_
.GetWeakPtr()));
463 case DEFERRED_FINISH
:
464 // Delay self-destruction since we don't know how we were reached.
465 base::ThreadTaskRunnerHandle::Get()->PostTask(
466 FROM_HERE
, base::Bind(&ResourceLoader::CallDidFinishLoading
,
467 weak_ptr_factory_
.GetWeakPtr()));
472 void ResourceLoader::Cancel() {
473 CancelRequest(false);
476 void ResourceLoader::StartRequestInternal() {
477 DCHECK(!request_
->is_pending());
479 if (!request_
->status().is_success()) {
483 started_request_
= true;
486 delegate_
->DidStartRequest(this);
489 void ResourceLoader::CancelRequestInternal(int error
, bool from_renderer
) {
490 DVLOG(1) << "CancelRequestInternal: " << request_
->url().spec();
492 ResourceRequestInfoImpl
* info
= GetRequestInfo();
494 // WebKit will send us a cancel for downloads since it no longer handles
495 // them. In this case, ignore the cancel since we handle downloads in the
497 if (from_renderer
&& (info
->IsDownload() || info
->is_stream()))
500 if (from_renderer
&& info
->detachable_handler()) {
501 // TODO(davidben): Fix Blink handling of prefetches so they are not
502 // cancelled on navigate away and end up in the local cache.
503 info
->detachable_handler()->Detach();
507 // TODO(darin): Perhaps we should really be looking to see if the status is
509 bool was_pending
= request_
->is_pending();
511 if (login_delegate_
.get()) {
512 login_delegate_
->OnRequestCancelled();
513 login_delegate_
= NULL
;
515 ssl_client_auth_handler_
.reset();
517 if (!started_request_
) {
518 times_cancelled_before_request_start_
++;
520 times_cancelled_after_request_start_
++;
523 request_
->CancelWithError(error
);
526 // If the request isn't in flight, then we won't get an asynchronous
527 // notification from the request, so we have to signal ourselves to finish
529 base::ThreadTaskRunnerHandle::Get()->PostTask(
530 FROM_HERE
, base::Bind(&ResourceLoader::ResponseCompleted
,
531 weak_ptr_factory_
.GetWeakPtr()));
535 void ResourceLoader::CompleteResponseStarted() {
536 ResourceRequestInfoImpl
* info
= GetRequestInfo();
537 scoped_refptr
<ResourceResponse
> response(new ResourceResponse());
538 PopulateResourceResponse(info
, request_
.get(), response
.get());
540 delegate_
->DidReceiveResponse(this);
542 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
543 tracked_objects::ScopedTracker
tracking_profile(
544 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseStarted()"));
547 if (!handler_
->OnResponseStarted(response
.get(), &defer
)) {
550 read_deferral_start_time_
= base::TimeTicks::Now();
551 deferred_stage_
= DEFERRED_READ
; // Read first chunk when resumed.
555 void ResourceLoader::StartReading(bool is_continuation
) {
557 ReadMore(&bytes_read
);
559 // If IO is pending, wait for the URLRequest to call OnReadCompleted.
560 if (request_
->status().is_io_pending())
563 if (!is_continuation
|| bytes_read
<= 0) {
564 OnReadCompleted(request_
.get(), bytes_read
);
566 // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
567 // thread in case the URLRequest can provide data synchronously.
568 base::ThreadTaskRunnerHandle::Get()->PostTask(
570 base::Bind(&ResourceLoader::OnReadCompleted
,
571 weak_ptr_factory_
.GetWeakPtr(), request_
.get(), bytes_read
));
575 void ResourceLoader::ResumeReading() {
576 DCHECK(!is_deferred());
578 if (!read_deferral_start_time_
.is_null()) {
579 UMA_HISTOGRAM_TIMES("Net.ResourceLoader.ReadDeferral",
580 base::TimeTicks::Now() - read_deferral_start_time_
);
581 read_deferral_start_time_
= base::TimeTicks();
583 if (request_
->status().is_success()) {
584 StartReading(false); // Read the next chunk (OK to complete synchronously).
590 void ResourceLoader::ReadMore(int* bytes_read
) {
591 DCHECK(!is_deferred());
593 // Make sure we track the buffer in at least one place. This ensures it gets
594 // deleted even in the case the request has already finished its job and
595 // doesn't use the buffer.
596 scoped_refptr
<net::IOBuffer
> buf
;
599 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
600 tracked_objects::ScopedTracker
tracking_profile2(
601 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnWillRead()"));
603 if (!handler_
->OnWillRead(&buf
, &buf_size
, -1)) {
610 DCHECK(buf_size
> 0);
612 request_
->Read(buf
.get(), buf_size
, bytes_read
);
614 // No need to check the return value here as we'll detect errors by
615 // inspecting the URLRequest's status.
618 void ResourceLoader::CompleteRead(int bytes_read
) {
619 DCHECK(bytes_read
>= 0);
620 DCHECK(request_
->status().is_success());
622 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
623 tracked_objects::ScopedTracker
tracking_profile(
624 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnReadCompleted()"));
627 if (!handler_
->OnReadCompleted(bytes_read
, &defer
)) {
631 bytes_read
> 0 ? DEFERRED_READ
: DEFERRED_RESPONSE_COMPLETE
;
634 // Note: the request may still have been cancelled while OnReadCompleted
635 // returns true if OnReadCompleted caused request to get cancelled
636 // out-of-band. (In AwResourceDispatcherHostDelegate::DownloadStarting, for
640 void ResourceLoader::ResponseCompleted() {
641 DVLOG(1) << "ResponseCompleted: " << request_
->url().spec();
643 ResourceRequestInfoImpl
* info
= GetRequestInfo();
645 std::string security_info
;
646 const net::SSLInfo
& ssl_info
= request_
->ssl_info();
647 if (ssl_info
.cert
.get() != NULL
) {
648 SSLStatus ssl_status
;
649 GetSSLStatusForRequest(request_
->url(), ssl_info
, info
->GetChildID(),
652 security_info
= SerializeSecurityInfo(ssl_status
);
657 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
658 tracked_objects::ScopedTracker
tracking_profile(
659 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseCompleted()"));
661 handler_
->OnResponseCompleted(request_
->status(), security_info
, &defer
);
664 // The handler is not ready to die yet. We will call DidFinishLoading when
666 deferred_stage_
= DEFERRED_FINISH
;
668 // This will result in our destruction.
669 CallDidFinishLoading();
673 void ResourceLoader::CallDidFinishLoading() {
674 delegate_
->DidFinishLoading(this);
677 void ResourceLoader::RecordHistograms() {
678 ResourceRequestInfoImpl
* info
= GetRequestInfo();
680 if (info
->GetResourceType() == RESOURCE_TYPE_PREFETCH
) {
681 PrefetchStatus status
= STATUS_UNDEFINED
;
682 TimeDelta total_time
= base::TimeTicks::Now() - request_
->creation_time();
684 switch (request_
->status().status()) {
685 case net::URLRequestStatus::SUCCESS
:
686 if (request_
->was_cached()) {
687 status
= STATUS_SUCCESS_FROM_CACHE
;
688 UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromCache",
691 status
= STATUS_SUCCESS_FROM_NETWORK
;
692 UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromNetwork",
696 case net::URLRequestStatus::CANCELED
:
697 status
= STATUS_CANCELED
;
698 UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeBeforeCancel", total_time
);
700 case net::URLRequestStatus::IO_PENDING
:
701 case net::URLRequestStatus::FAILED
:
702 status
= STATUS_UNDEFINED
;
706 UMA_HISTOGRAM_ENUMERATION("Net.Prefetch.Pattern", status
, STATUS_MAX
);
710 } // namespace content