| // 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 <utility> |
| |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/appcache/appcache_interceptor.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/loader/detachable_resource_handler.h" |
| #include "content/browser/loader/resource_controller.h" |
| #include "content/browser/loader/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/service_worker/service_worker_response_info.h" |
| #include "content/browser/ssl/ssl_client_auth_handler.h" |
| #include "content/browser/ssl/ssl_manager.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/login_delegate.h" |
| #include "content/public/common/appcache_info.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/resource_type.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/load_flags.h" |
| #include "net/cert/symantec_certs.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/nqe/effective_connection_type.h" |
| #include "net/nqe/network_quality_estimator.h" |
| #include "net/ssl/client_cert_store.h" |
| #include "net/url_request/redirect_info.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_status.h" |
| #include "services/network/loader_util.h" |
| #include "services/network/public/cpp/resource_response.h" |
| #include "url/url_constants.h" |
| |
| using base::TimeDelta; |
| using base::TimeTicks; |
| |
| namespace content { |
| namespace { |
| |
| // Copies selected fields from |in| to the returned SSLInfo. To avoid sending |
| // unnecessary data into the renderer, this only copies the fields that the |
| // renderer cares about. |
| net::SSLInfo SelectSSLInfoFields(const net::SSLInfo& in) { |
| net::SSLInfo out; |
| out.connection_status = in.connection_status; |
| out.key_exchange_group = in.key_exchange_group; |
| out.signed_certificate_timestamps = in.signed_certificate_timestamps; |
| out.cert = in.cert; |
| return out; |
| } |
| |
| void PopulateResourceResponse( |
| ResourceRequestInfoImpl* info, |
| net::URLRequest* request, |
| network::ResourceResponse* response, |
| const net::HttpRawRequestHeaders& raw_request_headers, |
| const net::HttpResponseHeaders* raw_response_headers) { |
| 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_alpn_negotiated = response_info.was_alpn_negotiated; |
| response->head.alpn_negotiated_protocol = |
| response_info.alpn_negotiated_protocol; |
| response->head.connection_info = response_info.connection_info; |
| response->head.socket_address = response_info.socket_address; |
| response->head.was_fetched_via_proxy = request->was_fetched_via_proxy(); |
| response->head.network_accessed = response_info.network_accessed; |
| response->head.async_revalidation_requested = |
| response_info.async_revalidation_requested; |
| const content::ResourceRequestInfo* request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| if (request_info) { |
| response->head.previews_state = |
| static_cast<int>(request_info->GetPreviewsState()); |
| } |
| if (info->ShouldReportRawHeaders()) { |
| response->head.raw_request_response_info = |
| network::BuildRawRequestResponseInfo(*request, raw_request_headers, |
| raw_response_headers); |
| } |
| |
| response->head.effective_connection_type = |
| net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| |
| if (info->GetResourceType() == RESOURCE_TYPE_MAIN_FRAME) { |
| DCHECK(info->IsMainFrame()); |
| net::NetworkQualityEstimator* estimator = |
| request->context()->network_quality_estimator(); |
| if (estimator) { |
| response->head.effective_connection_type = |
| estimator->GetEffectiveConnectionType(); |
| } |
| } |
| |
| const ServiceWorkerResponseInfo* service_worker_info = |
| ServiceWorkerResponseInfo::ForRequest(request); |
| if (service_worker_info) |
| service_worker_info->GetExtraResponseInfo(&response->head); |
| response->head.appcache_id = kAppCacheNoCacheId; |
| 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()) { |
| response->head.cert_status = request->ssl_info().cert_status; |
| response->head.ct_policy_compliance = |
| request->ssl_info().ct_policy_compliance; |
| response->head.is_legacy_symantec_cert = |
| (!net::IsCertStatusError(response->head.cert_status) || |
| net::IsCertStatusMinorError(response->head.cert_status)) && |
| net::IsLegacySymantecCert(request->ssl_info().public_key_hashes); |
| |
| if (info->ShouldReportSecurityInfo()) |
| response->head.ssl_info = SelectSSLInfoFields(request->ssl_info()); |
| } 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_group, 0); |
| DCHECK(!request->ssl_info().connection_status); |
| } |
| } |
| |
| } // namespace |
| |
| class ResourceLoader::Controller : public ResourceController { |
| public: |
| explicit Controller(ResourceLoader* resource_loader) |
| : resource_loader_(resource_loader){}; |
| |
| ~Controller() override {} |
| |
| // ResourceController implementation: |
| void Resume() override { |
| MarkAsUsed(); |
| resource_loader_->Resume(true /* called_from_resource_controller */); |
| } |
| |
| void Cancel() override { |
| MarkAsUsed(); |
| resource_loader_->Cancel(); |
| } |
| |
| void CancelWithError(int error_code) override { |
| MarkAsUsed(); |
| resource_loader_->CancelWithError(error_code); |
| } |
| |
| private: |
| void MarkAsUsed() { |
| #if DCHECK_IS_ON() |
| DCHECK(!used_); |
| used_ = true; |
| #endif |
| } |
| |
| ResourceLoader* const resource_loader_; |
| |
| #if DCHECK_IS_ON() |
| // Set to true once one of the ResourceContoller methods has been invoked. |
| bool used_ = false; |
| #endif |
| |
| DISALLOW_COPY_AND_ASSIGN(Controller); |
| }; |
| |
| // Helper class. Sets the stage of a ResourceLoader to DEFERRED_SYNC on |
| // construction, and on destruction does one of the following: |
| // 1) If the ResourceLoader has a deferred stage of DEFERRED_NONE, sets the |
| // ResourceLoader's stage to the stage specified on construction and resumes it. |
| // 2) If the ResourceLoader still has a deferred stage of DEFERRED_SYNC, sets |
| // the ResourceLoader's stage to the stage specified on construction. The |
| // ResourceLoader will be resumed at some point in the future. |
| class ResourceLoader::ScopedDeferral { |
| public: |
| ScopedDeferral(ResourceLoader* resource_loader, |
| ResourceLoader::DeferredStage deferred_stage) |
| : resource_loader_(resource_loader), deferred_stage_(deferred_stage) { |
| resource_loader_->deferred_stage_ = DEFERRED_SYNC; |
| } |
| |
| ~ScopedDeferral() { |
| DeferredStage old_deferred_stage = resource_loader_->deferred_stage_; |
| // On destruction, either the stage is still DEFERRED_SYNC, or Resume() was |
| // called once, and it advanced to DEFERRED_NONE. |
| DCHECK(old_deferred_stage == DEFERRED_NONE || |
| old_deferred_stage == DEFERRED_SYNC) |
| << old_deferred_stage; |
| resource_loader_->deferred_stage_ = deferred_stage_; |
| // If Resume() was called, it just advanced the state without doing |
| // anything. Go ahead and resume the request now. |
| if (old_deferred_stage == DEFERRED_NONE) |
| resource_loader_->Resume(false /* called_from_resource_controller */); |
| } |
| |
| private: |
| ResourceLoader* const resource_loader_; |
| const DeferredStage deferred_stage_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedDeferral); |
| }; |
| |
| ResourceLoader::ResourceLoader(std::unique_ptr<net::URLRequest> request, |
| std::unique_ptr<ResourceHandler> handler, |
| ResourceLoaderDelegate* delegate, |
| ResourceContext* resource_context) |
| : deferred_stage_(DEFERRED_NONE), |
| request_(std::move(request)), |
| handler_(std::move(handler)), |
| delegate_(delegate), |
| times_cancelled_before_request_start_(0), |
| started_request_(false), |
| times_cancelled_after_request_start_(0), |
| resource_context_(resource_context), |
| weak_ptr_factory_(this) { |
| request_->set_delegate(this); |
| handler_->SetDelegate(this); |
| } |
| |
| ResourceLoader::~ResourceLoader() { |
| if (update_body_read_before_paused_) |
| body_read_before_paused_ = request_->GetRawBodyBytes(); |
| if (body_read_before_paused_ != -1) { |
| // Only record histograms for web schemes. |
| bool should_record_scheme = request_->url().SchemeIs(url::kHttpScheme) || |
| request_->url().SchemeIs(url::kHttpsScheme) || |
| request_->url().SchemeIs(url::kFtpScheme); |
| if (!request_->was_cached() && should_record_scheme) { |
| UMA_HISTOGRAM_COUNTS_1M("Network.URLLoader.BodyReadFromNetBeforePaused", |
| body_read_before_paused_); |
| } else { |
| DVLOG(1) << "The request has been paused, but " |
| << "Network.URLLoader.BodyReadFromNetBeforePaused is not " |
| << "reported because the response body may not be from the " |
| << "network, or may be from cache. body_read_before_paused_: " |
| << body_read_before_paused_; |
| } |
| } |
| |
| 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() { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::StartRequest", this, |
| TRACE_EVENT_FLAG_FLOW_OUT); |
| |
| ScopedDeferral scoped_deferral(this, DEFERRED_START); |
| handler_->OnWillStart(request_->url(), std::make_unique<Controller>(this)); |
| } |
| |
| void ResourceLoader::CancelRequest(bool from_renderer) { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CancelRequest", this, |
| TRACE_EVENT_FLAG_FLOW_IN); |
| CancelRequestInternal(net::ERR_ABORTED, from_renderer); |
| } |
| |
| void ResourceLoader::CancelWithError(int error_code) { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CancelWithError", this, |
| TRACE_EVENT_FLAG_FLOW_IN); |
| CancelRequestInternal(error_code, false); |
| } |
| |
| ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() { |
| return ResourceRequestInfoImpl::ForRequest(request_.get()); |
| } |
| |
| void ResourceLoader::ClearLoginDelegate() { |
| login_delegate_ = nullptr; |
| } |
| |
| void ResourceLoader::OutOfBandCancel(int error_code, bool tell_renderer) { |
| CancelRequestInternal(error_code, !tell_renderer); |
| } |
| |
| void ResourceLoader::PauseReadingBodyFromNet() { |
| DVLOG(1) << "ResourceLoader pauses fetching response body for " |
| << request_->original_url().spec(); |
| |
| // Please note that reading the body is paused in all cases. Even if the URL |
| // request indicates that the response was cached, there could still be |
| // network activity involved. For example, the response was only partially |
| // cached. This also pauses things that don't come from the network (chrome |
| // URLs, file URLs, data URLs, etc.). |
| // |
| // On the other hand, BodyReadFromNetBeforePaused histogram is only reported |
| // when it is certain that the response body is read from the network and |
| // wasn't cached. This avoids polluting the histogram data. |
| should_pause_reading_body_ = true; |
| |
| if (pending_read_) { |
| update_body_read_before_paused_ = true; |
| } else { |
| body_read_before_paused_ = request_->GetRawBodyBytes(); |
| } |
| } |
| |
| void ResourceLoader::ResumeReadingBodyFromNet() { |
| DVLOG(1) << "ResourceLoader resumes fetching response body for " |
| << request_->original_url().spec(); |
| |
| should_pause_reading_body_ = false; |
| |
| if (read_more_body_supressed_) { |
| DCHECK(!is_deferred()); |
| read_more_body_supressed_ = false; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ResourceLoader::ReadMore, |
| weak_ptr_factory_.GetWeakPtr(), |
| false /* handle_result_asynchronously */)); |
| } |
| } |
| |
| void ResourceLoader::OnReceivedRedirect(net::URLRequest* unused, |
| const net::RedirectInfo& redirect_info, |
| bool* defer) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"), |
| "ResourceLoader::OnReceivedRedirect"); |
| DCHECK_EQ(request_.get(), unused); |
| |
| DVLOG(1) << "OnReceivedRedirect: " << request_->url().spec(); |
| DCHECK(request_->status().is_success()); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| // With PlzNavigate for frame navigations this check is done in the |
| // NavigationRequest::OnReceivedRedirect() function. |
| bool check_handled_elsewhere = IsBrowserSideNavigationEnabled() && |
| IsResourceTypeFrame(info->GetResourceType()); |
| |
| if (!check_handled_elsewhere) { |
| if (!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; |
| } |
| } |
| |
| scoped_refptr<network::ResourceResponse> response = |
| new network::ResourceResponse(); |
| PopulateResourceResponse(info, request_.get(), response.get(), |
| raw_request_headers_, raw_response_headers_.get()); |
| raw_request_headers_ = net::HttpRawRequestHeaders(); |
| raw_response_headers_ = nullptr; |
| |
| delegate_->DidReceiveRedirect(this, redirect_info.new_url, response.get()); |
| |
| // Can't used ScopedDeferral here, because on sync completion, need to set |
| // |defer| to false instead of calling back into the URLRequest. |
| deferred_stage_ = DEFERRED_SYNC; |
| handler_->OnRequestRedirected(redirect_info, response.get(), |
| std::make_unique<Controller>(this)); |
| if (is_deferred()) { |
| *defer = true; |
| deferred_redirect_url_ = redirect_info.new_url; |
| deferred_stage_ = DEFERRED_REDIRECT; |
| } else { |
| *defer = false; |
| if (delegate_->HandleExternalProtocol(this, redirect_info.new_url)) |
| Cancel(); |
| } |
| } |
| |
| 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"; |
| ResourceRequestInfo::WebContentsGetter web_contents_getter = |
| ResourceRequestInfo::ForRequest(request_.get()) |
| ->GetWebContentsGetterForRequest(); |
| |
| std::unique_ptr<net::ClientCertStore> client_cert_store = |
| GetContentClient()->browser()->CreateClientCertStore(resource_context_); |
| ssl_client_auth_handler_.reset(new SSLClientAuthHandler( |
| std::move(client_cert_store), std::move(web_contents_getter), 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::OnResponseStarted(net::URLRequest* unused, int net_error) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"), |
| "ResourceLoader::OnResponseStarted"); |
| DCHECK_EQ(request_.get(), unused); |
| |
| DVLOG(1) << "OnResponseStarted: " << request_->url().spec(); |
| |
| if (net_error != net::OK) { |
| ResponseCompleted(); |
| return; |
| } |
| |
| CompleteResponseStarted(); |
| } |
| |
| void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"), |
| "ResourceLoader::OnReadCompleted"); |
| DCHECK_EQ(request_.get(), unused); |
| DVLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\"" |
| << " bytes_read = " << bytes_read; |
| |
| pending_read_ = false; |
| |
| // bytes_read == -1 always implies an error. |
| if (bytes_read == -1 || !request_->status().is_success()) { |
| ResponseCompleted(); |
| return; |
| } |
| |
| CompleteRead(bytes_read); |
| } |
| |
| void ResourceLoader::CancelSSLRequest(int error, |
| const net::SSLInfo* ssl_info) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // 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(thread_checker_.CalledOnValidThread()); |
| |
| DVLOG(1) << "ContinueSSLRequest() url: " << request_->url().spec(); |
| |
| request_->ContinueDespiteLastError(); |
| } |
| |
| void ResourceLoader::ContinueWithCertificate( |
| scoped_refptr<net::X509Certificate> cert, |
| scoped_refptr<net::SSLPrivateKey> private_key) { |
| DCHECK(ssl_client_auth_handler_); |
| ssl_client_auth_handler_.reset(); |
| request_->ContinueWithCertificate(std::move(cert), std::move(private_key)); |
| } |
| |
| 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(bool called_from_resource_controller) { |
| DeferredStage stage = deferred_stage_; |
| deferred_stage_ = DEFERRED_NONE; |
| switch (stage) { |
| case DEFERRED_NONE: |
| NOTREACHED(); |
| break; |
| case DEFERRED_SYNC: |
| DCHECK(called_from_resource_controller); |
| // Request will be resumed when the stack unwinds. |
| break; |
| case DEFERRED_START: |
| // URLRequest::Start completes asynchronously, so starting the request now |
| // won't result in synchronously calling into a ResourceHandler, if this |
| // was called from Resume(). |
| StartRequestInternal(); |
| break; |
| case DEFERRED_REDIRECT: |
| // URLRequest::Start completes asynchronously, so starting the request now |
| // won't result in synchronously calling into a ResourceHandler, if this |
| // was called from Resume(). |
| FollowDeferredRedirectInternal(); |
| break; |
| case DEFERRED_ON_WILL_READ: |
| // Always post a task, as synchronous resumes don't go through this |
| // method. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ResourceLoader::ReadMore, |
| weak_ptr_factory_.GetWeakPtr(), |
| false /* handle_result_asynchronously */)); |
| break; |
| case DEFERRED_READ: |
| if (called_from_resource_controller) { |
| // TODO(mmenke): Call PrepareToReadMore instead? Strange that this is |
| // the only case which calls different methods, depending on the path. |
| // ResumeReading does check for cancellation. Should other paths do that |
| // as well? |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ResourceLoader::ResumeReading, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| // If this was called as a result of a handler succeeding synchronously, |
| // force the result of the next read to be handled asynchronously, to |
| // avoid blocking the IO thread. |
| PrepareToReadMore(true /* handle_result_asynchronously */); |
| } |
| break; |
| case DEFERRED_RESPONSE_COMPLETE: |
| if (called_from_resource_controller) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ResourceLoader::ResponseCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| ResponseCompleted(); |
| } |
| break; |
| case DEFERRED_FINISH: |
| if (called_from_resource_controller) { |
| // Delay self-destruction since we don't know how we were reached. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ResourceLoader::CallDidFinishLoading, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| CallDidFinishLoading(); |
| } |
| break; |
| } |
| } |
| |
| void ResourceLoader::Cancel() { |
| CancelRequest(false); |
| } |
| |
| void ResourceLoader::StartRequestInternal() { |
| DCHECK(!request_->is_pending()); |
| |
| // Note: at this point any possible deferred start actions are already over. |
| |
| if (!request_->status().is_success()) { |
| return; |
| } |
| |
| if (delegate_->HandleExternalProtocol(this, request_->url())) { |
| Cancel(); |
| return; |
| } |
| |
| started_request_ = true; |
| |
| if (GetRequestInfo()->ShouldReportRawHeaders()) { |
| request_->SetRequestHeadersCallback( |
| base::Bind(&net::HttpRawRequestHeaders::Assign, |
| base::Unretained(&raw_request_headers_))); |
| request_->SetResponseHeadersCallback(base::Bind( |
| &ResourceLoader::SetRawResponseHeaders, base::Unretained(this))); |
| } |
| UMA_HISTOGRAM_TIMES("Net.ResourceLoader.TimeToURLRequestStart", |
| base::TimeTicks::Now() - request_->creation_time()); |
| 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_ = nullptr; |
| } |
| 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::BindOnce(&ResourceLoader::ResponseCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void ResourceLoader::FollowDeferredRedirectInternal() { |
| DCHECK(!deferred_redirect_url_.is_empty()); |
| GURL redirect_url = deferred_redirect_url_; |
| deferred_redirect_url_ = GURL(); |
| if (delegate_->HandleExternalProtocol(this, redirect_url)) { |
| Cancel(); |
| } else { |
| request_->FollowDeferredRedirect( |
| base::nullopt /* modified_request_headers */); |
| } |
| } |
| |
| void ResourceLoader::CompleteResponseStarted() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| scoped_refptr<network::ResourceResponse> response = |
| new network::ResourceResponse(); |
| PopulateResourceResponse(info, request_.get(), response.get(), |
| raw_request_headers_, raw_response_headers_.get()); |
| raw_request_headers_ = net::HttpRawRequestHeaders(); |
| raw_response_headers_ = nullptr; |
| |
| delegate_->DidReceiveResponse(this, response.get()); |
| |
| // For back-forward navigations, record metrics. |
| // TODO(clamy): Remove once we understand the root cause behind the regression |
| // of PLT for b/f navigations in PlzNavigate. |
| if ((info->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK) && |
| IsResourceTypeFrame(info->GetResourceType()) && |
| request_->url().SchemeIsHTTPOrHTTPS()) { |
| UMA_HISTOGRAM_BOOLEAN("Navigation.BackForward.WasCached", |
| request_->was_cached()); |
| } |
| |
| read_deferral_start_time_ = base::TimeTicks::Now(); |
| // Using a ScopedDeferral here would result in calling ReadMore(true) on sync |
| // success. Calling PrepareToReadMore(false) here instead allows small |
| // responses to be handled completely synchronously, if no ResourceHandler |
| // defers handling of the response. |
| deferred_stage_ = DEFERRED_SYNC; |
| handler_->OnResponseStarted(response.get(), |
| std::make_unique<Controller>(this)); |
| if (is_deferred()) { |
| deferred_stage_ = DEFERRED_READ; |
| } else { |
| PrepareToReadMore(false); |
| } |
| } |
| |
| void ResourceLoader::PrepareToReadMore(bool handle_result_async) { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::PrepareToReadMore", this, |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| DCHECK(!is_deferred()); |
| |
| deferred_stage_ = DEFERRED_SYNC; |
| |
| handler_->OnWillRead(&read_buffer_, &read_buffer_size_, |
| std::make_unique<Controller>(this)); |
| |
| if (is_deferred()) { |
| deferred_stage_ = DEFERRED_ON_WILL_READ; |
| } else { |
| ReadMore(handle_result_async); |
| } |
| } |
| |
| void ResourceLoader::ReadMore(bool handle_result_async) { |
| DCHECK(read_buffer_.get()); |
| DCHECK_GT(read_buffer_size_, 0); |
| |
| if (should_pause_reading_body_) { |
| read_more_body_supressed_ = true; |
| return; |
| } |
| |
| pending_read_ = true; |
| |
| int result = request_->Read(read_buffer_.get(), read_buffer_size_); |
| // Have to do this after the Read call, to ensure it still has an outstanding |
| // reference. |
| read_buffer_ = nullptr; |
| read_buffer_size_ = 0; |
| |
| if (result == net::ERR_IO_PENDING) |
| return; |
| |
| if (!handle_result_async || result <= 0) { |
| OnReadCompleted(request_.get(), result); |
| } 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::BindOnce(&ResourceLoader::OnReadCompleted, |
| weak_ptr_factory_.GetWeakPtr(), request_.get(), result)); |
| } |
| } |
| |
| 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()) { |
| PrepareToReadMore(false /* handle_result_asynchronously */); |
| } else { |
| ResponseCompleted(); |
| } |
| } |
| |
| void ResourceLoader::CompleteRead(int bytes_read) { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CompleteRead", this, |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| |
| DCHECK(bytes_read >= 0); |
| DCHECK(request_->status().is_success()); |
| |
| if (update_body_read_before_paused_) { |
| update_body_read_before_paused_ = false; |
| body_read_before_paused_ = request_->GetRawBodyBytes(); |
| } |
| |
| ScopedDeferral scoped_deferral( |
| this, bytes_read > 0 ? DEFERRED_READ : DEFERRED_RESPONSE_COMPLETE); |
| handler_->OnReadCompleted(bytes_read, std::make_unique<Controller>(this)); |
| } |
| |
| void ResourceLoader::ResponseCompleted() { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::ResponseCompleted", this, |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| |
| DVLOG(1) << "ResponseCompleted: " << request_->url().spec(); |
| RecordHistograms(); |
| |
| ScopedDeferral scoped_deferral(this, DEFERRED_FINISH); |
| handler_->OnResponseCompleted(request_->status(), |
| std::make_unique<Controller>(this)); |
| } |
| |
| void ResourceLoader::CallDidFinishLoading() { |
| TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CallDidFinishLoading", |
| this, TRACE_EVENT_FLAG_FLOW_IN); |
| delegate_->DidFinishLoading(this); |
| } |
| |
| void ResourceLoader::RecordHistograms() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| if (request_->response_info().network_accessed) { |
| if (info->GetResourceType() == RESOURCE_TYPE_MAIN_FRAME) { |
| UMA_HISTOGRAM_ENUMERATION("Net.HttpResponseInfo.ConnectionInfo.MainFrame", |
| request_->response_info().connection_info, |
| net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Net.HttpResponseInfo.ConnectionInfo.SubResource", |
| request_->response_info().connection_info, |
| net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS); |
| } |
| } |
| |
| if (request_->load_flags() & net::LOAD_PREFETCH) { |
| // Note that RESOURCE_TYPE_PREFETCH requests are a subset of |
| // net::LOAD_PREFETCH requests. In the histograms below, "Prefetch" means |
| // RESOURCE_TYPE_PREFETCH and "LoadPrefetch" means net::LOAD_PREFETCH. |
| bool is_resource_type_prefetch = |
| info->GetResourceType() == RESOURCE_TYPE_PREFETCH; |
| PrefetchStatus prefetch_status = STATUS_UNDEFINED; |
| TimeDelta total_time = base::TimeTicks::Now() - request_->creation_time(); |
| |
| switch (request_->status().status()) { |
| case net::URLRequestStatus::SUCCESS: |
| if (request_->was_cached()) { |
| prefetch_status = request_->response_info().unused_since_prefetch |
| ? STATUS_SUCCESS_ALREADY_PREFETCHED |
| : STATUS_SUCCESS_FROM_CACHE; |
| if (is_resource_type_prefetch) { |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromCache", |
| total_time); |
| } |
| } else { |
| prefetch_status = STATUS_SUCCESS_FROM_NETWORK; |
| if (is_resource_type_prefetch) { |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromNetwork", |
| total_time); |
| } |
| } |
| break; |
| case net::URLRequestStatus::CANCELED: |
| prefetch_status = STATUS_CANCELED; |
| if (is_resource_type_prefetch) |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeBeforeCancel", total_time); |
| break; |
| case net::URLRequestStatus::IO_PENDING: |
| case net::URLRequestStatus::FAILED: |
| prefetch_status = STATUS_UNDEFINED; |
| break; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Net.LoadPrefetch.Pattern", prefetch_status, |
| STATUS_MAX); |
| if (is_resource_type_prefetch) { |
| UMA_HISTOGRAM_ENUMERATION("Net.Prefetch.Pattern", prefetch_status, |
| STATUS_MAX); |
| } |
| } else if (request_->response_info().unused_since_prefetch) { |
| TimeDelta total_time = base::TimeTicks::Now() - request_->creation_time(); |
| UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentOnPrefetchHit", total_time); |
| } |
| } |
| |
| void ResourceLoader::SetRawResponseHeaders( |
| scoped_refptr<const net::HttpResponseHeaders> headers) { |
| raw_response_headers_ = headers; |
| } |
| |
| } // namespace content |