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.
4
5#include "content/browser/loader/resource_loader.h"
6
7#include "base/command_line.h"
8#include "base/message_loop/message_loop.h"
9#include "base/metrics/histogram.h"
10#include "base/time/time.h"
11#include "content/browser/child_process_security_policy_impl.h"
12#include "content/browser/loader/cross_site_resource_handler.h"
13#include "content/browser/loader/detachable_resource_handler.h"
14#include "content/browser/loader/resource_loader_delegate.h"
15#include "content/browser/loader/resource_request_info_impl.h"
16#include "content/browser/ssl/ssl_client_auth_handler.h"
17#include "content/browser/ssl/ssl_manager.h"
18#include "content/common/ssl_status_serialization.h"
19#include "content/public/browser/cert_store.h"
20#include "content/public/browser/resource_context.h"
21#include "content/public/browser/resource_dispatcher_host_login_delegate.h"
22#include "content/public/browser/signed_certificate_timestamp_store.h"
23#include "content/public/common/content_client.h"
24#include "content/public/common/content_switches.h"
25#include "content/public/common/process_type.h"
26#include "content/public/common/resource_response.h"
27#include "net/base/io_buffer.h"
28#include "net/base/load_flags.h"
29#include "net/http/http_response_headers.h"
30#include "net/ssl/client_cert_store.h"
31#include "net/url_request/url_request_status.h"
32#include "webkit/browser/appcache/appcache_interceptor.h"
33
34using base::TimeDelta;
35using base::TimeTicks;
36
37namespace content {
38namespace {
39
40void PopulateResourceResponse(net::URLRequest* request,
41                              ResourceResponse* response) {
42  response->head.error_code = request->status().error();
43  response->head.request_time = request->request_time();
44  response->head.response_time = request->response_time();
45  response->head.headers = request->response_headers();
46  request->GetCharset(&response->head.charset);
47  response->head.content_length = request->GetExpectedContentSize();
48  request->GetMimeType(&response->head.mime_type);
49  net::HttpResponseInfo response_info = request->response_info();
50  response->head.was_fetched_via_spdy = response_info.was_fetched_via_spdy;
51  response->head.was_npn_negotiated = response_info.was_npn_negotiated;
52  response->head.npn_negotiated_protocol =
53      response_info.npn_negotiated_protocol;
54  response->head.connection_info = response_info.connection_info;
55  response->head.was_fetched_via_proxy = request->was_fetched_via_proxy();
56  response->head.socket_address = request->GetSocketAddress();
57  appcache::AppCacheInterceptor::GetExtraResponseInfo(
58      request,
59      &response->head.appcache_id,
60      &response->head.appcache_manifest_url);
61  // TODO(mmenke):  Figure out if LOAD_ENABLE_LOAD_TIMING is safe to remove.
62  if (request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING)
63    request->GetLoadTimingInfo(&response->head.load_timing);
64}
65
66}  // namespace
67
68ResourceLoader::ResourceLoader(scoped_ptr<net::URLRequest> request,
69                               scoped_ptr<ResourceHandler> handler,
70                               ResourceLoaderDelegate* delegate)
71    : deferred_stage_(DEFERRED_NONE),
72      request_(request.Pass()),
73      handler_(handler.Pass()),
74      delegate_(delegate),
75      last_upload_position_(0),
76      waiting_for_upload_progress_ack_(false),
77      is_transferring_(false),
78      weak_ptr_factory_(this) {
79  request_->set_delegate(this);
80  handler_->SetController(this);
81}
82
83ResourceLoader::~ResourceLoader() {
84  if (login_delegate_.get())
85    login_delegate_->OnRequestCancelled();
86  if (ssl_client_auth_handler_.get())
87    ssl_client_auth_handler_->OnRequestCancelled();
88
89  // Run ResourceHandler destructor before we tear-down the rest of our state
90  // as the ResourceHandler may want to inspect the URLRequest and other state.
91  handler_.reset();
92}
93
94void ResourceLoader::StartRequest() {
95  if (delegate_->HandleExternalProtocol(this, request_->url())) {
96    CancelAndIgnore();
97    return;
98  }
99
100  // Give the handler a chance to delay the URLRequest from being started.
101  bool defer_start = false;
102  if (!handler_->OnWillStart(GetRequestInfo()->GetRequestID(), request_->url(),
103                             &defer_start)) {
104    Cancel();
105    return;
106  }
107
108  if (defer_start) {
109    deferred_stage_ = DEFERRED_START;
110  } else {
111    StartRequestInternal();
112  }
113}
114
115void ResourceLoader::CancelRequest(bool from_renderer) {
116  CancelRequestInternal(net::ERR_ABORTED, from_renderer);
117}
118
119void ResourceLoader::CancelAndIgnore() {
120  ResourceRequestInfoImpl* info = GetRequestInfo();
121  info->set_was_ignored_by_handler(true);
122  CancelRequest(false);
123}
124
125void ResourceLoader::CancelWithError(int error_code) {
126  CancelRequestInternal(error_code, false);
127}
128
129void ResourceLoader::ReportUploadProgress() {
130  ResourceRequestInfoImpl* info = GetRequestInfo();
131
132  if (waiting_for_upload_progress_ack_)
133    return;  // Send one progress event at a time.
134
135  net::UploadProgress progress = request_->GetUploadProgress();
136  if (!progress.size())
137    return;  // Nothing to upload.
138
139  if (progress.position() == last_upload_position_)
140    return;  // No progress made since last time.
141
142  const uint64 kHalfPercentIncrements = 200;
143  const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000);
144
145  uint64 amt_since_last = progress.position() - last_upload_position_;
146  TimeDelta time_since_last = TimeTicks::Now() - last_upload_ticks_;
147
148  bool is_finished = (progress.size() == progress.position());
149  bool enough_new_progress =
150      (amt_since_last > (progress.size() / kHalfPercentIncrements));
151  bool too_much_time_passed = time_since_last > kOneSecond;
152
153  if (is_finished || enough_new_progress || too_much_time_passed) {
154    if (request_->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS) {
155      handler_->OnUploadProgress(
156          info->GetRequestID(), progress.position(), progress.size());
157      waiting_for_upload_progress_ack_ = true;
158    }
159    last_upload_ticks_ = TimeTicks::Now();
160    last_upload_position_ = progress.position();
161  }
162}
163
164void ResourceLoader::MarkAsTransferring(const GURL& target_url) {
165  CHECK_EQ(GetRequestInfo()->GetResourceType(), ResourceType::MAIN_FRAME)
166      << "Cannot transfer non-main frame navigations";
167  is_transferring_ = true;
168
169  // When transferring a request to another process, the renderer doesn't get
170  // a chance to update the cookie policy URL. Do it here instead.
171  request()->set_first_party_for_cookies(target_url);
172}
173
174void ResourceLoader::CompleteTransfer() {
175  DCHECK_EQ(DEFERRED_READ, deferred_stage_);
176
177  is_transferring_ = false;
178  GetRequestInfo()->cross_site_handler()->ResumeResponse();
179}
180
181ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() {
182  return ResourceRequestInfoImpl::ForRequest(request_.get());
183}
184
185void ResourceLoader::ClearLoginDelegate() {
186  login_delegate_ = NULL;
187}
188
189void ResourceLoader::ClearSSLClientAuthHandler() {
190  ssl_client_auth_handler_ = NULL;
191}
192
193void ResourceLoader::OnUploadProgressACK() {
194  waiting_for_upload_progress_ack_ = false;
195}
196
197void ResourceLoader::OnReceivedRedirect(net::URLRequest* unused,
198                                        const GURL& new_url,
199                                        bool* defer) {
200  DCHECK_EQ(request_.get(), unused);
201
202  VLOG(1) << "OnReceivedRedirect: " << request_->url().spec();
203  DCHECK(request_->status().is_success());
204
205  ResourceRequestInfoImpl* info = GetRequestInfo();
206
207  if (info->process_type() != PROCESS_TYPE_PLUGIN &&
208      !ChildProcessSecurityPolicyImpl::GetInstance()->
209          CanRequestURL(info->GetChildID(), new_url)) {
210    VLOG(1) << "Denied unauthorized request for "
211            << new_url.possibly_invalid_spec();
212
213    // Tell the renderer that this request was disallowed.
214    Cancel();
215    return;
216  }
217
218  delegate_->DidReceiveRedirect(this, new_url);
219
220  if (delegate_->HandleExternalProtocol(this, new_url)) {
221    // The request is complete so we can remove it.
222    CancelAndIgnore();
223    return;
224  }
225
226  scoped_refptr<ResourceResponse> response(new ResourceResponse());
227  PopulateResourceResponse(request_.get(), response.get());
228
229  if (!handler_->OnRequestRedirected(
230          info->GetRequestID(), new_url, response.get(), defer)) {
231    Cancel();
232  } else if (*defer) {
233    deferred_stage_ = DEFERRED_REDIRECT;  // Follow redirect when resumed.
234  }
235}
236
237void ResourceLoader::OnAuthRequired(net::URLRequest* unused,
238                                    net::AuthChallengeInfo* auth_info) {
239  DCHECK_EQ(request_.get(), unused);
240
241  if (request_->load_flags() & net::LOAD_DO_NOT_PROMPT_FOR_LOGIN) {
242    request_->CancelAuth();
243    return;
244  }
245
246  if (!delegate_->AcceptAuthRequest(this, auth_info)) {
247    request_->CancelAuth();
248    return;
249  }
250
251  // Create a login dialog on the UI thread to get authentication data, or pull
252  // from cache and continue on the IO thread.
253
254  DCHECK(!login_delegate_.get())
255      << "OnAuthRequired called with login_delegate pending";
256  login_delegate_ = delegate_->CreateLoginDelegate(this, auth_info);
257  if (!login_delegate_.get())
258    request_->CancelAuth();
259}
260
261void ResourceLoader::OnCertificateRequested(
262    net::URLRequest* unused,
263    net::SSLCertRequestInfo* cert_info) {
264  DCHECK_EQ(request_.get(), unused);
265
266  if (!delegate_->AcceptSSLClientCertificateRequest(this, cert_info)) {
267    request_->Cancel();
268    return;
269  }
270
271  DCHECK(!ssl_client_auth_handler_.get())
272      << "OnCertificateRequested called with ssl_client_auth_handler pending";
273  ssl_client_auth_handler_ = new SSLClientAuthHandler(
274      GetRequestInfo()->GetContext()->CreateClientCertStore(),
275      request_.get(),
276      cert_info);
277  ssl_client_auth_handler_->SelectCertificate();
278}
279
280void ResourceLoader::OnSSLCertificateError(net::URLRequest* request,
281                                           const net::SSLInfo& ssl_info,
282                                           bool fatal) {
283  ResourceRequestInfoImpl* info = GetRequestInfo();
284
285  int render_process_id;
286  int render_view_id;
287  if (!info->GetAssociatedRenderView(&render_process_id, &render_view_id))
288    NOTREACHED();
289
290  SSLManager::OnSSLCertificateError(
291      weak_ptr_factory_.GetWeakPtr(),
292      info->GetGlobalRequestID(),
293      info->GetResourceType(),
294      request_->url(),
295      render_process_id,
296      render_view_id,
297      ssl_info,
298      fatal);
299}
300
301void ResourceLoader::OnResponseStarted(net::URLRequest* unused) {
302  DCHECK_EQ(request_.get(), unused);
303
304  VLOG(1) << "OnResponseStarted: " << request_->url().spec();
305
306  // The CanLoadPage check should take place after any server redirects have
307  // finished, at the point in time that we know a page will commit in the
308  // renderer process.
309  ResourceRequestInfoImpl* info = GetRequestInfo();
310  ChildProcessSecurityPolicyImpl* policy =
311      ChildProcessSecurityPolicyImpl::GetInstance();
312  if (!policy->CanLoadPage(info->GetChildID(),
313                           request_->url(),
314                           info->GetResourceType())) {
315    Cancel();
316    return;
317  }
318
319  if (!request_->status().is_success()) {
320    ResponseCompleted();
321    return;
322  }
323
324  // We want to send a final upload progress message prior to sending the
325  // response complete message even if we're waiting for an ack to to a
326  // previous upload progress message.
327  waiting_for_upload_progress_ack_ = false;
328  ReportUploadProgress();
329
330  CompleteResponseStarted();
331
332  if (is_deferred())
333    return;
334
335  if (request_->status().is_success()) {
336    StartReading(false);  // Read the first chunk.
337  } else {
338    ResponseCompleted();
339  }
340}
341
342void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) {
343  DCHECK_EQ(request_.get(), unused);
344  VLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\""
345          << " bytes_read = " << bytes_read;
346
347  // bytes_read == -1 always implies an error.
348  if (bytes_read == -1 || !request_->status().is_success()) {
349    ResponseCompleted();
350    return;
351  }
352
353  CompleteRead(bytes_read);
354
355  if (is_deferred())
356    return;
357
358  if (request_->status().is_success() && bytes_read > 0) {
359    StartReading(true);  // Read the next chunk.
360  } else {
361    ResponseCompleted();
362  }
363}
364
365void ResourceLoader::CancelSSLRequest(const GlobalRequestID& id,
366                                      int error,
367                                      const net::SSLInfo* ssl_info) {
368  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
369
370  // The request can be NULL if it was cancelled by the renderer (as the
371  // request of the user navigating to a new page from the location bar).
372  if (!request_->is_pending())
373    return;
374  DVLOG(1) << "CancelSSLRequest() url: " << request_->url().spec();
375
376  if (ssl_info) {
377    request_->CancelWithSSLError(error, *ssl_info);
378  } else {
379    request_->CancelWithError(error);
380  }
381}
382
383void ResourceLoader::ContinueSSLRequest(const GlobalRequestID& id) {
384  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
385
386  DVLOG(1) << "ContinueSSLRequest() url: " << request_->url().spec();
387
388  request_->ContinueDespiteLastError();
389}
390
391void ResourceLoader::Resume() {
392  DCHECK(!is_transferring_);
393
394  DeferredStage stage = deferred_stage_;
395  deferred_stage_ = DEFERRED_NONE;
396  switch (stage) {
397    case DEFERRED_NONE:
398      NOTREACHED();
399      break;
400    case DEFERRED_START:
401      StartRequestInternal();
402      break;
403    case DEFERRED_REDIRECT:
404      request_->FollowDeferredRedirect();
405      break;
406    case DEFERRED_READ:
407      base::MessageLoop::current()->PostTask(
408          FROM_HERE,
409          base::Bind(&ResourceLoader::ResumeReading,
410                     weak_ptr_factory_.GetWeakPtr()));
411      break;
412    case DEFERRED_FINISH:
413      // Delay self-destruction since we don't know how we were reached.
414      base::MessageLoop::current()->PostTask(
415          FROM_HERE,
416          base::Bind(&ResourceLoader::CallDidFinishLoading,
417                     weak_ptr_factory_.GetWeakPtr()));
418      break;
419  }
420}
421
422void ResourceLoader::Cancel() {
423  CancelRequest(false);
424}
425
426void ResourceLoader::StartRequestInternal() {
427  DCHECK(!request_->is_pending());
428  request_->Start();
429
430  delegate_->DidStartRequest(this);
431}
432
433void ResourceLoader::CancelRequestInternal(int error, bool from_renderer) {
434  VLOG(1) << "CancelRequestInternal: " << request_->url().spec();
435
436  ResourceRequestInfoImpl* info = GetRequestInfo();
437
438  // WebKit will send us a cancel for downloads since it no longer handles
439  // them.  In this case, ignore the cancel since we handle downloads in the
440  // browser.
441  if (from_renderer && (info->IsDownload() || info->is_stream()))
442    return;
443
444  if (from_renderer && info->detachable_handler()) {
445    // TODO(davidben): Fix Blink handling of prefetches so they are not
446    // cancelled on navigate away and end up in the local cache.
447    info->detachable_handler()->Detach();
448    return;
449  }
450
451  // TODO(darin): Perhaps we should really be looking to see if the status is
452  // IO_PENDING?
453  bool was_pending = request_->is_pending();
454
455  if (login_delegate_.get()) {
456    login_delegate_->OnRequestCancelled();
457    login_delegate_ = NULL;
458  }
459  if (ssl_client_auth_handler_.get()) {
460    ssl_client_auth_handler_->OnRequestCancelled();
461    ssl_client_auth_handler_ = NULL;
462  }
463
464  request_->CancelWithError(error);
465
466  if (!was_pending) {
467    // If the request isn't in flight, then we won't get an asynchronous
468    // notification from the request, so we have to signal ourselves to finish
469    // this request.
470    base::MessageLoop::current()->PostTask(
471        FROM_HERE,
472        base::Bind(&ResourceLoader::ResponseCompleted,
473                   weak_ptr_factory_.GetWeakPtr()));
474  }
475}
476
477void ResourceLoader::StoreSignedCertificateTimestamps(
478    const net::SignedCertificateTimestampAndStatusList& sct_list,
479    int process_id,
480    SignedCertificateTimestampIDStatusList* sct_ids) {
481  SignedCertificateTimestampStore* sct_store(
482      SignedCertificateTimestampStore::GetInstance());
483
484  for (net::SignedCertificateTimestampAndStatusList::const_iterator iter =
485       sct_list.begin(); iter != sct_list.end(); ++iter) {
486    const int sct_id(sct_store->Store(iter->sct_, process_id));
487    sct_ids->push_back(
488        SignedCertificateTimestampIDAndStatus(sct_id, iter->status_));
489  }
490}
491
492void ResourceLoader::CompleteResponseStarted() {
493  ResourceRequestInfoImpl* info = GetRequestInfo();
494
495  scoped_refptr<ResourceResponse> response(new ResourceResponse());
496  PopulateResourceResponse(request_.get(), response.get());
497
498  if (request_->ssl_info().cert.get()) {
499    int cert_id = CertStore::GetInstance()->StoreCert(
500        request_->ssl_info().cert.get(), info->GetChildID());
501
502    SignedCertificateTimestampIDStatusList signed_certificate_timestamp_ids;
503    StoreSignedCertificateTimestamps(
504        request_->ssl_info().signed_certificate_timestamps,
505        info->GetChildID(),
506        &signed_certificate_timestamp_ids);
507
508    response->head.security_info = SerializeSecurityInfo(
509        cert_id,
510        request_->ssl_info().cert_status,
511        request_->ssl_info().security_bits,
512        request_->ssl_info().connection_status,
513        signed_certificate_timestamp_ids);
514  } else {
515    // We should not have any SSL state.
516    DCHECK(!request_->ssl_info().cert_status &&
517           request_->ssl_info().security_bits == -1 &&
518           !request_->ssl_info().connection_status);
519  }
520
521  delegate_->DidReceiveResponse(this);
522
523  bool defer = false;
524  if (!handler_->OnResponseStarted(
525          info->GetRequestID(), response.get(), &defer)) {
526    Cancel();
527  } else if (defer) {
528    read_deferral_start_time_ = base::TimeTicks::Now();
529    deferred_stage_ = DEFERRED_READ;  // Read first chunk when resumed.
530  }
531}
532
533void ResourceLoader::StartReading(bool is_continuation) {
534  int bytes_read = 0;
535  ReadMore(&bytes_read);
536
537  // If IO is pending, wait for the URLRequest to call OnReadCompleted.
538  if (request_->status().is_io_pending())
539    return;
540
541  if (!is_continuation || bytes_read <= 0) {
542    OnReadCompleted(request_.get(), bytes_read);
543  } else {
544    // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
545    // thread in case the URLRequest can provide data synchronously.
546    base::MessageLoop::current()->PostTask(
547        FROM_HERE,
548        base::Bind(&ResourceLoader::OnReadCompleted,
549                   weak_ptr_factory_.GetWeakPtr(),
550                   request_.get(),
551                   bytes_read));
552  }
553}
554
555void ResourceLoader::ResumeReading() {
556  DCHECK(!is_deferred());
557
558  if (!read_deferral_start_time_.is_null()) {
559    UMA_HISTOGRAM_TIMES("Net.ResourceLoader.ReadDeferral",
560                        base::TimeTicks::Now() - read_deferral_start_time_);
561    read_deferral_start_time_ = base::TimeTicks();
562  }
563  if (request_->status().is_success()) {
564    StartReading(false);  // Read the next chunk (OK to complete synchronously).
565  } else {
566    ResponseCompleted();
567  }
568}
569
570void ResourceLoader::ReadMore(int* bytes_read) {
571  ResourceRequestInfoImpl* info = GetRequestInfo();
572  DCHECK(!is_deferred());
573
574  // Make sure we track the buffer in at least one place.  This ensures it gets
575  // deleted even in the case the request has already finished its job and
576  // doesn't use the buffer.
577  scoped_refptr<net::IOBuffer> buf;
578  int buf_size;
579  if (!handler_->OnWillRead(info->GetRequestID(), &buf, &buf_size, -1)) {
580    Cancel();
581    return;
582  }
583
584  DCHECK(buf);
585  DCHECK(buf_size > 0);
586
587  request_->Read(buf.get(), buf_size, bytes_read);
588
589  // No need to check the return value here as we'll detect errors by
590  // inspecting the URLRequest's status.
591}
592
593void ResourceLoader::CompleteRead(int bytes_read) {
594  DCHECK(bytes_read >= 0);
595  DCHECK(request_->status().is_success());
596
597  ResourceRequestInfoImpl* info = GetRequestInfo();
598
599  bool defer = false;
600  if (!handler_->OnReadCompleted(info->GetRequestID(), bytes_read, &defer)) {
601    Cancel();
602  } else if (defer) {
603    deferred_stage_ = DEFERRED_READ;  // Read next chunk when resumed.
604  }
605}
606
607void ResourceLoader::ResponseCompleted() {
608  VLOG(1) << "ResponseCompleted: " << request_->url().spec();
609  RecordHistograms();
610  ResourceRequestInfoImpl* info = GetRequestInfo();
611
612  std::string security_info;
613  const net::SSLInfo& ssl_info = request_->ssl_info();
614  if (ssl_info.cert.get() != NULL) {
615    int cert_id = CertStore::GetInstance()->StoreCert(ssl_info.cert.get(),
616                                                      info->GetChildID());
617    SignedCertificateTimestampIDStatusList signed_certificate_timestamp_ids;
618    StoreSignedCertificateTimestamps(ssl_info.signed_certificate_timestamps,
619                                     info->GetChildID(),
620                                     &signed_certificate_timestamp_ids);
621
622    security_info = SerializeSecurityInfo(
623        cert_id, ssl_info.cert_status, ssl_info.security_bits,
624        ssl_info.connection_status, signed_certificate_timestamp_ids);
625  }
626
627  bool defer = false;
628  handler_->OnResponseCompleted(info->GetRequestID(), request_->status(),
629                                security_info, &defer);
630  if (defer) {
631    // The handler is not ready to die yet.  We will call DidFinishLoading when
632    // we resume.
633    deferred_stage_ = DEFERRED_FINISH;
634  } else {
635    // This will result in our destruction.
636    CallDidFinishLoading();
637  }
638}
639
640void ResourceLoader::CallDidFinishLoading() {
641  delegate_->DidFinishLoading(this);
642}
643
644void ResourceLoader::RecordHistograms() {
645  ResourceRequestInfoImpl* info = GetRequestInfo();
646
647  if (info->GetResourceType() == ResourceType::PREFETCH) {
648    PrefetchStatus status = STATUS_UNDEFINED;
649    TimeDelta total_time = base::TimeTicks::Now() - request_->creation_time();
650
651    switch (request_->status().status()) {
652      case net::URLRequestStatus::SUCCESS:
653        if (request_->was_cached()) {
654          status = STATUS_SUCCESS_FROM_CACHE;
655          UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromCache",
656                              total_time);
657        } else {
658          status = STATUS_SUCCESS_FROM_NETWORK;
659          UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromNetwork",
660                              total_time);
661        }
662        break;
663      case net::URLRequestStatus::CANCELED:
664        status = STATUS_CANCELED;
665        UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeBeforeCancel", total_time);
666        break;
667      case net::URLRequestStatus::IO_PENDING:
668      case net::URLRequestStatus::FAILED:
669        status = STATUS_UNDEFINED;
670        break;
671    }
672
673    UMA_HISTOGRAM_ENUMERATION("Net.Prefetch.Pattern", status, STATUS_MAX);
674  }
675}
676
677}  // namespace content
678