| // Copyright (c) 2011 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. |
| |
| // See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading |
| |
| #include "content/browser/renderer_host/resource_dispatcher_host.h" |
| |
| #include <set> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/command_line.h" |
| #include "base/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/scoped_ptr.h" |
| #include "base/shared_memory.h" |
| #include "base/stl_util-inl.h" |
| #include "base/time.h" |
| #include "chrome/browser/download/download_file_manager.h" |
| #include "chrome/browser/download/download_manager.h" |
| #include "chrome/browser/download/download_request_limiter.h" |
| #include "chrome/browser/download/download_util.h" |
| #include "chrome/browser/download/save_file_manager.h" |
| #include "chrome/browser/extensions/user_script_listener.h" |
| #include "chrome/browser/external_protocol_handler.h" |
| #include "chrome/browser/net/chrome_url_request_context.h" |
| #include "chrome/browser/net/url_request_tracking.h" |
| #include "chrome/browser/prerender/prerender_manager.h" |
| #include "chrome/browser/prerender/prerender_resource_handler.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/renderer_host/download_resource_handler.h" |
| #include "chrome/browser/renderer_host/safe_browsing_resource_handler.h" |
| #include "chrome/browser/renderer_host/save_file_resource_handler.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/browser/ssl/ssl_client_auth_handler.h" |
| #include "chrome/browser/ssl/ssl_manager.h" |
| #include "chrome/browser/ui/login/login_prompt.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/render_messages.h" |
| #include "chrome/common/render_messages_params.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/browser/cert_store.h" |
| #include "content/browser/child_process_security_policy.h" |
| #include "content/browser/chrome_blob_storage_context.h" |
| #include "content/browser/cross_site_request_manager.h" |
| #include "content/browser/in_process_webkit/webkit_thread.h" |
| #include "content/browser/plugin_service.h" |
| #include "content/browser/renderer_host/async_resource_handler.h" |
| #include "content/browser/renderer_host/buffered_resource_handler.h" |
| #include "content/browser/renderer_host/cross_site_resource_handler.h" |
| #include "content/browser/renderer_host/global_request_id.h" |
| #include "content/browser/renderer_host/redirect_to_file_resource_handler.h" |
| #include "content/browser/renderer_host/render_view_host.h" |
| #include "content/browser/renderer_host/render_view_host_delegate.h" |
| #include "content/browser/renderer_host/render_view_host_notification_task.h" |
| #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" |
| #include "content/browser/renderer_host/resource_message_filter.h" |
| #include "content/browser/renderer_host/resource_queue.h" |
| #include "content/browser/renderer_host/resource_request_details.h" |
| #include "content/browser/renderer_host/sync_resource_handler.h" |
| #include "content/browser/worker_host/worker_service.h" |
| #include "net/base/auth.h" |
| #include "net/base/cert_status_flags.h" |
| #include "net/base/cookie_monster.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/mime_util.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/request_priority.h" |
| #include "net/base/ssl_cert_request_info.h" |
| #include "net/base/upload_data.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "webkit/appcache/appcache_interceptor.h" |
| #include "webkit/appcache/appcache_interfaces.h" |
| #include "webkit/blob/blob_storage_controller.h" |
| #include "webkit/blob/deletable_file_reference.h" |
| |
| // TODO(oshima): Enable this for other platforms. |
| #if defined(OS_CHROMEOS) |
| #include "chrome/browser/renderer_host/offline_resource_handler.h" |
| #endif |
| |
| using base::Time; |
| using base::TimeDelta; |
| using base::TimeTicks; |
| using webkit_blob::DeletableFileReference; |
| |
| // ---------------------------------------------------------------------------- |
| |
| // A ShutdownTask proxies a shutdown task from the UI thread to the IO thread. |
| // It should be constructed on the UI thread and run in the IO thread. |
| class ResourceDispatcherHost::ShutdownTask : public Task { |
| public: |
| explicit ShutdownTask(ResourceDispatcherHost* resource_dispatcher_host) |
| : rdh_(resource_dispatcher_host) { |
| } |
| |
| void Run() { |
| rdh_->OnShutdown(); |
| } |
| |
| private: |
| ResourceDispatcherHost* rdh_; |
| }; |
| |
| namespace { |
| |
| // The interval for calls to ResourceDispatcherHost::UpdateLoadStates |
| const int kUpdateLoadStatesIntervalMsec = 100; |
| |
| // Maximum number of pending data messages sent to the renderer at any |
| // given time for a given request. |
| const int kMaxPendingDataMessages = 20; |
| |
| // Maximum byte "cost" of all the outstanding requests for a renderer. |
| // See delcaration of |max_outstanding_requests_cost_per_process_| for details. |
| // This bound is 25MB, which allows for around 6000 outstanding requests. |
| const int kMaxOutstandingRequestsCostPerProcess = 26214400; |
| |
| // Consults the RendererSecurity policy to determine whether the |
| // ResourceDispatcherHost should service this request. A request might be |
| // disallowed if the renderer is not authorized to retrieve the request URL or |
| // if the renderer is attempting to upload an unauthorized file. |
| bool ShouldServiceRequest(ChildProcessInfo::ProcessType process_type, |
| int child_id, |
| const ViewHostMsg_Resource_Request& request_data) { |
| if (process_type == ChildProcessInfo::PLUGIN_PROCESS) |
| return true; |
| |
| if (request_data.resource_type == ResourceType::PREFETCH) { |
| prerender::PrerenderManager::RecordPrefetchTagObserved(); |
| if (!ResourceDispatcherHost::is_prefetch_enabled()) |
| return false; |
| } |
| |
| ChildProcessSecurityPolicy* policy = |
| ChildProcessSecurityPolicy::GetInstance(); |
| |
| // Check if the renderer is permitted to request the requested URL. |
| if (!policy->CanRequestURL(child_id, request_data.url)) { |
| VLOG(1) << "Denied unauthorized request for " |
| << request_data.url.possibly_invalid_spec(); |
| return false; |
| } |
| |
| // Check if the renderer is permitted to upload the requested files. |
| if (request_data.upload_data) { |
| const std::vector<net::UploadData::Element>* uploads = |
| request_data.upload_data->elements(); |
| std::vector<net::UploadData::Element>::const_iterator iter; |
| for (iter = uploads->begin(); iter != uploads->end(); ++iter) { |
| if (iter->type() == net::UploadData::TYPE_FILE && |
| !policy->CanReadFile(child_id, iter->file_path())) { |
| NOTREACHED() << "Denied unauthorized upload of " |
| << iter->file_path().value(); |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| void PopulateResourceResponse(net::URLRequest* request, |
| bool replace_extension_localization_templates, |
| ResourceResponse* response) { |
| response->response_head.status = request->status(); |
| response->response_head.request_time = request->request_time(); |
| response->response_head.response_time = request->response_time(); |
| response->response_head.headers = request->response_headers(); |
| request->GetCharset(&response->response_head.charset); |
| response->response_head.replace_extension_localization_templates = |
| replace_extension_localization_templates; |
| response->response_head.content_length = request->GetExpectedContentSize(); |
| request->GetMimeType(&response->response_head.mime_type); |
| response->response_head.was_fetched_via_spdy = |
| request->was_fetched_via_spdy(); |
| response->response_head.was_npn_negotiated = request->was_npn_negotiated(); |
| response->response_head.was_alternate_protocol_available = |
| request->was_alternate_protocol_available(); |
| response->response_head.was_fetched_via_proxy = |
| request->was_fetched_via_proxy(); |
| response->response_head.socket_address = request->GetSocketAddress(); |
| appcache::AppCacheInterceptor::GetExtraResponseInfo( |
| request, |
| &response->response_head.appcache_id, |
| &response->response_head.appcache_manifest_url); |
| } |
| |
| // Returns a list of all the possible error codes from the network module. |
| // Note that the error codes are all positive (since histograms expect positive |
| // sample values). |
| std::vector<int> GetAllNetErrorCodes() { |
| std::vector<int> all_error_codes; |
| #define NET_ERROR(label, value) all_error_codes.push_back(-(value)); |
| #include "net/base/net_error_list.h" |
| #undef NET_ERROR |
| return all_error_codes; |
| } |
| |
| #if defined(OS_WIN) |
| #pragma warning(disable: 4748) |
| #pragma optimize("", off) |
| #endif |
| |
| #if defined(OS_WIN) |
| #pragma optimize("", on) |
| #pragma warning(default: 4748) |
| #endif |
| |
| } // namespace |
| |
| ResourceDispatcherHost::ResourceDispatcherHost() |
| : ALLOW_THIS_IN_INITIALIZER_LIST( |
| download_file_manager_(new DownloadFileManager(this))), |
| download_request_limiter_(new DownloadRequestLimiter()), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| save_file_manager_(new SaveFileManager(this))), |
| user_script_listener_(new UserScriptListener(&resource_queue_)), |
| safe_browsing_(SafeBrowsingService::CreateSafeBrowsingService()), |
| webkit_thread_(new WebKitThread), |
| request_id_(-1), |
| ALLOW_THIS_IN_INITIALIZER_LIST(method_runner_(this)), |
| is_shutdown_(false), |
| max_outstanding_requests_cost_per_process_( |
| kMaxOutstandingRequestsCostPerProcess), |
| filter_(NULL) { |
| ResourceQueue::DelegateSet resource_queue_delegates; |
| resource_queue_delegates.insert(user_script_listener_.get()); |
| resource_queue_.Initialize(resource_queue_delegates); |
| } |
| |
| ResourceDispatcherHost::~ResourceDispatcherHost() { |
| AsyncResourceHandler::GlobalCleanup(); |
| STLDeleteValues(&pending_requests_); |
| |
| user_script_listener_->ShutdownMainThread(); |
| } |
| |
| void ResourceDispatcherHost::Initialize() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| webkit_thread_->Initialize(); |
| safe_browsing_->Initialize(); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| NewRunnableFunction(&appcache::AppCacheInterceptor::EnsureRegistered)); |
| } |
| |
| void ResourceDispatcherHost::Shutdown() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, new ShutdownTask(this)); |
| } |
| |
| void ResourceDispatcherHost::SetRequestInfo( |
| net::URLRequest* request, |
| ResourceDispatcherHostRequestInfo* info) { |
| request->SetUserData(NULL, info); |
| } |
| |
| void ResourceDispatcherHost::OnShutdown() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| is_shutdown_ = true; |
| resource_queue_.Shutdown(); |
| STLDeleteValues(&pending_requests_); |
| // Make sure we shutdown the timer now, otherwise by the time our destructor |
| // runs if the timer is still running the Task is deleted twice (once by |
| // the MessageLoop and the second time by RepeatingTimer). |
| update_load_states_timer_.Stop(); |
| |
| // Clear blocked requests if any left. |
| // Note that we have to do this in 2 passes as we cannot call |
| // CancelBlockedRequestsForRoute while iterating over |
| // blocked_requests_map_, as it modifies it. |
| std::set<ProcessRouteIDs> ids; |
| for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); |
| iter != blocked_requests_map_.end(); ++iter) { |
| std::pair<std::set<ProcessRouteIDs>::iterator, bool> result = |
| ids.insert(iter->first); |
| // We should not have duplicates. |
| DCHECK(result.second); |
| } |
| for (std::set<ProcessRouteIDs>::const_iterator iter = ids.begin(); |
| iter != ids.end(); ++iter) { |
| CancelBlockedRequestsForRoute(iter->first, iter->second); |
| } |
| } |
| |
| bool ResourceDispatcherHost::HandleExternalProtocol(int request_id, |
| int child_id, |
| int route_id, |
| const GURL& url, |
| ResourceType::Type type, |
| ResourceHandler* handler) { |
| if (!ResourceType::IsFrame(type) || net::URLRequest::IsHandledURL(url)) |
| return false; |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| NewRunnableFunction( |
| &ExternalProtocolHandler::LaunchUrl, url, child_id, route_id)); |
| |
| handler->OnResponseCompleted( |
| request_id, |
| net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_ABORTED), |
| std::string()); // No security info necessary. |
| return true; |
| } |
| |
| bool ResourceDispatcherHost::OnMessageReceived(const IPC::Message& message, |
| ResourceMessageFilter* filter, |
| bool* message_was_ok) { |
| filter_ = filter; |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP_EX(ResourceDispatcherHost, message, *message_was_ok) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_RequestResource, OnRequestResource) |
| IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_SyncLoad, OnSyncLoad) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_ReleaseDownloadedFile, |
| OnReleaseDownloadedFile) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_DataReceived_ACK, OnDataReceivedACK) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_DataDownloaded_ACK, OnDataDownloadedACK) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_UploadProgress_ACK, OnUploadProgressACK) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_CancelRequest, OnCancelRequest) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_FollowRedirect, OnFollowRedirect) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_ClosePage_ACK, OnClosePageACK) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP_EX() |
| |
| filter_ = NULL; |
| return handled; |
| } |
| |
| void ResourceDispatcherHost::OnRequestResource( |
| const IPC::Message& message, |
| int request_id, |
| const ViewHostMsg_Resource_Request& request_data) { |
| BeginRequest(request_id, request_data, NULL, message.routing_id()); |
| } |
| |
| // Begins a resource request with the given params on behalf of the specified |
| // child process. Responses will be dispatched through the given receiver. The |
| // process ID is used to lookup TabContents from routing_id's in the case of a |
| // request from a renderer. request_context is the cookie/cache context to be |
| // used for this request. |
| // |
| // If sync_result is non-null, then a SyncLoad reply will be generated, else |
| // a normal asynchronous set of response messages will be generated. |
| void ResourceDispatcherHost::OnSyncLoad( |
| int request_id, |
| const ViewHostMsg_Resource_Request& request_data, |
| IPC::Message* sync_result) { |
| BeginRequest(request_id, request_data, sync_result, |
| sync_result->routing_id()); |
| } |
| |
| void ResourceDispatcherHost::BeginRequest( |
| int request_id, |
| const ViewHostMsg_Resource_Request& request_data, |
| IPC::Message* sync_result, // only valid for sync |
| int route_id) { |
| ChildProcessInfo::ProcessType process_type = filter_->process_type(); |
| int child_id = filter_->child_id(); |
| |
| ChromeURLRequestContext* context = filter_->GetURLRequestContext( |
| request_data); |
| |
| // Might need to resolve the blob references in the upload data. |
| if (request_data.upload_data && context) { |
| context->blob_storage_context()->controller()-> |
| ResolveBlobReferencesInUploadData(request_data.upload_data.get()); |
| } |
| |
| if (is_shutdown_ || |
| !ShouldServiceRequest(process_type, child_id, request_data)) { |
| net::URLRequestStatus status(net::URLRequestStatus::FAILED, |
| net::ERR_ABORTED); |
| if (sync_result) { |
| SyncLoadResult result; |
| result.status = status; |
| ViewHostMsg_SyncLoad::WriteReplyParams(sync_result, result); |
| filter_->Send(sync_result); |
| } else { |
| // Tell the renderer that this request was disallowed. |
| filter_->Send(new ViewMsg_Resource_RequestComplete( |
| route_id, |
| request_id, |
| status, |
| std::string(), // No security info needed, connection was not |
| base::Time())); // established. |
| } |
| return; |
| } |
| |
| // Ensure the Chrome plugins are loaded, as they may intercept network |
| // requests. Does nothing if they are already loaded. |
| // TODO(mpcomplete): This takes 200 ms! Investigate parallelizing this by |
| // starting the load earlier in a BG thread. |
| PluginService::GetInstance()->LoadChromePlugins(this); |
| |
| // Construct the event handler. |
| scoped_refptr<ResourceHandler> handler; |
| if (sync_result) { |
| handler = new SyncResourceHandler( |
| filter_, request_data.url, sync_result, this); |
| } else { |
| handler = new AsyncResourceHandler( |
| filter_, route_id, request_data.url, this); |
| } |
| |
| // The RedirectToFileResourceHandler depends on being next in the chain. |
| if (request_data.download_to_file) |
| handler = new RedirectToFileResourceHandler(handler, child_id, this); |
| |
| if (HandleExternalProtocol(request_id, child_id, route_id, |
| request_data.url, request_data.resource_type, |
| handler)) { |
| return; |
| } |
| |
| // Construct the request. |
| net::URLRequest* request = new net::URLRequest(request_data.url, this); |
| request->set_method(request_data.method); |
| request->set_first_party_for_cookies(request_data.first_party_for_cookies); |
| request->set_referrer(CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kNoReferrers) ? std::string() : request_data.referrer.spec()); |
| net::HttpRequestHeaders headers; |
| headers.AddHeadersFromString(request_data.headers); |
| request->SetExtraRequestHeaders(headers); |
| |
| int load_flags = request_data.load_flags; |
| // Although EV status is irrelevant to sub-frames and sub-resources, we have |
| // to perform EV certificate verification on all resources because an HTTP |
| // keep-alive connection created to load a sub-frame or a sub-resource could |
| // be reused to load a main frame. |
| load_flags |= net::LOAD_VERIFY_EV_CERT; |
| if (request_data.resource_type == ResourceType::MAIN_FRAME) { |
| load_flags |= net::LOAD_MAIN_FRAME; |
| } else if (request_data.resource_type == ResourceType::SUB_FRAME) { |
| load_flags |= net::LOAD_SUB_FRAME; |
| } else if (request_data.resource_type == ResourceType::PREFETCH) { |
| load_flags |= net::LOAD_PREFETCH; |
| } |
| // Raw headers are sensitive, as they inclide Cookie/Set-Cookie, so only |
| // allow requesting them if requestor has ReadRawCookies permission. |
| if ((load_flags & net::LOAD_REPORT_RAW_HEADERS) |
| && !ChildProcessSecurityPolicy::GetInstance()-> |
| CanReadRawCookies(child_id)) { |
| VLOG(1) << "Denied unathorized request for raw headers"; |
| load_flags &= ~net::LOAD_REPORT_RAW_HEADERS; |
| } |
| |
| request->set_load_flags(load_flags); |
| request->set_context(context); |
| request->set_priority(DetermineRequestPriority(request_data.resource_type)); |
| |
| // Set upload data. |
| uint64 upload_size = 0; |
| if (request_data.upload_data) { |
| request->set_upload(request_data.upload_data); |
| upload_size = request_data.upload_data->GetContentLength(); |
| } |
| |
| // Install a PrerenderResourceHandler if the requested URL could |
| // be prerendered. This should be in front of the [a]syncResourceHandler, |
| // but after the BufferedResourceHandler since it depends on the MIME |
| // sniffing capabilities in the BufferedResourceHandler. |
| prerender::PrerenderResourceHandler* pre_handler = |
| prerender::PrerenderResourceHandler::MaybeCreate(*request, |
| context, |
| handler); |
| if (pre_handler) |
| handler = pre_handler; |
| |
| // Install a CrossSiteResourceHandler if this request is coming from a |
| // RenderViewHost with a pending cross-site request. We only check this for |
| // MAIN_FRAME requests. Unblock requests only come from a blocked page, do |
| // not count as cross-site, otherwise it gets blocked indefinitely. |
| if (request_data.resource_type == ResourceType::MAIN_FRAME && |
| process_type == ChildProcessInfo::RENDER_PROCESS && |
| CrossSiteRequestManager::GetInstance()-> |
| HasPendingCrossSiteRequest(child_id, route_id)) { |
| // Wrap the event handler to be sure the current page's onunload handler |
| // has a chance to run before we render the new page. |
| handler = new CrossSiteResourceHandler(handler, |
| child_id, |
| route_id, |
| this); |
| } |
| |
| // Insert a buffered event handler before the actual one. |
| handler = new BufferedResourceHandler(handler, this, request); |
| |
| // Insert safe browsing at the front of the chain, so it gets to decide |
| // on policies first. |
| if (safe_browsing_->enabled()) { |
| handler = CreateSafeBrowsingResourceHandler(handler, child_id, route_id, |
| request_data.resource_type); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| // We check offline first, then check safe browsing so that we still can block |
| // unsafe site after we remove offline page. |
| handler = |
| new OfflineResourceHandler(handler, child_id, route_id, this, request); |
| #endif |
| |
| // Make extra info and read footer (contains request ID). |
| ResourceDispatcherHostRequestInfo* extra_info = |
| new ResourceDispatcherHostRequestInfo( |
| handler, |
| process_type, |
| child_id, |
| route_id, |
| request_id, |
| request_data.resource_type, |
| upload_size, |
| false, // is download |
| ResourceType::IsFrame(request_data.resource_type), // allow_download |
| request_data.has_user_gesture, |
| request_data.host_renderer_id, |
| request_data.host_render_view_id); |
| ApplyExtensionLocalizationFilter(request_data.url, request_data.resource_type, |
| extra_info); |
| SetRequestInfo(request, extra_info); // Request takes ownership. |
| chrome_browser_net::SetOriginPIDForRequest( |
| request_data.origin_pid, request); |
| |
| if (request->url().SchemeIs(chrome::kBlobScheme) && context) { |
| // Hang on to a reference to ensure the blob is not released prior |
| // to the job being started. |
| webkit_blob::BlobStorageController* controller = |
| context->blob_storage_context()->controller(); |
| extra_info->set_requested_blob_data( |
| controller->GetBlobDataFromUrl(request->url())); |
| } |
| |
| // Have the appcache associate its extra info with the request. |
| appcache::AppCacheInterceptor::SetExtraRequestInfo( |
| request, context ? context->appcache_service() : NULL, child_id, |
| request_data.appcache_host_id, request_data.resource_type); |
| |
| BeginRequestInternal(request); |
| } |
| |
| void ResourceDispatcherHost::OnReleaseDownloadedFile(int request_id) { |
| DCHECK(pending_requests_.end() == |
| pending_requests_.find( |
| GlobalRequestID(filter_->child_id(), request_id))); |
| UnregisterDownloadedTempFile(filter_->child_id(), request_id); |
| } |
| |
| void ResourceDispatcherHost::OnDataReceivedACK(int request_id) { |
| DataReceivedACK(filter_->child_id(), request_id); |
| } |
| |
| void ResourceDispatcherHost::DataReceivedACK(int child_id, |
| int request_id) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(child_id, request_id)); |
| if (i == pending_requests_.end()) |
| return; |
| |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); |
| |
| // Decrement the number of pending data messages. |
| info->DecrementPendingDataCount(); |
| |
| // If the pending data count was higher than the max, resume the request. |
| if (info->pending_data_count() == kMaxPendingDataMessages) { |
| // Decrement the pending data count one more time because we also |
| // incremented it before pausing the request. |
| info->DecrementPendingDataCount(); |
| |
| // Resume the request. |
| PauseRequest(child_id, request_id, false); |
| } |
| } |
| |
| void ResourceDispatcherHost::OnDataDownloadedACK(int request_id) { |
| // TODO(michaeln): maybe throttle DataDownloaded messages |
| } |
| |
| void ResourceDispatcherHost::RegisterDownloadedTempFile( |
| int child_id, int request_id, DeletableFileReference* reference) { |
| registered_temp_files_[child_id][request_id] = reference; |
| ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( |
| child_id, reference->path()); |
| } |
| |
| void ResourceDispatcherHost::UnregisterDownloadedTempFile( |
| int child_id, int request_id) { |
| DeletableFilesMap& map = registered_temp_files_[child_id]; |
| DeletableFilesMap::iterator found = map.find(request_id); |
| if (found == map.end()) |
| return; |
| |
| ChildProcessSecurityPolicy::GetInstance()->RevokeAllPermissionsForFile( |
| child_id, found->second->path()); |
| map.erase(found); |
| } |
| |
| bool ResourceDispatcherHost::Send(IPC::Message* message) { |
| delete message; |
| return false; |
| } |
| |
| void ResourceDispatcherHost::OnUploadProgressACK(int request_id) { |
| int child_id = filter_->child_id(); |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(child_id, request_id)); |
| if (i == pending_requests_.end()) |
| return; |
| |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); |
| info->set_waiting_for_upload_progress_ack(false); |
| } |
| |
| void ResourceDispatcherHost::OnCancelRequest(int request_id) { |
| CancelRequest(filter_->child_id(), request_id, true); |
| } |
| |
| void ResourceDispatcherHost::OnFollowRedirect( |
| int request_id, |
| bool has_new_first_party_for_cookies, |
| const GURL& new_first_party_for_cookies) { |
| FollowDeferredRedirect(filter_->child_id(), request_id, |
| has_new_first_party_for_cookies, |
| new_first_party_for_cookies); |
| } |
| |
| ResourceHandler* ResourceDispatcherHost::CreateSafeBrowsingResourceHandler( |
| ResourceHandler* handler, int child_id, int route_id, |
| ResourceType::Type resource_type) { |
| return new SafeBrowsingResourceHandler( |
| handler, child_id, route_id, resource_type, safe_browsing_, this); |
| } |
| |
| ResourceDispatcherHostRequestInfo* |
| ResourceDispatcherHost::CreateRequestInfoForBrowserRequest( |
| ResourceHandler* handler, int child_id, int route_id, bool download) { |
| return new ResourceDispatcherHostRequestInfo(handler, |
| ChildProcessInfo::RENDER_PROCESS, |
| child_id, |
| route_id, |
| request_id_, |
| ResourceType::SUB_RESOURCE, |
| 0, // upload_size |
| download, // is_download |
| download, // allow_download |
| false, // has_user_gesture |
| -1, // host renderer id |
| -1); // host render view id |
| } |
| |
| void ResourceDispatcherHost::OnClosePageACK( |
| const ViewMsg_ClosePage_Params& params) { |
| if (params.for_cross_site_transition) { |
| // Closes for cross-site transitions are handled such that the cross-site |
| // transition continues. |
| GlobalRequestID global_id(params.new_render_process_host_id, |
| params.new_request_id); |
| PendingRequestList::iterator i = pending_requests_.find(global_id); |
| if (i != pending_requests_.end()) { |
| // The response we were meant to resume could have already been canceled. |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); |
| if (info->cross_site_handler()) |
| info->cross_site_handler()->ResumeResponse(); |
| } |
| } else { |
| // This is a tab close, so just forward the message to close it. |
| DCHECK(params.new_render_process_host_id == -1); |
| DCHECK(params.new_request_id == -1); |
| CallRenderViewHost(params.closing_process_id, |
| params.closing_route_id, |
| &RenderViewHost::ClosePageIgnoringUnloadEvents); |
| } |
| } |
| |
| // We are explicitly forcing the download of 'url'. |
| void ResourceDispatcherHost::BeginDownload( |
| const GURL& url, |
| const GURL& referrer, |
| const DownloadSaveInfo& save_info, |
| bool prompt_for_save_location, |
| int child_id, |
| int route_id, |
| net::URLRequestContext* request_context) { |
| if (is_shutdown_) |
| return; |
| |
| // Check if the renderer is permitted to request the requested URL. |
| if (!ChildProcessSecurityPolicy::GetInstance()-> |
| CanRequestURL(child_id, url)) { |
| VLOG(1) << "Denied unauthorized download request for " |
| << url.possibly_invalid_spec(); |
| return; |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| NewRunnableFunction(&download_util::NotifyDownloadInitiated, |
| child_id, route_id)); |
| |
| // Ensure the Chrome plugins are loaded, as they may intercept network |
| // requests. Does nothing if they are already loaded. |
| PluginService::GetInstance()->LoadChromePlugins(this); |
| net::URLRequest* request = new net::URLRequest(url, this); |
| |
| request_id_--; |
| |
| scoped_refptr<ResourceHandler> handler( |
| new DownloadResourceHandler(this, |
| child_id, |
| route_id, |
| request_id_, |
| url, |
| download_file_manager_.get(), |
| request, |
| prompt_for_save_location, |
| save_info)); |
| |
| if (safe_browsing_->enabled()) { |
| handler = CreateSafeBrowsingResourceHandler(handler, child_id, route_id, |
| ResourceType::MAIN_FRAME); |
| } |
| |
| if (!net::URLRequest::IsHandledURL(url)) { |
| VLOG(1) << "Download request for unsupported protocol: " |
| << url.possibly_invalid_spec(); |
| return; |
| } |
| |
| request->set_method("GET"); |
| request->set_referrer(CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kNoReferrers) ? std::string() : referrer.spec()); |
| request->set_context(request_context); |
| request->set_load_flags(request->load_flags() | |
| net::LOAD_IS_DOWNLOAD); |
| |
| ResourceDispatcherHostRequestInfo* extra_info = |
| CreateRequestInfoForBrowserRequest(handler, child_id, route_id, true); |
| SetRequestInfo(request, extra_info); // Request takes ownership. |
| |
| BeginRequestInternal(request); |
| } |
| |
| // This function is only used for saving feature. |
| void ResourceDispatcherHost::BeginSaveFile( |
| const GURL& url, |
| const GURL& referrer, |
| int child_id, |
| int route_id, |
| net::URLRequestContext* request_context) { |
| if (is_shutdown_) |
| return; |
| |
| // Ensure the Chrome plugins are loaded, as they may intercept network |
| // requests. Does nothing if they are already loaded. |
| PluginService::GetInstance()->LoadChromePlugins(this); |
| |
| scoped_refptr<ResourceHandler> handler( |
| new SaveFileResourceHandler(child_id, |
| route_id, |
| url, |
| save_file_manager_.get())); |
| request_id_--; |
| |
| bool known_proto = net::URLRequest::IsHandledURL(url); |
| if (!known_proto) { |
| // Since any URLs which have non-standard scheme have been filtered |
| // by save manager(see GURL::SchemeIsStandard). This situation |
| // should not happen. |
| NOTREACHED(); |
| return; |
| } |
| |
| net::URLRequest* request = new net::URLRequest(url, this); |
| request->set_method("GET"); |
| request->set_referrer(CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kNoReferrers) ? std::string() : referrer.spec()); |
| // So far, for saving page, we need fetch content from cache, in the |
| // future, maybe we can use a configuration to configure this behavior. |
| request->set_load_flags(net::LOAD_PREFERRING_CACHE); |
| request->set_context(request_context); |
| |
| // Since we're just saving some resources we need, disallow downloading. |
| ResourceDispatcherHostRequestInfo* extra_info = |
| CreateRequestInfoForBrowserRequest(handler, child_id, route_id, false); |
| SetRequestInfo(request, extra_info); // Request takes ownership. |
| |
| BeginRequestInternal(request); |
| } |
| |
| void ResourceDispatcherHost::FollowDeferredRedirect( |
| int child_id, |
| int request_id, |
| bool has_new_first_party_for_cookies, |
| const GURL& new_first_party_for_cookies) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(child_id, request_id)); |
| if (i == pending_requests_.end() || !i->second->status().is_success()) { |
| DLOG(WARNING) << "FollowDeferredRedirect for invalid request"; |
| return; |
| } |
| |
| if (has_new_first_party_for_cookies) |
| i->second->set_first_party_for_cookies(new_first_party_for_cookies); |
| i->second->FollowDeferredRedirect(); |
| } |
| |
| void ResourceDispatcherHost::StartDeferredRequest(int process_unique_id, |
| int request_id) { |
| GlobalRequestID global_id(process_unique_id, request_id); |
| PendingRequestList::iterator i = pending_requests_.find(global_id); |
| if (i == pending_requests_.end()) { |
| // The request may have been destroyed |
| LOG(WARNING) << "Trying to resume a non-existent request (" |
| << process_unique_id << ", " << request_id << ")"; |
| return; |
| } |
| |
| // TODO(eroman): are there other considerations for paused or blocked |
| // requests? |
| |
| net::URLRequest* request = i->second; |
| InsertIntoResourceQueue(request, *InfoForRequest(request)); |
| } |
| |
| bool ResourceDispatcherHost::WillSendData(int child_id, |
| int request_id) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(child_id, request_id)); |
| if (i == pending_requests_.end()) { |
| NOTREACHED() << "WillSendData for invalid request"; |
| return false; |
| } |
| |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); |
| |
| info->IncrementPendingDataCount(); |
| if (info->pending_data_count() > kMaxPendingDataMessages) { |
| // We reached the max number of data messages that can be sent to |
| // the renderer for a given request. Pause the request and wait for |
| // the renderer to start processing them before resuming it. |
| PauseRequest(child_id, request_id, true); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ResourceDispatcherHost::PauseRequest(int child_id, |
| int request_id, |
| bool pause) { |
| GlobalRequestID global_id(child_id, request_id); |
| PendingRequestList::iterator i = pending_requests_.find(global_id); |
| if (i == pending_requests_.end()) { |
| DLOG(WARNING) << "Pausing a request that wasn't found"; |
| return; |
| } |
| |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); |
| int pause_count = info->pause_count() + (pause ? 1 : -1); |
| if (pause_count < 0) { |
| NOTREACHED(); // Unbalanced call to pause. |
| return; |
| } |
| info->set_pause_count(pause_count); |
| |
| VLOG(1) << "To pause (" << pause << "): " << i->second->url().spec(); |
| |
| // If we're resuming, kick the request to start reading again. Run the read |
| // asynchronously to avoid recursion problems. |
| if (info->pause_count() == 0) { |
| MessageLoop::current()->PostTask(FROM_HERE, |
| method_runner_.NewRunnableMethod( |
| &ResourceDispatcherHost::ResumeRequest, global_id)); |
| } |
| } |
| |
| int ResourceDispatcherHost::GetOutstandingRequestsMemoryCost( |
| int child_id) const { |
| OutstandingRequestsMemoryCostMap::const_iterator entry = |
| outstanding_requests_memory_cost_map_.find(child_id); |
| return (entry == outstanding_requests_memory_cost_map_.end()) ? |
| 0 : entry->second; |
| } |
| |
| // The object died, so cancel and detach all requests associated with it except |
| // for downloads, which belong to the browser process even if initiated via a |
| // renderer. |
| void ResourceDispatcherHost::CancelRequestsForProcess(int child_id) { |
| CancelRequestsForRoute(child_id, -1 /* cancel all */); |
| registered_temp_files_.erase(child_id); |
| } |
| |
| void ResourceDispatcherHost::CancelRequestsForRoute(int child_id, |
| int route_id) { |
| // Since pending_requests_ is a map, we first build up a list of all of the |
| // matching requests to be cancelled, and then we cancel them. Since there |
| // may be more than one request to cancel, we cannot simply hold onto the map |
| // iterators found in the first loop. |
| |
| // Find the global ID of all matching elements. |
| std::vector<GlobalRequestID> matching_requests; |
| for (PendingRequestList::const_iterator i = pending_requests_.begin(); |
| i != pending_requests_.end(); ++i) { |
| if (i->first.child_id == child_id) { |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); |
| if (!info->is_download() && |
| (route_id == -1 || route_id == info->route_id())) { |
| matching_requests.push_back( |
| GlobalRequestID(child_id, i->first.request_id)); |
| } |
| } |
| } |
| |
| // Remove matches. |
| for (size_t i = 0; i < matching_requests.size(); ++i) { |
| PendingRequestList::iterator iter = |
| pending_requests_.find(matching_requests[i]); |
| // Although every matching request was in pending_requests_ when we built |
| // matching_requests, it is normal for a matching request to be not found |
| // in pending_requests_ after we have removed some matching requests from |
| // pending_requests_. For example, deleting a net::URLRequest that has |
| // exclusive (write) access to an HTTP cache entry may unblock another |
| // net::URLRequest that needs exclusive access to the same cache entry, and |
| // that net::URLRequest may complete and remove itself from |
| // pending_requests_. So we need to check that iter is not equal to |
| // pending_requests_.end(). |
| if (iter != pending_requests_.end()) |
| RemovePendingRequest(iter); |
| } |
| |
| // Now deal with blocked requests if any. |
| if (route_id != -1) { |
| if (blocked_requests_map_.find(std::pair<int, int>(child_id, route_id)) != |
| blocked_requests_map_.end()) { |
| CancelBlockedRequestsForRoute(child_id, route_id); |
| } |
| } else { |
| // We have to do all render views for the process |child_id|. |
| // Note that we have to do this in 2 passes as we cannot call |
| // CancelBlockedRequestsForRoute while iterating over |
| // blocked_requests_map_, as it modifies it. |
| std::set<int> route_ids; |
| for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); |
| iter != blocked_requests_map_.end(); ++iter) { |
| if (iter->first.first == child_id) |
| route_ids.insert(iter->first.second); |
| } |
| for (std::set<int>::const_iterator iter = route_ids.begin(); |
| iter != route_ids.end(); ++iter) { |
| CancelBlockedRequestsForRoute(child_id, *iter); |
| } |
| } |
| } |
| |
| // Cancels the request and removes it from the list. |
| void ResourceDispatcherHost::RemovePendingRequest(int child_id, |
| int request_id) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(child_id, request_id)); |
| if (i == pending_requests_.end()) { |
| NOTREACHED() << "Trying to remove a request that's not here"; |
| return; |
| } |
| RemovePendingRequest(i); |
| } |
| |
| void ResourceDispatcherHost::RemovePendingRequest( |
| const PendingRequestList::iterator& iter) { |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(iter->second); |
| |
| // Remove the memory credit that we added when pushing the request onto |
| // the pending list. |
| IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost(), |
| info->child_id()); |
| |
| // Notify interested parties that the request object is going away. |
| if (info->login_handler()) |
| info->login_handler()->OnRequestCancelled(); |
| if (info->ssl_client_auth_handler()) |
| info->ssl_client_auth_handler()->OnRequestCancelled(); |
| resource_queue_.RemoveRequest(iter->first); |
| |
| delete iter->second; |
| pending_requests_.erase(iter); |
| |
| // If we have no more pending requests, then stop the load state monitor |
| if (pending_requests_.empty()) |
| update_load_states_timer_.Stop(); |
| } |
| |
| // net::URLRequest::Delegate --------------------------------------------------- |
| |
| void ResourceDispatcherHost::OnReceivedRedirect(net::URLRequest* request, |
| const GURL& new_url, |
| bool* defer_redirect) { |
| VLOG(1) << "OnReceivedRedirect: " << request->url().spec(); |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| |
| DCHECK(request->status().is_success()); |
| |
| if (info->process_type() != ChildProcessInfo::PLUGIN_PROCESS && |
| !ChildProcessSecurityPolicy::GetInstance()-> |
| CanRequestURL(info->child_id(), new_url)) { |
| VLOG(1) << "Denied unauthorized request for " |
| << new_url.possibly_invalid_spec(); |
| |
| // Tell the renderer that this request was disallowed. |
| CancelRequestInternal(request, false); |
| return; |
| } |
| |
| NotifyReceivedRedirect(request, info->child_id(), new_url); |
| |
| if (HandleExternalProtocol(info->request_id(), info->child_id(), |
| info->route_id(), new_url, |
| info->resource_type(), info->resource_handler())) { |
| // The request is complete so we can remove it. |
| RemovePendingRequest(info->child_id(), info->request_id()); |
| return; |
| } |
| |
| scoped_refptr<ResourceResponse> response(new ResourceResponse); |
| PopulateResourceResponse(request, |
| info->replace_extension_localization_templates(), response); |
| if (!info->resource_handler()->OnRequestRedirected(info->request_id(), |
| new_url, |
| response, defer_redirect)) |
| CancelRequestInternal(request, false); |
| } |
| |
| void ResourceDispatcherHost::OnAuthRequired( |
| net::URLRequest* request, |
| net::AuthChallengeInfo* auth_info) { |
| if (request->load_flags() & net::LOAD_PREFETCH) { |
| 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. |
| // TODO(mpcomplete): We should block the parent tab while waiting for |
| // authentication. |
| // That would also solve the problem of the net::URLRequest being cancelled |
| // before we receive authentication. |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| DCHECK(!info->login_handler()) << |
| "OnAuthRequired called with login_handler pending"; |
| info->set_login_handler(CreateLoginPrompt(auth_info, request)); |
| } |
| |
| void ResourceDispatcherHost::OnCertificateRequested( |
| net::URLRequest* request, |
| net::SSLCertRequestInfo* cert_request_info) { |
| DCHECK(request); |
| |
| if (cert_request_info->client_certs.empty()) { |
| // No need to query the user if there are no certs to choose from. |
| request->ContinueWithCertificate(NULL); |
| return; |
| } |
| |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| DCHECK(!info->ssl_client_auth_handler()) << |
| "OnCertificateRequested called with ssl_client_auth_handler pending"; |
| info->set_ssl_client_auth_handler( |
| new SSLClientAuthHandler(request, cert_request_info)); |
| info->ssl_client_auth_handler()->SelectCertificate(); |
| } |
| |
| void ResourceDispatcherHost::OnSSLCertificateError( |
| net::URLRequest* request, |
| int cert_error, |
| net::X509Certificate* cert) { |
| DCHECK(request); |
| SSLManager::OnSSLCertificateError(this, request, cert_error, cert); |
| } |
| |
| void ResourceDispatcherHost::OnGetCookies( |
| net::URLRequest* request, |
| bool blocked_by_policy) { |
| VLOG(1) << "OnGetCookies: " << request->url().spec(); |
| |
| int render_process_id, render_view_id; |
| if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) |
| return; |
| |
| net::URLRequestContext* context = request->context(); |
| |
| net::CookieMonster* cookie_monster = |
| context->cookie_store()->GetCookieMonster(); |
| net::CookieList cookie_list = |
| cookie_monster->GetAllCookiesForURL(request->url()); |
| CallRenderViewHostContentSettingsDelegate( |
| render_process_id, render_view_id, |
| &RenderViewHostDelegate::ContentSettings::OnCookiesRead, |
| request->url(), cookie_list, blocked_by_policy); |
| } |
| |
| void ResourceDispatcherHost::OnSetCookie(net::URLRequest* request, |
| const std::string& cookie_line, |
| const net::CookieOptions& options, |
| bool blocked_by_policy) { |
| VLOG(1) << "OnSetCookie: " << request->url().spec(); |
| |
| int render_process_id, render_view_id; |
| if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) |
| return; |
| |
| CallRenderViewHostContentSettingsDelegate( |
| render_process_id, render_view_id, |
| &RenderViewHostDelegate::ContentSettings::OnCookieChanged, |
| request->url(), cookie_line, options, blocked_by_policy); |
| } |
| |
| void ResourceDispatcherHost::OnResponseStarted(net::URLRequest* request) { |
| VLOG(1) << "OnResponseStarted: " << request->url().spec(); |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| if (PauseRequestIfNeeded(info)) { |
| VLOG(1) << "OnResponseStarted pausing: " << request->url().spec(); |
| return; |
| } |
| |
| if (request->status().is_success()) { |
| // We want to send a final upload progress message prior to sending |
| // the response complete message even if we're waiting for an ack to |
| // to a previous upload progress message. |
| info->set_waiting_for_upload_progress_ack(false); |
| MaybeUpdateUploadProgress(info, request); |
| |
| if (!CompleteResponseStarted(request)) { |
| CancelRequestInternal(request, false); |
| } else { |
| // Check if the handler paused the request in their OnResponseStarted. |
| if (PauseRequestIfNeeded(info)) { |
| VLOG(1) << "OnResponseStarted pausing2: " << request->url().spec(); |
| return; |
| } |
| |
| StartReading(request); |
| } |
| } else { |
| OnResponseCompleted(request); |
| } |
| } |
| |
| bool ResourceDispatcherHost::CompleteResponseStarted(net::URLRequest* request) { |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| |
| scoped_refptr<ResourceResponse> response(new ResourceResponse); |
| PopulateResourceResponse(request, |
| info->replace_extension_localization_templates(), response); |
| |
| if (request->ssl_info().cert) { |
| int cert_id = |
| CertStore::GetInstance()->StoreCert(request->ssl_info().cert, |
| info->child_id()); |
| response->response_head.security_info = |
| SSLManager::SerializeSecurityInfo( |
| cert_id, request->ssl_info().cert_status, |
| request->ssl_info().security_bits, |
| request->ssl_info().connection_status); |
| } else { |
| // We should not have any SSL state. |
| DCHECK(!request->ssl_info().cert_status && |
| request->ssl_info().security_bits == -1 && |
| !request->ssl_info().connection_status); |
| } |
| |
| NotifyResponseStarted(request, info->child_id()); |
| info->set_called_on_response_started(true); |
| return info->resource_handler()->OnResponseStarted(info->request_id(), |
| response.get()); |
| } |
| |
| void ResourceDispatcherHost::CancelRequest(int child_id, |
| int request_id, |
| bool from_renderer) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(child_id, request_id)); |
| if (i == pending_requests_.end()) { |
| // We probably want to remove this warning eventually, but I wanted to be |
| // able to notice when this happens during initial development since it |
| // should be rare and may indicate a bug. |
| DLOG(WARNING) << "Canceling a request that wasn't found"; |
| return; |
| } |
| CancelRequestInternal(i->second, from_renderer); |
| } |
| |
| void ResourceDispatcherHost::CancelRequestInternal(net::URLRequest* request, |
| bool from_renderer) { |
| VLOG(1) << "CancelRequest: " << request->url().spec(); |
| |
| // 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. |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| if (!from_renderer || !info->is_download()) { |
| if (info->login_handler()) { |
| info->login_handler()->OnRequestCancelled(); |
| info->set_login_handler(NULL); |
| } |
| if (info->ssl_client_auth_handler()) { |
| info->ssl_client_auth_handler()->OnRequestCancelled(); |
| info->set_ssl_client_auth_handler(NULL); |
| } |
| request->Cancel(); |
| // Our callers assume |request| is valid after we return. |
| DCHECK(IsValidRequest(request)); |
| } |
| |
| // Do not remove from the pending requests, as the request will still |
| // call AllDataReceived, and may even have more data before it does |
| // that. |
| } |
| |
| int ResourceDispatcherHost::IncrementOutstandingRequestsMemoryCost( |
| int cost, |
| int child_id) { |
| // Retrieve the previous value (defaulting to 0 if not found). |
| OutstandingRequestsMemoryCostMap::iterator prev_entry = |
| outstanding_requests_memory_cost_map_.find(child_id); |
| int new_cost = 0; |
| if (prev_entry != outstanding_requests_memory_cost_map_.end()) |
| new_cost = prev_entry->second; |
| |
| // Insert/update the total; delete entries when their value reaches 0. |
| new_cost += cost; |
| CHECK(new_cost >= 0); |
| if (new_cost == 0) |
| outstanding_requests_memory_cost_map_.erase(prev_entry); |
| else |
| outstanding_requests_memory_cost_map_[child_id] = new_cost; |
| |
| return new_cost; |
| } |
| |
| // static |
| int ResourceDispatcherHost::CalculateApproximateMemoryCost( |
| net::URLRequest* request) { |
| // The following fields should be a minor size contribution (experimentally |
| // on the order of 100). However since they are variable length, it could |
| // in theory be a sizeable contribution. |
| int strings_cost = request->extra_request_headers().ToString().size() + |
| request->original_url().spec().size() + |
| request->referrer().size() + |
| request->method().size(); |
| |
| int upload_cost = 0; |
| |
| // TODO(eroman): don't enable the upload throttling until we have data |
| // showing what a reasonable limit is (limiting to 25MB of uploads may |
| // be too restrictive). |
| #if 0 |
| // Sum all the (non-file) upload data attached to the request, if any. |
| if (request->has_upload()) { |
| const std::vector<net::UploadData::Element>& uploads = |
| request->get_upload()->elements(); |
| std::vector<net::UploadData::Element>::const_iterator iter; |
| for (iter = uploads.begin(); iter != uploads.end(); ++iter) { |
| if (iter->type() == net::UploadData::TYPE_BYTES) { |
| int64 element_size = iter->GetContentLength(); |
| // This cast should not result in truncation. |
| upload_cost += static_cast<int>(element_size); |
| } |
| } |
| } |
| #endif |
| |
| // Note that this expression will typically be dominated by: |
| // |kAvgBytesPerOutstandingRequest|. |
| return kAvgBytesPerOutstandingRequest + strings_cost + upload_cost; |
| } |
| |
| void ResourceDispatcherHost::BeginRequestInternal(net::URLRequest* request) { |
| DCHECK(!request->is_pending()); |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| |
| // Add the memory estimate that starting this request will consume. |
| info->set_memory_cost(CalculateApproximateMemoryCost(request)); |
| int memory_cost = IncrementOutstandingRequestsMemoryCost(info->memory_cost(), |
| info->child_id()); |
| |
| // If enqueing/starting this request will exceed our per-process memory |
| // bound, abort it right away. |
| if (memory_cost > max_outstanding_requests_cost_per_process_) { |
| // We call "SimulateError()" as a way of setting the net::URLRequest's |
| // status -- it has no effect beyond this, since the request hasn't started. |
| request->SimulateError(net::ERR_INSUFFICIENT_RESOURCES); |
| |
| // TODO(eroman): this is kinda funky -- we insert the unstarted request into |
| // |pending_requests_| simply to please OnResponseCompleted(). |
| GlobalRequestID global_id(info->child_id(), info->request_id()); |
| pending_requests_[global_id] = request; |
| OnResponseCompleted(request); |
| return; |
| } |
| |
| std::pair<int, int> pair_id(info->child_id(), info->route_id()); |
| BlockedRequestMap::const_iterator iter = blocked_requests_map_.find(pair_id); |
| if (iter != blocked_requests_map_.end()) { |
| // The request should be blocked. |
| iter->second->push_back(request); |
| return; |
| } |
| |
| GlobalRequestID global_id(info->child_id(), info->request_id()); |
| pending_requests_[global_id] = request; |
| |
| // Give the resource handlers an opportunity to delay the net::URLRequest from |
| // being started. |
| // |
| // There are three cases: |
| // |
| // (1) if OnWillStart() returns false, the request is cancelled (regardless |
| // of whether |defer_start| was set). |
| // (2) If |defer_start| was set to true, then the request is not added |
| // into the resource queue, and will only be started in response to |
| // calling StartDeferredRequest(). |
| // (3) If |defer_start| is not set, then the request is inserted into |
| // the resource_queue_ (which may pause it further, or start it). |
| bool defer_start = false; |
| if (!info->resource_handler()->OnWillStart( |
| info->request_id(), request->url(), |
| &defer_start)) { |
| CancelRequestInternal(request, false); |
| return; |
| } |
| |
| if (!defer_start) { |
| InsertIntoResourceQueue(request, *info); |
| } |
| } |
| |
| void ResourceDispatcherHost::InsertIntoResourceQueue( |
| net::URLRequest* request, |
| const ResourceDispatcherHostRequestInfo& request_info) { |
| resource_queue_.AddRequest(request, request_info); |
| |
| // Make sure we have the load state monitor running |
| if (!update_load_states_timer_.IsRunning()) { |
| update_load_states_timer_.Start( |
| TimeDelta::FromMilliseconds(kUpdateLoadStatesIntervalMsec), |
| this, &ResourceDispatcherHost::UpdateLoadStates); |
| } |
| } |
| |
| bool ResourceDispatcherHost::PauseRequestIfNeeded( |
| ResourceDispatcherHostRequestInfo* info) { |
| if (info->pause_count() > 0) |
| info->set_is_paused(true); |
| return info->is_paused(); |
| } |
| |
| void ResourceDispatcherHost::ResumeRequest(const GlobalRequestID& request_id) { |
| PendingRequestList::iterator i = pending_requests_.find(request_id); |
| if (i == pending_requests_.end()) // The request may have been destroyed |
| return; |
| |
| net::URLRequest* request = i->second; |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| if (!info->is_paused()) |
| return; |
| |
| VLOG(1) << "Resuming: " << i->second->url().spec(); |
| |
| info->set_is_paused(false); |
| |
| if (info->called_on_response_started()) { |
| if (info->has_started_reading()) { |
| OnReadCompleted(i->second, info->paused_read_bytes()); |
| } else { |
| StartReading(request); |
| } |
| } else { |
| OnResponseStarted(i->second); |
| } |
| } |
| |
| void ResourceDispatcherHost::StartReading(net::URLRequest* request) { |
| // Start reading. |
| int bytes_read = 0; |
| if (Read(request, &bytes_read)) { |
| OnReadCompleted(request, bytes_read); |
| } else if (!request->status().is_io_pending()) { |
| DCHECK(!InfoForRequest(request)->is_paused()); |
| // If the error is not an IO pending, then we're done reading. |
| OnResponseCompleted(request); |
| } |
| } |
| |
| bool ResourceDispatcherHost::Read(net::URLRequest* request, int* bytes_read) { |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| DCHECK(!info->is_paused()); |
| |
| net::IOBuffer* buf; |
| int buf_size; |
| if (!info->resource_handler()->OnWillRead(info->request_id(), |
| &buf, &buf_size, -1)) { |
| return false; |
| } |
| |
| DCHECK(buf); |
| DCHECK(buf_size > 0); |
| |
| info->set_has_started_reading(true); |
| return request->Read(buf, buf_size, bytes_read); |
| } |
| |
| void ResourceDispatcherHost::OnReadCompleted(net::URLRequest* request, |
| int bytes_read) { |
| DCHECK(request); |
| VLOG(1) << "OnReadCompleted: " << request->url().spec(); |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| |
| // OnReadCompleted can be called without Read (e.g., for chrome:// URLs). |
| // Make sure we know that a read has begun. |
| info->set_has_started_reading(true); |
| |
| if (PauseRequestIfNeeded(info)) { |
| info->set_paused_read_bytes(bytes_read); |
| VLOG(1) << "OnReadCompleted pausing: " << request->url().spec(); |
| return; |
| } |
| |
| if (request->status().is_success() && CompleteRead(request, &bytes_read)) { |
| // The request can be paused if we realize that the renderer is not |
| // servicing messages fast enough. |
| if (info->pause_count() == 0 && |
| Read(request, &bytes_read) && |
| request->status().is_success()) { |
| if (bytes_read == 0) { |
| CompleteRead(request, &bytes_read); |
| } else { |
| // Force the next CompleteRead / Read pair to run as a separate task. |
| // This avoids a fast, large network request from monopolizing the IO |
| // thread and starving other IO operations from running. |
| info->set_paused_read_bytes(bytes_read); |
| info->set_is_paused(true); |
| GlobalRequestID id(info->child_id(), info->request_id()); |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| method_runner_.NewRunnableMethod( |
| &ResourceDispatcherHost::ResumeRequest, id)); |
| return; |
| } |
| } |
| } |
| |
| if (PauseRequestIfNeeded(info)) { |
| info->set_paused_read_bytes(bytes_read); |
| VLOG(1) << "OnReadCompleted (CompleteRead) pausing: " |
| << request->url().spec(); |
| return; |
| } |
| |
| // If the status is not IO pending then we've either finished (success) or we |
| // had an error. Either way, we're done! |
| if (!request->status().is_io_pending()) |
| OnResponseCompleted(request); |
| } |
| |
| bool ResourceDispatcherHost::CompleteRead(net::URLRequest* request, |
| int* bytes_read) { |
| if (!request || !request->status().is_success()) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| if (!info->resource_handler()->OnReadCompleted(info->request_id(), |
| bytes_read)) { |
| CancelRequestInternal(request, false); |
| return false; |
| } |
| |
| return *bytes_read != 0; |
| } |
| |
| void ResourceDispatcherHost::OnResponseCompleted(net::URLRequest* request) { |
| VLOG(1) << "OnResponseCompleted: " << request->url().spec(); |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| |
| // If the load for a main frame has failed, track it in a histogram, |
| // since it will probably cause the user to see an error page. |
| if (!request->status().is_success() && |
| info->resource_type() == ResourceType::MAIN_FRAME && |
| request->status().os_error() != net::ERR_ABORTED) { |
| UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.ErrorCodesForMainFrame", |
| -request->status().os_error(), |
| GetAllNetErrorCodes()); |
| } |
| |
| std::string security_info; |
| const net::SSLInfo& ssl_info = request->ssl_info(); |
| if (ssl_info.cert != NULL) { |
| int cert_id = CertStore::GetInstance()->StoreCert(ssl_info.cert, |
| info->child_id()); |
| security_info = SSLManager::SerializeSecurityInfo( |
| cert_id, ssl_info.cert_status, ssl_info.security_bits, |
| ssl_info.connection_status); |
| } |
| |
| if (info->resource_handler()->OnResponseCompleted(info->request_id(), |
| request->status(), |
| security_info)) { |
| NotifyResponseCompleted(request, info->child_id()); |
| |
| // The request is complete so we can remove it. |
| RemovePendingRequest(info->child_id(), info->request_id()); |
| } |
| // If the handler's OnResponseCompleted returns false, we are deferring the |
| // call until later. We will notify the world and clean up when we resume. |
| } |
| |
| // static |
| ResourceDispatcherHostRequestInfo* ResourceDispatcherHost::InfoForRequest( |
| net::URLRequest* request) { |
| // Avoid writing this function twice by casting the cosnt version. |
| const net::URLRequest* const_request = request; |
| return const_cast<ResourceDispatcherHostRequestInfo*>( |
| InfoForRequest(const_request)); |
| } |
| |
| // static |
| const ResourceDispatcherHostRequestInfo* ResourceDispatcherHost::InfoForRequest( |
| const net::URLRequest* request) { |
| const ResourceDispatcherHostRequestInfo* info = |
| static_cast<const ResourceDispatcherHostRequestInfo*>( |
| request->GetUserData(NULL)); |
| DLOG_IF(WARNING, !info) << "Request doesn't seem to have our data"; |
| return info; |
| } |
| |
| // static |
| bool ResourceDispatcherHost::RenderViewForRequest( |
| const net::URLRequest* request, |
| int* render_process_host_id, |
| int* render_view_host_id) { |
| const ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| if (!info) { |
| *render_process_host_id = -1; |
| *render_view_host_id = -1; |
| return false; |
| } |
| |
| // If the request is from the worker process, find a tab that owns the worker. |
| if (info->process_type() == ChildProcessInfo::WORKER_PROCESS) { |
| // Need to display some related UI for this network request - pick an |
| // arbitrary parent to do so. |
| if (!WorkerService::GetInstance()->GetRendererForWorker( |
| info->child_id(), render_process_host_id, render_view_host_id)) { |
| *render_process_host_id = -1; |
| *render_view_host_id = -1; |
| return false; |
| } |
| } else { |
| *render_process_host_id = info->child_id(); |
| *render_view_host_id = info->route_id(); |
| } |
| return true; |
| } |
| |
| void ResourceDispatcherHost::AddObserver(Observer* obs) { |
| observer_list_.AddObserver(obs); |
| } |
| |
| void ResourceDispatcherHost::RemoveObserver(Observer* obs) { |
| observer_list_.RemoveObserver(obs); |
| } |
| |
| net::URLRequest* ResourceDispatcherHost::GetURLRequest( |
| const GlobalRequestID& request_id) const { |
| // This should be running in the IO loop. |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| PendingRequestList::const_iterator i = pending_requests_.find(request_id); |
| if (i == pending_requests_.end()) |
| return NULL; |
| |
| return i->second; |
| } |
| |
| static int GetCertID(net::URLRequest* request, int child_id) { |
| if (request->ssl_info().cert) { |
| return CertStore::GetInstance()->StoreCert(request->ssl_info().cert, |
| child_id); |
| } |
| return 0; |
| } |
| |
| void ResourceDispatcherHost::NotifyResponseStarted(net::URLRequest* request, |
| int child_id) { |
| // Notify the observers on the IO thread. |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnRequestStarted(this, request)); |
| |
| int render_process_id, render_view_id; |
| if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) |
| return; |
| |
| // Notify the observers on the UI thread. |
| ResourceRequestDetails* detail = new ResourceRequestDetails( |
| request, GetCertID(request, child_id)); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| NewRunnableFunction( |
| &ResourceDispatcherHost::NotifyOnUI<ResourceRequestDetails>, |
| NotificationType::RESOURCE_RESPONSE_STARTED, |
| render_process_id, render_view_id, detail)); |
| } |
| |
| void ResourceDispatcherHost::NotifyResponseCompleted(net::URLRequest* request, |
| int child_id) { |
| // Notify the observers on the IO thread. |
| FOR_EACH_OBSERVER(Observer, observer_list_, |
| OnResponseCompleted(this, request)); |
| } |
| |
| void ResourceDispatcherHost::NotifyReceivedRedirect(net::URLRequest* request, |
| int child_id, |
| const GURL& new_url) { |
| // Notify the observers on the IO thread. |
| FOR_EACH_OBSERVER(Observer, observer_list_, |
| OnReceivedRedirect(this, request, new_url)); |
| |
| int render_process_id, render_view_id; |
| if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) |
| return; |
| |
| // Notify the observers on the UI thread. |
| ResourceRedirectDetails* detail = new ResourceRedirectDetails( |
| request, GetCertID(request, child_id), new_url); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| NewRunnableFunction( |
| &ResourceDispatcherHost::NotifyOnUI<ResourceRedirectDetails>, |
| NotificationType::RESOURCE_RECEIVED_REDIRECT, |
| render_process_id, render_view_id, detail)); |
| } |
| |
| template <class T> |
| void ResourceDispatcherHost::NotifyOnUI(NotificationType type, |
| int render_process_id, |
| int render_view_id, |
| T* detail) { |
| RenderViewHost* rvh = |
| RenderViewHost::FromID(render_process_id, render_view_id); |
| if (rvh) { |
| RenderViewHostDelegate* rvhd = rvh->delegate(); |
| NotificationService::current()->Notify( |
| type, Source<RenderViewHostDelegate>(rvhd), Details<T>(detail)); |
| } |
| delete detail; |
| } |
| |
| namespace { |
| |
| // This function attempts to return the "more interesting" load state of |a| |
| // and |b|. We don't have temporal information about these load states |
| // (meaning we don't know when we transitioned into these states), so we just |
| // rank them according to how "interesting" the states are. |
| // |
| // We take advantage of the fact that the load states are an enumeration listed |
| // in the order in which they occur during the lifetime of a request, so we can |
| // regard states with larger numeric values as being further along toward |
| // completion. We regard those states as more interesting to report since they |
| // represent progress. |
| // |
| // For example, by this measure "tranferring data" is a more interesting state |
| // than "resolving host" because when we are transferring data we are actually |
| // doing something that corresponds to changes that the user might observe, |
| // whereas waiting for a host name to resolve implies being stuck. |
| // |
| net::LoadState MoreInterestingLoadState(net::LoadState a, net::LoadState b) { |
| return (a < b) ? b : a; |
| } |
| |
| // Carries information about a load state change. |
| struct LoadInfo { |
| GURL url; |
| net::LoadState load_state; |
| uint64 upload_position; |
| uint64 upload_size; |
| }; |
| |
| // Map from ProcessID+ViewID pair to LoadState |
| typedef std::map<std::pair<int, int>, LoadInfo> LoadInfoMap; |
| |
| // Used to marshall calls to LoadStateChanged from the IO to UI threads. We do |
| // them all as a single task to avoid spamming the UI thread. |
| class LoadInfoUpdateTask : public Task { |
| public: |
| virtual void Run() { |
| LoadInfoMap::const_iterator i; |
| for (i = info_map.begin(); i != info_map.end(); ++i) { |
| RenderViewHost* view = |
| RenderViewHost::FromID(i->first.first, i->first.second); |
| if (view) // The view could be gone at this point. |
| view->LoadStateChanged(i->second.url, i->second.load_state, |
| i->second.upload_position, |
| i->second.upload_size); |
| } |
| } |
| LoadInfoMap info_map; |
| }; |
| |
| } // namespace |
| |
| void ResourceDispatcherHost::UpdateLoadStates() { |
| // Populate this map with load state changes, and then send them on to the UI |
| // thread where they can be passed along to the respective RVHs. |
| LoadInfoMap info_map; |
| |
| PendingRequestList::const_iterator i; |
| |
| // Determine the largest upload size of all requests |
| // in each View (good chance it's zero). |
| std::map<std::pair<int, int>, uint64> largest_upload_size; |
| for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { |
| net::URLRequest* request = i->second; |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| uint64 upload_size = info->upload_size(); |
| if (request->GetLoadState() != net::LOAD_STATE_SENDING_REQUEST) |
| upload_size = 0; |
| std::pair<int, int> key(info->child_id(), info->route_id()); |
| if (upload_size && largest_upload_size[key] < upload_size) |
| largest_upload_size[key] = upload_size; |
| } |
| |
| for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { |
| net::URLRequest* request = i->second; |
| net::LoadState load_state = request->GetLoadState(); |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| |
| // We also poll for upload progress on this timer and send upload |
| // progress ipc messages to the plugin process. |
| bool update_upload_progress = MaybeUpdateUploadProgress(info, request); |
| |
| if (info->last_load_state() != load_state || update_upload_progress) { |
| std::pair<int, int> key(info->child_id(), info->route_id()); |
| |
| // If a request is uploading data, ignore all other requests so that the |
| // upload progress takes priority for being shown in the status bar. |
| if (largest_upload_size.find(key) != largest_upload_size.end() && |
| info->upload_size() < largest_upload_size[key]) |
| continue; |
| |
| info->set_last_load_state(load_state); |
| |
| net::LoadState to_insert; |
| LoadInfoMap::iterator existing = info_map.find(key); |
| if (existing == info_map.end()) { |
| to_insert = load_state; |
| } else { |
| to_insert = |
| MoreInterestingLoadState(existing->second.load_state, load_state); |
| if (to_insert == existing->second.load_state) |
| continue; |
| } |
| LoadInfo& load_info = info_map[key]; |
| load_info.url = request->url(); |
| load_info.load_state = to_insert; |
| load_info.upload_size = info->upload_size(); |
| load_info.upload_position = request->GetUploadProgress(); |
| } |
| } |
| |
| if (info_map.empty()) |
| return; |
| |
| LoadInfoUpdateTask* task = new LoadInfoUpdateTask; |
| task->info_map.swap(info_map); |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, task); |
| } |
| |
| // Calls the ResourceHandler to send upload progress messages to the renderer. |
| // Returns true iff an upload progress message should be sent to the UI thread. |
| bool ResourceDispatcherHost::MaybeUpdateUploadProgress( |
| ResourceDispatcherHostRequestInfo *info, |
| net::URLRequest *request) { |
| |
| if (!info->upload_size() || info->waiting_for_upload_progress_ack()) |
| return false; |
| |
| uint64 size = info->upload_size(); |
| uint64 position = request->GetUploadProgress(); |
| if (position == info->last_upload_position()) |
| return false; // no progress made since last time |
| |
| const uint64 kHalfPercentIncrements = 200; |
| const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000); |
| |
| uint64 amt_since_last = position - info->last_upload_position(); |
| TimeDelta time_since_last = TimeTicks::Now() - info->last_upload_ticks(); |
| |
| bool is_finished = (size == position); |
| bool enough_new_progress = (amt_since_last > (size / kHalfPercentIncrements)); |
| bool too_much_time_passed = time_since_last > kOneSecond; |
| |
| if (is_finished || enough_new_progress || too_much_time_passed) { |
| if (request->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS) { |
| info->resource_handler()->OnUploadProgress(info->request_id(), |
| position, size); |
| info->set_waiting_for_upload_progress_ack(true); |
| } |
| info->set_last_upload_ticks(TimeTicks::Now()); |
| info->set_last_upload_position(position); |
| return true; |
| } |
| return false; |
| } |
| |
| void ResourceDispatcherHost::BlockRequestsForRoute(int child_id, int route_id) { |
| std::pair<int, int> key(child_id, route_id); |
| DCHECK(blocked_requests_map_.find(key) == blocked_requests_map_.end()) << |
| "BlockRequestsForRoute called multiple time for the same RVH"; |
| blocked_requests_map_[key] = new BlockedRequestsList(); |
| } |
| |
| void ResourceDispatcherHost::ResumeBlockedRequestsForRoute(int child_id, |
| int route_id) { |
| ProcessBlockedRequestsForRoute(child_id, route_id, false); |
| } |
| |
| void ResourceDispatcherHost::CancelBlockedRequestsForRoute(int child_id, |
| int route_id) { |
| ProcessBlockedRequestsForRoute(child_id, route_id, true); |
| } |
| |
| void ResourceDispatcherHost::ProcessBlockedRequestsForRoute( |
| int child_id, |
| int route_id, |
| bool cancel_requests) { |
| BlockedRequestMap::iterator iter = blocked_requests_map_.find( |
| std::pair<int, int>(child_id, route_id)); |
| if (iter == blocked_requests_map_.end()) { |
| // It's possible to reach here if the renderer crashed while an interstitial |
| // page was showing. |
| return; |
| } |
| |
| BlockedRequestsList* requests = iter->second; |
| |
| // Removing the vector from the map unblocks any subsequent requests. |
| blocked_requests_map_.erase(iter); |
| |
| for (BlockedRequestsList::iterator req_iter = requests->begin(); |
| req_iter != requests->end(); ++req_iter) { |
| // Remove the memory credit that we added when pushing the request onto |
| // the blocked list. |
| net::URLRequest* request = *req_iter; |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost(), |
| info->child_id()); |
| if (cancel_requests) |
| delete request; |
| else |
| BeginRequestInternal(request); |
| } |
| |
| delete requests; |
| } |
| |
| bool ResourceDispatcherHost::IsValidRequest(net::URLRequest* request) { |
| if (!request) |
| return false; |
| ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); |
| return pending_requests_.find( |
| GlobalRequestID(info->child_id(), info->request_id())) != |
| pending_requests_.end(); |
| } |
| |
| // static |
| void ResourceDispatcherHost::ApplyExtensionLocalizationFilter( |
| const GURL& url, |
| const ResourceType::Type& resource_type, |
| ResourceDispatcherHostRequestInfo* request_info) { |
| // Apply filter to chrome extension CSS files. |
| if (url.SchemeIs(chrome::kExtensionScheme) && |
| resource_type == ResourceType::STYLESHEET) |
| request_info->set_replace_extension_localization_templates(); |
| } |
| |
| // static |
| net::RequestPriority ResourceDispatcherHost::DetermineRequestPriority( |
| ResourceType::Type type) { |
| // Determine request priority based on how critical this resource typically |
| // is to user-perceived page load performance. Important considerations are: |
| // * Can this resource block the download of other resources. |
| // * Can this resource block the rendering of the page. |
| // * How useful is the page to the user if this resource is not loaded yet. |
| switch (type) { |
| // Main frames are the highest priority because they can block nearly every |
| // type of other resource and there is no useful display without them. |
| // Sub frames are a close second, however it is a common pattern to wrap |
| // ads in an iframe or even in multiple nested iframes. It is worth |
| // investigating if there is a better priority for them. |
| case ResourceType::MAIN_FRAME: |
| case ResourceType::SUB_FRAME: |
| return net::HIGHEST; |
| |
| // Stylesheets and scripts can block rendering and loading of other |
| // resources. Fonts can block text from rendering. |
| case ResourceType::STYLESHEET: |
| case ResourceType::SCRIPT: |
| case ResourceType::FONT_RESOURCE: |
| return net::MEDIUM; |
| |
| // Sub resources, objects and media are lower priority than potentially |
| // blocking stylesheets, scripts and fonts, but are higher priority than |
| // images because if they exist they are probably more central to the page |
| // focus than images on the page. |
| case ResourceType::SUB_RESOURCE: |
| case ResourceType::OBJECT: |
| case ResourceType::MEDIA: |
| case ResourceType::WORKER: |
| case ResourceType::SHARED_WORKER: |
| return net::LOW; |
| |
| // Images are the "lowest" priority because they typically do not block |
| // downloads or rendering and most pages have some useful content without |
| // them. |
| case ResourceType::IMAGE: |
| return net::LOWEST; |
| |
| // Prefetches are at a lower priority than even LOWEST, since they |
| // are not even required for rendering of the current page. |
| case ResourceType::PREFETCH: |
| return net::IDLE; |
| |
| default: |
| // When new resource types are added, their priority must be considered. |
| NOTREACHED(); |
| return net::LOW; |
| } |
| } |
| |
| // static |
| bool ResourceDispatcherHost::is_prefetch_enabled() { |
| return is_prefetch_enabled_; |
| } |
| |
| // static |
| void ResourceDispatcherHost::set_is_prefetch_enabled(bool value) { |
| is_prefetch_enabled_ = value; |
| } |
| |
| // static |
| bool ResourceDispatcherHost::is_prefetch_enabled_ = false; |