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
= SSLStatus(SSLPolicy::GetSecurityStyleForResource(
75 url
, cert_id
, ssl_info
.cert_status
),
76 cert_id
, signed_certificate_timestamp_ids
, ssl_info
);
79 void PopulateResourceResponse(ResourceRequestInfoImpl
* info
,
80 net::URLRequest
* request
,
81 ResourceResponse
* response
) {
82 response
->head
.request_time
= request
->request_time();
83 response
->head
.response_time
= request
->response_time();
84 response
->head
.headers
= request
->response_headers();
85 request
->GetCharset(&response
->head
.charset
);
86 response
->head
.content_length
= request
->GetExpectedContentSize();
87 request
->GetMimeType(&response
->head
.mime_type
);
88 net::HttpResponseInfo response_info
= request
->response_info();
89 response
->head
.was_fetched_via_spdy
= response_info
.was_fetched_via_spdy
;
90 response
->head
.was_npn_negotiated
= response_info
.was_npn_negotiated
;
91 response
->head
.npn_negotiated_protocol
=
92 response_info
.npn_negotiated_protocol
;
93 response
->head
.connection_info
= response_info
.connection_info
;
94 response
->head
.was_fetched_via_proxy
= request
->was_fetched_via_proxy();
95 response
->head
.proxy_server
= response_info
.proxy_server
;
96 response
->head
.socket_address
= request
->GetSocketAddress();
97 if (ServiceWorkerRequestHandler
* handler
=
98 ServiceWorkerRequestHandler::GetHandler(request
)) {
99 handler
->GetExtraResponseInfo(&response
->head
);
101 AppCacheInterceptor::GetExtraResponseInfo(
102 request
, &response
->head
.appcache_id
,
103 &response
->head
.appcache_manifest_url
);
104 if (info
->is_load_timing_enabled())
105 request
->GetLoadTimingInfo(&response
->head
.load_timing
);
107 if (request
->ssl_info().cert
.get()) {
108 SSLStatus ssl_status
;
109 GetSSLStatusForRequest(request
->url(), request
->ssl_info(),
110 info
->GetChildID(), &ssl_status
);
111 response
->head
.security_info
= SerializeSecurityInfo(ssl_status
);
113 // We should not have any SSL state.
114 DCHECK(!request
->ssl_info().cert_status
);
115 DCHECK_EQ(request
->ssl_info().security_bits
, -1);
116 DCHECK_EQ(request
->ssl_info().key_exchange_info
, 0);
117 DCHECK(!request
->ssl_info().connection_status
);
123 ResourceLoader::ResourceLoader(scoped_ptr
<net::URLRequest
> request
,
124 scoped_ptr
<ResourceHandler
> handler
,
125 ResourceLoaderDelegate
* delegate
)
126 : deferred_stage_(DEFERRED_NONE
),
127 request_(request
.Pass()),
128 handler_(handler
.Pass()),
130 is_transferring_(false),
131 times_cancelled_before_request_start_(0),
132 started_request_(false),
133 times_cancelled_after_request_start_(0),
134 weak_ptr_factory_(this) {
135 request_
->set_delegate(this);
136 handler_
->SetController(this);
139 ResourceLoader::~ResourceLoader() {
140 if (login_delegate_
.get())
141 login_delegate_
->OnRequestCancelled();
142 ssl_client_auth_handler_
.reset();
144 // Run ResourceHandler destructor before we tear-down the rest of our state
145 // as the ResourceHandler may want to inspect the URLRequest and other state.
149 void ResourceLoader::StartRequest() {
150 if (delegate_
->HandleExternalProtocol(this, request_
->url())) {
155 // Give the handler a chance to delay the URLRequest from being started.
156 bool defer_start
= false;
157 if (!handler_
->OnWillStart(request_
->url(), &defer_start
)) {
163 deferred_stage_
= DEFERRED_START
;
165 StartRequestInternal();
169 void ResourceLoader::CancelRequest(bool from_renderer
) {
170 CancelRequestInternal(net::ERR_ABORTED
, from_renderer
);
173 void ResourceLoader::CancelAndIgnore() {
174 ResourceRequestInfoImpl
* info
= GetRequestInfo();
175 info
->set_was_ignored_by_handler(true);
176 CancelRequest(false);
179 void ResourceLoader::CancelWithError(int error_code
) {
180 CancelRequestInternal(error_code
, false);
183 void ResourceLoader::MarkAsTransferring() {
184 CHECK(IsResourceTypeFrame(GetRequestInfo()->GetResourceType()))
185 << "Can only transfer for navigations";
186 is_transferring_
= true;
188 int child_id
= GetRequestInfo()->GetChildID();
189 AppCacheInterceptor::PrepareForCrossSiteTransfer(request(), child_id
);
190 ServiceWorkerRequestHandler
* handler
=
191 ServiceWorkerRequestHandler::GetHandler(request());
193 handler
->PrepareForCrossSiteTransfer(child_id
);
196 void ResourceLoader::CompleteTransfer() {
197 // Although CrossSiteResourceHandler defers at OnResponseStarted
198 // (DEFERRED_READ), it may be seeing a replay of events via
199 // MimeTypeResourceHandler, and so the request itself is actually deferred
200 // at a later read stage.
201 DCHECK(DEFERRED_READ
== deferred_stage_
||
202 DEFERRED_RESPONSE_COMPLETE
== deferred_stage_
);
203 DCHECK(is_transferring_
);
205 // In some cases, a process transfer doesn't really happen and the
206 // request is resumed in the original process. Real transfers to a new process
207 // are completed via ResourceDispatcherHostImpl::UpdateRequestForTransfer.
208 int child_id
= GetRequestInfo()->GetChildID();
209 AppCacheInterceptor::MaybeCompleteCrossSiteTransferInOldProcess(
210 request(), child_id
);
211 ServiceWorkerRequestHandler
* handler
=
212 ServiceWorkerRequestHandler::GetHandler(request());
214 handler
->MaybeCompleteCrossSiteTransferInOldProcess(child_id
);
216 is_transferring_
= false;
217 GetRequestInfo()->cross_site_handler()->ResumeResponse();
220 ResourceRequestInfoImpl
* ResourceLoader::GetRequestInfo() {
221 return ResourceRequestInfoImpl::ForRequest(request_
.get());
224 void ResourceLoader::ClearLoginDelegate() {
225 login_delegate_
= NULL
;
228 void ResourceLoader::OnReceivedRedirect(net::URLRequest
* unused
,
229 const net::RedirectInfo
& redirect_info
,
231 DCHECK_EQ(request_
.get(), unused
);
233 DVLOG(1) << "OnReceivedRedirect: " << request_
->url().spec();
234 DCHECK(request_
->status().is_success());
236 ResourceRequestInfoImpl
* info
= GetRequestInfo();
238 if (info
->GetProcessType() != PROCESS_TYPE_PLUGIN
&&
239 !ChildProcessSecurityPolicyImpl::GetInstance()->
240 CanRequestURL(info
->GetChildID(), redirect_info
.new_url
)) {
241 DVLOG(1) << "Denied unauthorized request for "
242 << redirect_info
.new_url
.possibly_invalid_spec();
244 // Tell the renderer that this request was disallowed.
249 delegate_
->DidReceiveRedirect(this, redirect_info
.new_url
);
251 if (delegate_
->HandleExternalProtocol(this, redirect_info
.new_url
)) {
252 // The request is complete so we can remove it.
257 scoped_refptr
<ResourceResponse
> response(new ResourceResponse());
258 PopulateResourceResponse(info
, request_
.get(), response
.get());
259 if (!handler_
->OnRequestRedirected(redirect_info
, response
.get(), defer
)) {
262 deferred_stage_
= DEFERRED_REDIRECT
; // Follow redirect when resumed.
266 void ResourceLoader::OnAuthRequired(net::URLRequest
* unused
,
267 net::AuthChallengeInfo
* auth_info
) {
268 DCHECK_EQ(request_
.get(), unused
);
270 ResourceRequestInfoImpl
* info
= GetRequestInfo();
271 if (info
->do_not_prompt_for_login()) {
272 request_
->CancelAuth();
276 // Create a login dialog on the UI thread to get authentication data, or pull
277 // from cache and continue on the IO thread.
279 DCHECK(!login_delegate_
.get())
280 << "OnAuthRequired called with login_delegate pending";
281 login_delegate_
= delegate_
->CreateLoginDelegate(this, auth_info
);
282 if (!login_delegate_
.get())
283 request_
->CancelAuth();
286 void ResourceLoader::OnCertificateRequested(
287 net::URLRequest
* unused
,
288 net::SSLCertRequestInfo
* cert_info
) {
289 DCHECK_EQ(request_
.get(), unused
);
291 if (request_
->load_flags() & net::LOAD_PREFETCH
) {
296 DCHECK(!ssl_client_auth_handler_
)
297 << "OnCertificateRequested called with ssl_client_auth_handler pending";
298 ssl_client_auth_handler_
.reset(new SSLClientAuthHandler(
299 GetRequestInfo()->GetContext()->CreateClientCertStore(), request_
.get(),
301 ssl_client_auth_handler_
->SelectCertificate();
304 void ResourceLoader::OnSSLCertificateError(net::URLRequest
* request
,
305 const net::SSLInfo
& ssl_info
,
307 ResourceRequestInfoImpl
* info
= GetRequestInfo();
309 int render_process_id
;
311 if (!info
->GetAssociatedRenderFrame(&render_process_id
, &render_frame_id
))
314 SSLManager::OnSSLCertificateError(
315 weak_ptr_factory_
.GetWeakPtr(),
316 info
->GetResourceType(),
324 void ResourceLoader::OnBeforeNetworkStart(net::URLRequest
* unused
,
326 DCHECK_EQ(request_
.get(), unused
);
328 // Give the handler a chance to delay the URLRequest from using the network.
329 if (!handler_
->OnBeforeNetworkStart(request_
->url(), defer
)) {
333 deferred_stage_
= DEFERRED_NETWORK_START
;
337 void ResourceLoader::OnResponseStarted(net::URLRequest
* unused
) {
338 DCHECK_EQ(request_
.get(), unused
);
340 DVLOG(1) << "OnResponseStarted: " << request_
->url().spec();
342 if (!request_
->status().is_success()) {
347 CompleteResponseStarted();
352 if (request_
->status().is_success())
353 StartReading(false); // Read the first chunk.
358 void ResourceLoader::OnReadCompleted(net::URLRequest
* unused
, int bytes_read
) {
359 DCHECK_EQ(request_
.get(), unused
);
360 DVLOG(1) << "OnReadCompleted: \"" << request_
->url().spec() << "\""
361 << " bytes_read = " << bytes_read
;
363 // bytes_read == -1 always implies an error.
364 if (bytes_read
== -1 || !request_
->status().is_success()) {
369 CompleteRead(bytes_read
);
371 // If the handler cancelled or deferred the request, do not continue
372 // processing the read. If cancelled, the URLRequest has already been
373 // cancelled and will schedule an erroring OnReadCompleted later. If deferred,
374 // do nothing until resumed.
376 // Note: if bytes_read is 0 (EOF) and the handler defers, resumption will call
377 // ResponseCompleted().
378 if (is_deferred() || !request_
->status().is_success())
381 if (bytes_read
> 0) {
382 StartReading(true); // Read the next chunk.
384 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
385 tracked_objects::ScopedTracker
tracking_profile(
386 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 ResponseCompleted()"));
388 // URLRequest reported an EOF. Call ResponseCompleted.
389 DCHECK_EQ(0, bytes_read
);
394 void ResourceLoader::CancelSSLRequest(int error
,
395 const net::SSLInfo
* ssl_info
) {
396 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
398 // The request can be NULL if it was cancelled by the renderer (as the
399 // request of the user navigating to a new page from the location bar).
400 if (!request_
->is_pending())
402 DVLOG(1) << "CancelSSLRequest() url: " << request_
->url().spec();
405 request_
->CancelWithSSLError(error
, *ssl_info
);
407 request_
->CancelWithError(error
);
411 void ResourceLoader::ContinueSSLRequest() {
412 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
414 DVLOG(1) << "ContinueSSLRequest() url: " << request_
->url().spec();
416 request_
->ContinueDespiteLastError();
419 void ResourceLoader::ContinueWithCertificate(net::X509Certificate
* cert
) {
420 DCHECK(ssl_client_auth_handler_
);
421 ssl_client_auth_handler_
.reset();
422 request_
->ContinueWithCertificate(cert
);
425 void ResourceLoader::CancelCertificateSelection() {
426 DCHECK(ssl_client_auth_handler_
);
427 ssl_client_auth_handler_
.reset();
428 request_
->CancelWithError(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED
);
431 void ResourceLoader::Resume() {
432 DCHECK(!is_transferring_
);
434 DeferredStage stage
= deferred_stage_
;
435 deferred_stage_
= DEFERRED_NONE
;
441 StartRequestInternal();
443 case DEFERRED_NETWORK_START
:
444 request_
->ResumeNetworkStart();
446 case DEFERRED_REDIRECT
:
447 request_
->FollowDeferredRedirect();
450 base::ThreadTaskRunnerHandle::Get()->PostTask(
451 FROM_HERE
, base::Bind(&ResourceLoader::ResumeReading
,
452 weak_ptr_factory_
.GetWeakPtr()));
454 case DEFERRED_RESPONSE_COMPLETE
:
455 base::ThreadTaskRunnerHandle::Get()->PostTask(
456 FROM_HERE
, base::Bind(&ResourceLoader::ResponseCompleted
,
457 weak_ptr_factory_
.GetWeakPtr()));
459 case DEFERRED_FINISH
:
460 // Delay self-destruction since we don't know how we were reached.
461 base::ThreadTaskRunnerHandle::Get()->PostTask(
462 FROM_HERE
, base::Bind(&ResourceLoader::CallDidFinishLoading
,
463 weak_ptr_factory_
.GetWeakPtr()));
468 void ResourceLoader::Cancel() {
469 CancelRequest(false);
472 void ResourceLoader::StartRequestInternal() {
473 DCHECK(!request_
->is_pending());
475 if (!request_
->status().is_success()) {
479 started_request_
= true;
482 delegate_
->DidStartRequest(this);
485 void ResourceLoader::CancelRequestInternal(int error
, bool from_renderer
) {
486 DVLOG(1) << "CancelRequestInternal: " << request_
->url().spec();
488 ResourceRequestInfoImpl
* info
= GetRequestInfo();
490 // WebKit will send us a cancel for downloads since it no longer handles
491 // them. In this case, ignore the cancel since we handle downloads in the
493 if (from_renderer
&& (info
->IsDownload() || info
->is_stream()))
496 if (from_renderer
&& info
->detachable_handler()) {
497 // TODO(davidben): Fix Blink handling of prefetches so they are not
498 // cancelled on navigate away and end up in the local cache.
499 info
->detachable_handler()->Detach();
503 // TODO(darin): Perhaps we should really be looking to see if the status is
505 bool was_pending
= request_
->is_pending();
507 if (login_delegate_
.get()) {
508 login_delegate_
->OnRequestCancelled();
509 login_delegate_
= NULL
;
511 ssl_client_auth_handler_
.reset();
513 if (!started_request_
) {
514 times_cancelled_before_request_start_
++;
516 times_cancelled_after_request_start_
++;
519 request_
->CancelWithError(error
);
522 // If the request isn't in flight, then we won't get an asynchronous
523 // notification from the request, so we have to signal ourselves to finish
525 base::ThreadTaskRunnerHandle::Get()->PostTask(
526 FROM_HERE
, base::Bind(&ResourceLoader::ResponseCompleted
,
527 weak_ptr_factory_
.GetWeakPtr()));
531 void ResourceLoader::CompleteResponseStarted() {
532 ResourceRequestInfoImpl
* info
= GetRequestInfo();
533 scoped_refptr
<ResourceResponse
> response(new ResourceResponse());
534 PopulateResourceResponse(info
, request_
.get(), response
.get());
536 delegate_
->DidReceiveResponse(this);
538 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
539 tracked_objects::ScopedTracker
tracking_profile(
540 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseStarted()"));
543 if (!handler_
->OnResponseStarted(response
.get(), &defer
)) {
546 read_deferral_start_time_
= base::TimeTicks::Now();
547 deferred_stage_
= DEFERRED_READ
; // Read first chunk when resumed.
551 void ResourceLoader::StartReading(bool is_continuation
) {
553 ReadMore(&bytes_read
);
555 // If IO is pending, wait for the URLRequest to call OnReadCompleted.
556 if (request_
->status().is_io_pending())
559 if (!is_continuation
|| bytes_read
<= 0) {
560 OnReadCompleted(request_
.get(), bytes_read
);
562 // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
563 // thread in case the URLRequest can provide data synchronously.
564 base::ThreadTaskRunnerHandle::Get()->PostTask(
566 base::Bind(&ResourceLoader::OnReadCompleted
,
567 weak_ptr_factory_
.GetWeakPtr(), request_
.get(), bytes_read
));
571 void ResourceLoader::ResumeReading() {
572 DCHECK(!is_deferred());
574 if (!read_deferral_start_time_
.is_null()) {
575 UMA_HISTOGRAM_TIMES("Net.ResourceLoader.ReadDeferral",
576 base::TimeTicks::Now() - read_deferral_start_time_
);
577 read_deferral_start_time_
= base::TimeTicks();
579 if (request_
->status().is_success()) {
580 StartReading(false); // Read the next chunk (OK to complete synchronously).
586 void ResourceLoader::ReadMore(int* bytes_read
) {
587 DCHECK(!is_deferred());
589 // Make sure we track the buffer in at least one place. This ensures it gets
590 // deleted even in the case the request has already finished its job and
591 // doesn't use the buffer.
592 scoped_refptr
<net::IOBuffer
> buf
;
595 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
596 tracked_objects::ScopedTracker
tracking_profile2(
597 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnWillRead()"));
599 if (!handler_
->OnWillRead(&buf
, &buf_size
, -1)) {
606 DCHECK(buf_size
> 0);
608 request_
->Read(buf
.get(), buf_size
, bytes_read
);
610 // No need to check the return value here as we'll detect errors by
611 // inspecting the URLRequest's status.
614 void ResourceLoader::CompleteRead(int bytes_read
) {
615 DCHECK(bytes_read
>= 0);
616 DCHECK(request_
->status().is_success());
618 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
619 tracked_objects::ScopedTracker
tracking_profile(
620 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnReadCompleted()"));
623 if (!handler_
->OnReadCompleted(bytes_read
, &defer
)) {
627 bytes_read
> 0 ? DEFERRED_READ
: DEFERRED_RESPONSE_COMPLETE
;
630 // Note: the request may still have been cancelled while OnReadCompleted
631 // returns true if OnReadCompleted caused request to get cancelled
632 // out-of-band. (In AwResourceDispatcherHostDelegate::DownloadStarting, for
636 void ResourceLoader::ResponseCompleted() {
637 DVLOG(1) << "ResponseCompleted: " << request_
->url().spec();
639 ResourceRequestInfoImpl
* info
= GetRequestInfo();
641 std::string security_info
;
642 const net::SSLInfo
& ssl_info
= request_
->ssl_info();
643 if (ssl_info
.cert
.get() != NULL
) {
644 SSLStatus ssl_status
;
645 GetSSLStatusForRequest(request_
->url(), ssl_info
, info
->GetChildID(),
648 security_info
= SerializeSecurityInfo(ssl_status
);
653 // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
654 tracked_objects::ScopedTracker
tracking_profile(
655 FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseCompleted()"));
657 handler_
->OnResponseCompleted(request_
->status(), security_info
, &defer
);
660 // The handler is not ready to die yet. We will call DidFinishLoading when
662 deferred_stage_
= DEFERRED_FINISH
;
664 // This will result in our destruction.
665 CallDidFinishLoading();
669 void ResourceLoader::CallDidFinishLoading() {
670 delegate_
->DidFinishLoading(this);
673 void ResourceLoader::RecordHistograms() {
674 ResourceRequestInfoImpl
* info
= GetRequestInfo();
676 if (info
->GetResourceType() == RESOURCE_TYPE_PREFETCH
) {
677 PrefetchStatus status
= STATUS_UNDEFINED
;
678 TimeDelta total_time
= base::TimeTicks::Now() - request_
->creation_time();
680 switch (request_
->status().status()) {
681 case net::URLRequestStatus::SUCCESS
:
682 if (request_
->was_cached()) {
683 status
= STATUS_SUCCESS_FROM_CACHE
;
684 UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromCache",
687 status
= STATUS_SUCCESS_FROM_NETWORK
;
688 UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromNetwork",
692 case net::URLRequestStatus::CANCELED
:
693 status
= STATUS_CANCELED
;
694 UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeBeforeCancel", total_time
);
696 case net::URLRequestStatus::IO_PENDING
:
697 case net::URLRequestStatus::FAILED
:
698 status
= STATUS_UNDEFINED
;
702 UMA_HISTOGRAM_ENUMERATION("Net.Prefetch.Pattern", status
, STATUS_MAX
);
706 } // namespace content