| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/loader/resource_loader.h" |
| |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram.h" |
| #include "base/profiler/scoped_tracker.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "content/browser/appcache/appcache_interceptor.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/loader/cross_site_resource_handler.h" |
| #include "content/browser/loader/detachable_resource_handler.h" |
| #include "content/browser/loader/resource_loader_delegate.h" |
| #include "content/browser/loader/resource_request_info_impl.h" |
| #include "content/browser/service_worker/service_worker_request_handler.h" |
| #include "content/browser/ssl/ssl_client_auth_handler.h" |
| #include "content/browser/ssl/ssl_manager.h" |
| #include "content/browser/ssl/ssl_policy.h" |
| #include "content/common/ssl_status_serialization.h" |
| #include "content/public/browser/cert_store.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/resource_dispatcher_host_login_delegate.h" |
| #include "content/public/browser/signed_certificate_timestamp_store.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/process_type.h" |
| #include "content/public/common/resource_response.h" |
| #include "content/public/common/security_style.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/ssl/client_cert_store.h" |
| #include "net/ssl/ssl_platform_key.h" |
| #include "net/ssl/ssl_private_key.h" |
| #include "net/url_request/redirect_info.h" |
| #include "net/url_request/url_request_status.h" |
| |
| using base::TimeDelta; |
| using base::TimeTicks; |
| |
| namespace content { |
| namespace { |
| |
| void StoreSignedCertificateTimestamps( |
| const net::SignedCertificateTimestampAndStatusList& sct_list, |
| int process_id, |
| SignedCertificateTimestampIDStatusList* sct_ids) { |
| SignedCertificateTimestampStore* sct_store( |
| SignedCertificateTimestampStore::GetInstance()); |
| |
| for (auto iter = sct_list.begin(); iter != sct_list.end(); ++iter) { |
| const int sct_id(sct_store->Store(iter->sct.get(), process_id)); |
| sct_ids->push_back( |
| SignedCertificateTimestampIDAndStatus(sct_id, iter->status)); |
| } |
| } |
| |
| void GetSSLStatusForRequest(const GURL& url, |
| const net::SSLInfo& ssl_info, |
| int child_id, |
| SSLStatus* ssl_status) { |
| DCHECK(ssl_info.cert); |
| |
| int cert_id = |
| CertStore::GetInstance()->StoreCert(ssl_info.cert.get(), child_id); |
| |
| SignedCertificateTimestampIDStatusList signed_certificate_timestamp_ids; |
| StoreSignedCertificateTimestamps(ssl_info.signed_certificate_timestamps, |
| child_id, &signed_certificate_timestamp_ids); |
| |
| *ssl_status = SSLStatus(SSLPolicy::GetSecurityStyleForResource( |
| url, cert_id, ssl_info.cert_status), |
| cert_id, signed_certificate_timestamp_ids, ssl_info); |
| } |
| |
| void PopulateResourceResponse(ResourceRequestInfoImpl* info, |
| net::URLRequest* request, |
| ResourceResponse* response) { |
| response->head.request_time = request->request_time(); |
| response->head.response_time = request->response_time(); |
| response->head.headers = request->response_headers(); |
| request->GetCharset(&response->head.charset); |
| response->head.content_length = request->GetExpectedContentSize(); |
| request->GetMimeType(&response->head.mime_type); |
| net::HttpResponseInfo response_info = request->response_info(); |
| response->head.was_fetched_via_spdy = response_info.was_fetched_via_spdy; |
| response->head.was_npn_negotiated = response_info.was_npn_negotiated; |
| response->head.npn_negotiated_protocol = |
| response_info.npn_negotiated_protocol; |
| response->head.connection_info = response_info.connection_info; |
| response->head.was_fetched_via_proxy = request->was_fetched_via_proxy(); |
| response->head.proxy_server = response_info.proxy_server; |
| response->head.socket_address = request->GetSocketAddress(); |
| const content::ResourceRequestInfo* request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| if (request_info) |
| response->head.is_using_lofi = request_info->IsUsingLoFi(); |
| if (ServiceWorkerRequestHandler* handler = |
| ServiceWorkerRequestHandler::GetHandler(request)) { |
| handler->GetExtraResponseInfo(&response->head); |
| } |
| AppCacheInterceptor::GetExtraResponseInfo( |
| request, &response->head.appcache_id, |
| &response->head.appcache_manifest_url); |
| if (info->is_load_timing_enabled()) |
| request->GetLoadTimingInfo(&response->head.load_timing); |
| |
| if (request->ssl_info().cert.get()) { |
| SSLStatus ssl_status; |
| GetSSLStatusForRequest(request->url(), request->ssl_info(), |
| info->GetChildID(), &ssl_status); |
| response->head.security_info = SerializeSecurityInfo(ssl_status); |
| } else { |
| // We should not have any SSL state. |
| DCHECK(!request->ssl_info().cert_status); |
| DCHECK_EQ(request->ssl_info().security_bits, -1); |
| DCHECK_EQ(request->ssl_info().key_exchange_info, 0); |
| DCHECK(!request->ssl_info().connection_status); |
| } |
| } |
| |
| } // namespace |
| |
| ResourceLoader::ResourceLoader(scoped_ptr<net::URLRequest> request, |
| scoped_ptr<ResourceHandler> handler, |
| ResourceLoaderDelegate* delegate) |
| : deferred_stage_(DEFERRED_NONE), |
| request_(request.Pass()), |
| handler_(handler.Pass()), |
| delegate_(delegate), |
| is_transferring_(false), |
| times_cancelled_before_request_start_(0), |
| started_request_(false), |
| times_cancelled_after_request_start_(0), |
| weak_ptr_factory_(this) { |
| request_->set_delegate(this); |
| handler_->SetController(this); |
| } |
| |
| ResourceLoader::~ResourceLoader() { |
| if (login_delegate_.get()) |
| login_delegate_->OnRequestCancelled(); |
| ssl_client_auth_handler_.reset(); |
| |
| // Run ResourceHandler destructor before we tear-down the rest of our state |
| // as the ResourceHandler may want to inspect the URLRequest and other state. |
| handler_.reset(); |
| } |
| |
| void ResourceLoader::StartRequest() { |
| if (delegate_->HandleExternalProtocol(this, request_->url())) { |
| CancelAndIgnore(); |
| return; |
| } |
| |
| // Give the handler a chance to delay the URLRequest from being started. |
| bool defer_start = false; |
| if (!handler_->OnWillStart(request_->url(), &defer_start)) { |
| Cancel(); |
| return; |
| } |
| |
| if (defer_start) { |
| deferred_stage_ = DEFERRED_START; |
| } else { |
| StartRequestInternal(); |
| } |
| } |
| |
| void ResourceLoader::CancelRequest(bool from_renderer) { |
| CancelRequestInternal(net::ERR_ABORTED, from_renderer); |
| } |
| |
| void ResourceLoader::CancelAndIgnore() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| info->set_was_ignored_by_handler(true); |
| CancelRequest(false); |
| } |
| |
| void ResourceLoader::CancelWithError(int error_code) { |
| CancelRequestInternal(error_code, false); |
| } |
| |
| void ResourceLoader::MarkAsTransferring() { |
| CHECK(IsResourceTypeFrame(GetRequestInfo()->GetResourceType())) |
| << "Can only transfer for navigations"; |
| is_transferring_ = true; |
| |
| int child_id = GetRequestInfo()->GetChildID(); |
| AppCacheInterceptor::PrepareForCrossSiteTransfer(request(), child_id); |
| ServiceWorkerRequestHandler* handler = |
| ServiceWorkerRequestHandler::GetHandler(request()); |
| if (handler) |
| handler->PrepareForCrossSiteTransfer(child_id); |
| } |
| |
| void ResourceLoader::CompleteTransfer() { |
| // Although CrossSiteResourceHandler defers at OnResponseStarted |
| // (DEFERRED_READ), it may be seeing a replay of events via |
| // MimeTypeResourceHandler, and so the request itself is actually deferred |
| // at a later read stage. |
| DCHECK(DEFERRED_READ == deferred_stage_ || |
| DEFERRED_RESPONSE_COMPLETE == deferred_stage_); |
| DCHECK(is_transferring_); |
| |
| // In some cases, a process transfer doesn't really happen and the |
| // request is resumed in the original process. Real transfers to a new process |
| // are completed via ResourceDispatcherHostImpl::UpdateRequestForTransfer. |
| int child_id = GetRequestInfo()->GetChildID(); |
| AppCacheInterceptor::MaybeCompleteCrossSiteTransferInOldProcess( |
| request(), child_id); |
| ServiceWorkerRequestHandler* handler = |
| ServiceWorkerRequestHandler::GetHandler(request()); |
| if (handler) |
| handler->MaybeCompleteCrossSiteTransferInOldProcess(child_id); |
| |
| is_transferring_ = false; |
| GetRequestInfo()->cross_site_handler()->ResumeResponse(); |
| } |
| |
| ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() { |
| return ResourceRequestInfoImpl::ForRequest(request_.get()); |
| } |
| |
| void ResourceLoader::ClearLoginDelegate() { |
| login_delegate_ = NULL; |
| } |
| |
| void ResourceLoader::OnReceivedRedirect(net::URLRequest* unused, |
| const net::RedirectInfo& redirect_info, |
| bool* defer) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| DVLOG(1) << "OnReceivedRedirect: " << request_->url().spec(); |
| DCHECK(request_->status().is_success()); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| if (info->GetProcessType() != PROCESS_TYPE_PLUGIN && |
| !ChildProcessSecurityPolicyImpl::GetInstance()-> |
| CanRequestURL(info->GetChildID(), redirect_info.new_url)) { |
| DVLOG(1) << "Denied unauthorized request for " |
| << redirect_info.new_url.possibly_invalid_spec(); |
| |
| // Tell the renderer that this request was disallowed. |
| Cancel(); |
| return; |
| } |
| |
| delegate_->DidReceiveRedirect(this, redirect_info.new_url); |
| |
| if (delegate_->HandleExternalProtocol(this, redirect_info.new_url)) { |
| // The request is complete so we can remove it. |
| CancelAndIgnore(); |
| return; |
| } |
| |
| scoped_refptr<ResourceResponse> response(new ResourceResponse()); |
| PopulateResourceResponse(info, request_.get(), response.get()); |
| if (!handler_->OnRequestRedirected(redirect_info, response.get(), defer)) { |
| Cancel(); |
| } else if (*defer) { |
| deferred_stage_ = DEFERRED_REDIRECT; // Follow redirect when resumed. |
| } |
| } |
| |
| void ResourceLoader::OnAuthRequired(net::URLRequest* unused, |
| net::AuthChallengeInfo* auth_info) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| if (info->do_not_prompt_for_login()) { |
| request_->CancelAuth(); |
| return; |
| } |
| |
| // Create a login dialog on the UI thread to get authentication data, or pull |
| // from cache and continue on the IO thread. |
| |
| DCHECK(!login_delegate_.get()) |
| << "OnAuthRequired called with login_delegate pending"; |
| login_delegate_ = delegate_->CreateLoginDelegate(this, auth_info); |
| if (!login_delegate_.get()) |
| request_->CancelAuth(); |
| } |
| |
| void ResourceLoader::OnCertificateRequested( |
| net::URLRequest* unused, |
| net::SSLCertRequestInfo* cert_info) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| if (request_->load_flags() & net::LOAD_PREFETCH) { |
| request_->Cancel(); |
| return; |
| } |
| |
| DCHECK(!ssl_client_auth_handler_) |
| << "OnCertificateRequested called with ssl_client_auth_handler pending"; |
| ssl_client_auth_handler_.reset(new SSLClientAuthHandler( |
| GetRequestInfo()->GetContext()->CreateClientCertStore(), request_.get(), |
| cert_info, this)); |
| ssl_client_auth_handler_->SelectCertificate(); |
| } |
| |
| void ResourceLoader::OnSSLCertificateError(net::URLRequest* request, |
| const net::SSLInfo& ssl_info, |
| bool fatal) { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| SSLManager::OnSSLCertificateError( |
| weak_ptr_factory_.GetWeakPtr(), info->GetResourceType(), request_->url(), |
| info->GetWebContentsGetterForRequest(), ssl_info, fatal); |
| } |
| |
| void ResourceLoader::OnBeforeNetworkStart(net::URLRequest* unused, |
| bool* defer) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| // Give the handler a chance to delay the URLRequest from using the network. |
| if (!handler_->OnBeforeNetworkStart(request_->url(), defer)) { |
| Cancel(); |
| return; |
| } else if (*defer) { |
| deferred_stage_ = DEFERRED_NETWORK_START; |
| } |
| } |
| |
| void ResourceLoader::OnResponseStarted(net::URLRequest* unused) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| DVLOG(1) << "OnResponseStarted: " << request_->url().spec(); |
| |
| if (!request_->status().is_success()) { |
| ResponseCompleted(); |
| return; |
| } |
| |
| CompleteResponseStarted(); |
| |
| if (is_deferred()) |
| return; |
| |
| if (request_->status().is_success()) |
| StartReading(false); // Read the first chunk. |
| else |
| ResponseCompleted(); |
| } |
| |
| void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) { |
| DCHECK_EQ(request_.get(), unused); |
| DVLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\"" |
| << " bytes_read = " << bytes_read; |
| |
| // bytes_read == -1 always implies an error. |
| if (bytes_read == -1 || !request_->status().is_success()) { |
| ResponseCompleted(); |
| return; |
| } |
| |
| CompleteRead(bytes_read); |
| |
| // If the handler cancelled or deferred the request, do not continue |
| // processing the read. If cancelled, the URLRequest has already been |
| // cancelled and will schedule an erroring OnReadCompleted later. If deferred, |
| // do nothing until resumed. |
| // |
| // Note: if bytes_read is 0 (EOF) and the handler defers, resumption will call |
| // ResponseCompleted(). |
| if (is_deferred() || !request_->status().is_success()) |
| return; |
| |
| if (bytes_read > 0) { |
| StartReading(true); // Read the next chunk. |
| } else { |
| // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed. |
| tracked_objects::ScopedTracker tracking_profile( |
| FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 ResponseCompleted()")); |
| |
| // URLRequest reported an EOF. Call ResponseCompleted. |
| DCHECK_EQ(0, bytes_read); |
| ResponseCompleted(); |
| } |
| } |
| |
| void ResourceLoader::CancelSSLRequest(int error, |
| const net::SSLInfo* ssl_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // The request can be NULL if it was cancelled by the renderer (as the |
| // request of the user navigating to a new page from the location bar). |
| if (!request_->is_pending()) |
| return; |
| DVLOG(1) << "CancelSSLRequest() url: " << request_->url().spec(); |
| |
| if (ssl_info) { |
| request_->CancelWithSSLError(error, *ssl_info); |
| } else { |
| request_->CancelWithError(error); |
| } |
| } |
| |
| void ResourceLoader::ContinueSSLRequest() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DVLOG(1) << "ContinueSSLRequest() url: " << request_->url().spec(); |
| |
| request_->ContinueDespiteLastError(); |
| } |
| |
| void ResourceLoader::ContinueWithCertificate(net::X509Certificate* cert) { |
| DCHECK(ssl_client_auth_handler_); |
| ssl_client_auth_handler_.reset(); |
| if (!cert) { |
| request_->ContinueWithCertificate(nullptr, nullptr); |
| return; |
| } |
| scoped_refptr<net::SSLPrivateKey> private_key = |
| net::FetchClientCertPrivateKey(cert); |
| request_->ContinueWithCertificate(cert, private_key.get()); |
| } |
| |
| void ResourceLoader::CancelCertificateSelection() { |
| DCHECK(ssl_client_auth_handler_); |
| ssl_client_auth_handler_.reset(); |
| request_->CancelWithError(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); |
| } |
| |
| void ResourceLoader::Resume() { |
| DCHECK(!is_transferring_); |
| |
| DeferredStage stage = deferred_stage_; |
| deferred_stage_ = DEFERRED_NONE; |
| switch (stage) { |
| case DEFERRED_NONE: |
| NOTREACHED(); |
| break; |
| case DEFERRED_START: |
| StartRequestInternal(); |
| break; |
| case DEFERRED_NETWORK_START: |
| request_->ResumeNetworkStart(); |
| break; |
| case DEFERRED_REDIRECT: |
| request_->FollowDeferredRedirect(); |
| break; |
| case DEFERRED_READ: |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&ResourceLoader::ResumeReading, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| case DEFERRED_RESPONSE_COMPLETE: |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&ResourceLoader::ResponseCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| case DEFERRED_FINISH: |
| // Delay self-destruction since we don't know how we were reached. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&ResourceLoader::CallDidFinishLoading, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| } |
| } |
| |
| void ResourceLoader::Cancel() { |
| CancelRequest(false); |
| } |
| |
| void ResourceLoader::StartRequestInternal() { |
| DCHECK(!request_->is_pending()); |
| |
| if (!request_->status().is_success()) { |
| return; |
| } |
| |
| started_request_ = true; |
| request_->Start(); |
| |
| delegate_->DidStartRequest(this); |
| } |
| |
| void ResourceLoader::CancelRequestInternal(int error, bool from_renderer) { |
| DVLOG(1) << "CancelRequestInternal: " << request_->url().spec(); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| // WebKit will send us a cancel for downloads since it no longer handles |
| // them. In this case, ignore the cancel since we handle downloads in the |
| // browser. |
| if (from_renderer && (info->IsDownload() || info->is_stream())) |
| return; |
| |
| if (from_renderer && info->detachable_handler()) { |
| // TODO(davidben): Fix Blink handling of prefetches so they are not |
| // cancelled on navigate away and end up in the local cache. |
| info->detachable_handler()->Detach(); |
| return; |
| } |
| |
| // TODO(darin): Perhaps we should really be looking to see if the status is |
| // IO_PENDING? |
| bool was_pending = request_->is_pending(); |
| |
| if (login_delegate_.get()) { |
| login_delegate_->OnRequestCancelled(); |
| login_delegate_ = NULL; |
| } |
| ssl_client_auth_handler_.reset(); |
| |
| if (!started_request_) { |
| times_cancelled_before_request_start_++; |
| } else { |
| times_cancelled_after_request_start_++; |
| } |
| |
| request_->CancelWithError(error); |
| |
| if (!was_pending) { |
| // If the request isn't in flight, then we won't get an asynchronous |
| // notification from the request, so we have to signal ourselves to finish |
| // this request. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&ResourceLoader::ResponseCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void ResourceLoader::CompleteResponseStarted() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| scoped_refptr<ResourceResponse> response(new ResourceResponse()); |
| PopulateResourceResponse(info, request_.get(), response.get()); |
| |
| delegate_->DidReceiveResponse(this); |
| |
| // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed. |
| tracked_objects::ScopedTracker tracking_profile( |
| FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseStarted()")); |
| |
| bool defer = false; |
| if (!handler_->OnResponseStarted(response.get(), &defer)) { |
| Cancel(); |
| } else if (defer) { |
| read_deferral_start_time_ = base::TimeTicks::Now(); |
| deferred_stage_ = DEFERRED_READ; // Read first chunk when resumed. |
| } |
| } |
| |
| void ResourceLoader::StartReading(bool is_continuation) { |
| int bytes_read = 0; |
| ReadMore(&bytes_read); |
| |
| // If IO is pending, wait for the URLRequest to call OnReadCompleted. |
| if (request_->status().is_io_pending()) |
| return; |
| |
| if (!is_continuation || bytes_read <= 0) { |
| OnReadCompleted(request_.get(), bytes_read); |
| } else { |
| // Else, trigger OnReadCompleted asynchronously to avoid starving the IO |
| // thread in case the URLRequest can provide data synchronously. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&ResourceLoader::OnReadCompleted, |
| weak_ptr_factory_.GetWeakPtr(), request_.get(), bytes_read)); |
| } |
| } |
| |
| void ResourceLoader::ResumeReading() { |
| DCHECK(!is_deferred()); |
| |
| if (!read_deferral_start_time_.is_null()) { |
| UMA_HISTOGRAM_TIMES("Net.ResourceLoader.ReadDeferral", |
| base::TimeTicks::Now() - read_deferral_start_time_); |
| read_deferral_start_time_ = base::TimeTicks(); |
| } |
| if (request_->status().is_success()) { |
| StartReading(false); // Read the next chunk (OK to complete synchronously). |
| } else { |
| ResponseCompleted(); |
| } |
| } |
| |
| void ResourceLoader::ReadMore(int* bytes_read) { |
| DCHECK(!is_deferred()); |
| |
| // Make sure we track the buffer in at least one place. This ensures it gets |
| // deleted even in the case the request has already finished its job and |
| // doesn't use the buffer. |
| scoped_refptr<net::IOBuffer> buf; |
| int buf_size; |
| { |
| // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed. |
| tracked_objects::ScopedTracker tracking_profile2( |
| FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnWillRead()")); |
| |
| if (!handler_->OnWillRead(&buf, &buf_size, -1)) { |
| Cancel(); |
| return; |
| } |
| } |
| |
| DCHECK(buf.get()); |
| DCHECK(buf_size > 0); |
| |
| request_->Read(buf.get(), buf_size, bytes_read); |
| |
| // No need to check the return value here as we'll detect errors by |
| // inspecting the URLRequest's status. |
| } |
| |
| void ResourceLoader::CompleteRead(int bytes_read) { |
| DCHECK(bytes_read >= 0); |
| DCHECK(request_->status().is_success()); |
| |
| // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed. |
| tracked_objects::ScopedTracker tracking_profile( |
| FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnReadCompleted()")); |
| |
| bool defer = false; |
| if (!handler_->OnReadCompleted(bytes_read, &defer)) { |
| Cancel(); |
| } else if (defer) { |
| deferred_stage_ = |
| bytes_read > 0 ? DEFERRED_READ : DEFERRED_RESPONSE_COMPLETE; |
| } |
| |
| // Note: the request may still have been cancelled while OnReadCompleted |
| // returns true if OnReadCompleted caused request to get cancelled |
| // out-of-band. (In AwResourceDispatcherHostDelegate::DownloadStarting, for |
| // instance.) |
| } |
| |
| void ResourceLoader::ResponseCompleted() { |
| DVLOG(1) << "ResponseCompleted: " << request_->url().spec(); |
| RecordHistograms(); |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| std::string security_info; |
| const net::SSLInfo& ssl_info = request_->ssl_info(); |
| if (ssl_info.cert.get() != NULL) { |
| SSLStatus ssl_status; |
| GetSSLStatusForRequest(request_->url(), ssl_info, info->GetChildID(), |
| &ssl_status); |
| |
| security_info = SerializeSecurityInfo(ssl_status); |
| } |
| |
| bool defer = false; |
| { |
| // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed. |
| tracked_objects::ScopedTracker tracking_profile( |
| FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseCompleted()")); |
| |
| handler_->OnResponseCompleted(request_->status(), security_info, &defer); |
| } |
| if (defer) { |
| // The handler is not ready to die yet. We will call DidFinishLoading when |
| // we resume. |
| deferred_stage_ = DEFERRED_FINISH; |
| } else { |
| // This will result in our destruction. |
| CallDidFinishLoading(); |
| } |
| } |
| |
| void ResourceLoader::CallDidFinishLoading() { |
| delegate_->DidFinishLoading(this); |
| } |
| |
| void ResourceLoader::RecordHistograms() { |
| if (request_->response_info().network_accessed) { |
| UMA_HISTOGRAM_ENUMERATION("Net.HttpResponseInfo.ConnectionInfo", |
| request_->response_info().connection_info, |
| net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS); |
| } |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| if (info->GetResourceType() == RESOURCE_TYPE_PREFETCH) { |
| PrefetchStatus status = STATUS_UNDEFINED; |
| TimeDelta total_time = base::TimeTicks::Now() - request_->creation_time(); |
| |
| switch (request_->status().status()) { |
| case net::URLRequestStatus::SUCCESS: |
| if (request_->was_cached()) { |
| status = STATUS_SUCCESS_FROM_CACHE; |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromCache", |
| total_time); |
| } else { |
| status = STATUS_SUCCESS_FROM_NETWORK; |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromNetwork", |
| total_time); |
| } |
| break; |
| case net::URLRequestStatus::CANCELED: |
| status = STATUS_CANCELED; |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeBeforeCancel", total_time); |
| break; |
| case net::URLRequestStatus::IO_PENDING: |
| case net::URLRequestStatus::FAILED: |
| status = STATUS_UNDEFINED; |
| break; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Net.Prefetch.Pattern", status, STATUS_MAX); |
| } |
| } |
| |
| } // namespace content |