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 34 using base::TimeDelta; 35 using base::TimeTicks; 36 37 namespace content { 38 namespace { 39 40 void 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 68 ResourceLoader::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 83 ResourceLoader::~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 94 void 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 115 void ResourceLoader::CancelRequest(bool from_renderer) { 116 CancelRequestInternal(net::ERR_ABORTED, from_renderer); 117 } 118 119 void ResourceLoader::CancelAndIgnore() { 120 ResourceRequestInfoImpl* info = GetRequestInfo(); 121 info->set_was_ignored_by_handler(true); 122 CancelRequest(false); 123 } 124 125 void ResourceLoader::CancelWithError(int error_code) { 126 CancelRequestInternal(error_code, false); 127 } 128 129 void 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 164 void 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 174 void ResourceLoader::CompleteTransfer() { 175 DCHECK_EQ(DEFERRED_READ, deferred_stage_); 176 177 is_transferring_ = false; 178 GetRequestInfo()->cross_site_handler()->ResumeResponse(); 179 } 180 181 ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() { 182 return ResourceRequestInfoImpl::ForRequest(request_.get()); 183 } 184 185 void ResourceLoader::ClearLoginDelegate() { 186 login_delegate_ = NULL; 187 } 188 189 void ResourceLoader::ClearSSLClientAuthHandler() { 190 ssl_client_auth_handler_ = NULL; 191 } 192 193 void ResourceLoader::OnUploadProgressACK() { 194 waiting_for_upload_progress_ack_ = false; 195 } 196 197 void 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 237 void 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 261 void 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 280 void 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 301 void 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 342 void 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 365 void 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 383 void 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 391 void 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 422 void ResourceLoader::Cancel() { 423 CancelRequest(false); 424 } 425 426 void ResourceLoader::StartRequestInternal() { 427 DCHECK(!request_->is_pending()); 428 request_->Start(); 429 430 delegate_->DidStartRequest(this); 431 } 432 433 void 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 477 void 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 492 void 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 533 void 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 555 void 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 570 void 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 593 void 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 607 void 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 640 void ResourceLoader::CallDidFinishLoading() { 641 delegate_->DidFinishLoading(this); 642 } 643 644 void 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