// Copyright (c) 2010 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 "chrome/browser/renderer_host/resource_dispatcher_host.h" #include #include "base/command_line.h" #include "base/message_loop.h" #include "base/scoped_ptr.h" #include "base/shared_memory.h" #include "base/stl_util-inl.h" #include "base/time.h" #include "chrome/browser/cert_store.h" #include "chrome/browser/child_process_security_policy.h" #include "chrome/browser/cross_site_request_manager.h" #include "chrome/browser/download/download_file.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_request_manager.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/in_process_webkit/webkit_thread.h" #include "chrome/browser/login_prompt.h" #include "chrome/browser/net/chrome_url_request_context.h" #include "chrome/browser/net/url_request_tracking.h" #include "chrome/browser/plugin_service.h" #include "chrome/browser/privacy_blacklist/blacklist.h" #include "chrome/browser/privacy_blacklist/blacklist_request_info.h" #include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/async_resource_handler.h" #include "chrome/browser/renderer_host/buffered_resource_handler.h" #include "chrome/browser/renderer_host/cross_site_resource_handler.h" #include "chrome/browser/renderer_host/download_resource_handler.h" #include "chrome/browser/renderer_host/global_request_id.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/renderer_host/render_view_host_delegate.h" #include "chrome/browser/renderer_host/render_view_host_notification_task.h" #include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" #include "chrome/browser/renderer_host/resource_queue.h" #include "chrome/browser/renderer_host/resource_request_details.h" #include "chrome/browser/renderer_host/safe_browsing_resource_handler.h" #include "chrome/browser/renderer_host/save_file_resource_handler.h" #include "chrome/browser/renderer_host/socket_stream_dispatcher_host.h" #include "chrome/browser/renderer_host/sync_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/worker_host/worker_service.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" #include "chrome/common/url_constants.h" #include "net/base/auth.h" #include "net/base/cert_status_flags.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/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" // Uncomment to enable logging of request traffic. // #define LOG_RESOURCE_DISPATCHER_REQUESTS #ifdef LOG_RESOURCE_DISPATCHER_REQUESTS # define RESOURCE_LOG(stuff) LOG(INFO) << stuff #else # define RESOURCE_LOG(stuff) #endif using base::Time; using base::TimeDelta; using base::TimeTicks; // ---------------------------------------------------------------------------- // 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; ChildProcessSecurityPolicy* policy = ChildProcessSecurityPolicy::GetInstance(); // Check if the renderer is permitted to request the requested URL. if (!policy->CanRequestURL(child_id, request_data.url)) { LOG(INFO) << "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* uploads = request_data.upload_data->elements(); std::vector::const_iterator iter; for (iter = uploads->begin(); iter != uploads->end(); ++iter) { if (iter->type() == net::UploadData::TYPE_FILE && !policy->CanUploadFile(child_id, iter->file_path())) { NOTREACHED() << "Denied unauthorized upload of " << iter->file_path().value(); return false; } } } return true; } void PopulateResourceResponse(URLRequest* request, FilterPolicy::Type filter_policy, 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.filter_policy = filter_policy; 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(); appcache::AppCacheInterceptor::GetExtraResponseInfo( request, &response->response_head.appcache_id, &response->response_head.appcache_manifest_url); } } // namespace ResourceDispatcherHost::ResourceDispatcherHost() : ALLOW_THIS_IN_INITIALIZER_LIST( download_file_manager_(new DownloadFileManager(this))), download_request_manager_(new DownloadRequestManager()), ALLOW_THIS_IN_INITIALIZER_LIST( save_file_manager_(new SaveFileManager(this))), user_script_listener_(new UserScriptListener(&resource_queue_)), safe_browsing_(new SafeBrowsingService), socket_stream_dispatcher_host_(new SocketStreamDispatcherHost), 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), receiver_(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(ChromeThread::CurrentlyOn(ChromeThread::UI)); webkit_thread_->Initialize(); safe_browsing_->Initialize(); ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableFunction(&appcache::AppCacheInterceptor::EnsureRegistered)); } void ResourceDispatcherHost::Shutdown() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, new ShutdownTask(this)); } void ResourceDispatcherHost::SetRequestInfo( URLRequest* request, ResourceDispatcherHostRequestInfo* info) { request->SetUserData(NULL, info); } void ResourceDispatcherHost::OnShutdown() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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 ids; for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); iter != blocked_requests_map_.end(); ++iter) { std::pair::iterator, bool> result = ids.insert(iter->first); // We should not have duplicates. DCHECK(result.second); } for (std::set::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) || URLRequest::IsHandledURL(url)) return false; ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableFunction( &ExternalProtocolHandler::LaunchUrl, url, child_id, route_id)); handler->OnResponseCompleted(request_id, URLRequestStatus( URLRequestStatus::FAILED, net::ERR_ABORTED), std::string()); // No security info necessary. return true; } bool ResourceDispatcherHost::OnMessageReceived(const IPC::Message& message, Receiver* receiver, bool* message_was_ok) { if (!IsResourceDispatcherHostMessage(message)) { return socket_stream_dispatcher_host_->OnMessageReceived( message, receiver, message_was_ok); } *message_was_ok = true; receiver_ = receiver; 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_DataReceived_ACK, OnDataReceivedACK) 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_END_MESSAGE_MAP_EX() receiver_ = NULL; return true; } 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 = receiver_->type(); int child_id = receiver_->id(); ChromeURLRequestContext* context = static_cast( receiver_->GetRequestContext(request_id, request_data)); if (!context) { URLRequestContextGetter* context_getter = Profile::GetDefaultRequestContext(); if (context_getter) { context = static_cast( context_getter->GetURLRequestContext()); } } if (is_shutdown_ || !ShouldServiceRequest(process_type, child_id, request_data)) { URLRequestStatus status(URLRequestStatus::FAILED, net::ERR_ABORTED); if (sync_result) { SyncLoadResult result; result.status = status; ViewHostMsg_SyncLoad::WriteReplyParams(sync_result, result); receiver_->Send(sync_result); } else { // Tell the renderer that this request was disallowed. receiver_->Send(new ViewMsg_Resource_RequestComplete( route_id, request_id, status, std::string())); // No security info needed, connection was not // 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 handler; if (sync_result) { handler = new SyncResourceHandler(receiver_, request_data.url, sync_result); } else { handler = new AsyncResourceHandler(receiver_, child_id, route_id, receiver_->handle(), request_data.url, this); } if (HandleExternalProtocol(request_id, child_id, route_id, request_data.url, request_data.resource_type, handler)) { return; } // Construct the request. URLRequest* request = new 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()); request->SetExtraRequestHeaders(request_data.headers); int load_flags = request_data.load_flags; // EV certificate verification could be expensive. We don't want to spend // time performing EV certificate verification on all resources because // EV status is irrelevant to sub-frames and sub-resources. if (request_data.resource_type == ResourceType::MAIN_FRAME) load_flags |= net::LOAD_VERIFY_EV_CERT; 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 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 && Singleton::get()-> 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() && safe_browsing_->CanCheckUrl(request_data.url)) { handler = new SafeBrowsingResourceHandler(handler, child_id, route_id, request_data.resource_type, safe_browsing_, this, receiver_); } // 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.frame_origin, request_data.main_frame_origin, request_data.resource_type, upload_size, false, // is download ResourceType::IsFrame(request_data.resource_type), // allow_download request_data.host_renderer_id, request_data.host_render_view_id); ApplyExtensionMessageFilterPolicy(request_data.url, request_data.resource_type, extra_info); SetRequestInfo(request, extra_info); // Request takes ownership. chrome_browser_net::SetOriginProcessUniqueIDForRequest( request_data.origin_child_id, request); // 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); // Associate Privacy Blacklist information with the request. if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnablePrivacyBlacklists)) { request->SetUserData(&BlacklistRequestInfo::kURLRequestDataKey, new BlacklistRequestInfo(request_data.url, request_data.resource_type, context ? context->GetPrivacyBlacklist() : NULL)); } BeginRequestInternal(request); } void ResourceDispatcherHost::OnDataReceivedACK(int request_id) { DataReceivedACK(receiver_->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::OnUploadProgressACK(int request_id) { int child_id = receiver_->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(receiver_->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(receiver_->id(), request_id, has_new_first_party_for_cookies, new_first_party_for_cookies); } 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, int child_id, int route_id, 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)) { LOG(INFO) << "Denied unauthorized download request for " << url.possibly_invalid_spec(); return; } // Ensure the Chrome plugins are loaded, as they may intercept network // requests. Does nothing if they are already loaded. PluginService::GetInstance()->LoadChromePlugins(this); URLRequest* request = new URLRequest(url, this); request_id_--; scoped_refptr handler = new DownloadResourceHandler(this, child_id, route_id, request_id_, url, download_file_manager_.get(), request, true, save_info); if (safe_browsing_->enabled() && safe_browsing_->CanCheckUrl(url)) { handler = new SafeBrowsingResourceHandler(handler, child_id, route_id, ResourceType::MAIN_FRAME, safe_browsing_, this, receiver_); } if (!URLRequest::IsHandledURL(url)) { LOG(INFO) << "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 = new ResourceDispatcherHostRequestInfo(handler, ChildProcessInfo::RENDER_PROCESS, child_id, route_id, request_id_, "null", // frame_origin "null", // main_frame_origin ResourceType::SUB_RESOURCE, 0, // upload_size true, // is_download true, // allow_download -1, // Host renderer id -1); // Host render view id SetRequestInfo(request, extra_info); // Request takes ownership. chrome_browser_net::SetOriginProcessUniqueIDForRequest(child_id, request); 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, 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 handler = new SaveFileResourceHandler(child_id, route_id, url, save_file_manager_.get()); request_id_--; bool known_proto = 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; } URLRequest* request = new 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 = new ResourceDispatcherHostRequestInfo(handler, ChildProcessInfo::RENDER_PROCESS, child_id, route_id, request_id_, "null", // frame_origin "null", // main_frame_origin ResourceType::SUB_RESOURCE, 0, // upload_size false, // is_download false, // allow_download -1, // Host renderer id -1); // Host render view id SetRequestInfo(request, extra_info); // Request takes ownership. chrome_browser_net::SetOriginProcessUniqueIDForRequest(child_id, request); 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()) { 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? 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); RESOURCE_LOG("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) { socket_stream_dispatcher_host_->CancelRequestsForProcess(child_id); CancelRequestsForRoute(child_id, -1 /* cancel all */); } 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 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 URLRequest that has // exclusive (write) access to an HTTP cache entry may unblock another // URLRequest that needs exclusive access to the same cache entry, and // that 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(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 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::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 && info->login_handler()) info->login_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(); } // URLRequest::Delegate ------------------------------------------------------- void ResourceDispatcherHost::OnReceivedRedirect(URLRequest* request, const GURL& new_url, bool* defer_redirect) { RESOURCE_LOG("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)) { LOG(INFO) << "Denied unauthorized request for " << new_url.possibly_invalid_spec(); // Tell the renderer that this request was disallowed. CancelRequest(info->child_id(), info->request_id(), 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 response = new ResourceResponse; PopulateResourceResponse(request, info->filter_policy(), response); if (!info->resource_handler()->OnRequestRedirected(info->request_id(), new_url, response, defer_redirect)) CancelRequest(info->child_id(), info->request_id(), false); } void ResourceDispatcherHost::OnAuthRequired( URLRequest* request, net::AuthChallengeInfo* auth_info) { // 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 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( 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( URLRequest* request, int cert_error, net::X509Certificate* cert) { DCHECK(request); SSLManager::OnSSLCertificateError(this, request, cert_error, cert); } void ResourceDispatcherHost::OnSetCookieBlocked(URLRequest* request) { RESOURCE_LOG("OnSetCookieBlocked: " << request->url().spec()); int render_process_id, render_view_id; if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) return; CallRenderViewHostResourceDelegate( render_process_id, render_view_id, &RenderViewHostDelegate::Resource::OnContentBlocked, CONTENT_SETTINGS_TYPE_COOKIES); } void ResourceDispatcherHost::OnResponseStarted(URLRequest* request) { RESOURCE_LOG("OnResponseStarted: " << request->url().spec()); ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); if (PauseRequestIfNeeded(info)) { RESOURCE_LOG("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)) { CancelRequest(info->child_id(), info->request_id(), false); } else { // Check if the handler paused the request in their OnResponseStarted. if (PauseRequestIfNeeded(info)) { RESOURCE_LOG("OnResponseStarted pausing2: " << request->url().spec()); return; } StartReading(request); } } else { OnResponseCompleted(request); } } bool ResourceDispatcherHost::CompleteResponseStarted(URLRequest* request) { ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); scoped_refptr response = new ResourceResponse; PopulateResourceResponse(request, info->filter_policy(), response); if (request->ssl_info().cert) { int cert_id = CertStore::GetSharedInstance()->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); } else { // We should not have any SSL state. DCHECK(!request->ssl_info().cert_status && (request->ssl_info().security_bits == -1 || request->ssl_info().security_bits == 0)); } 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; } RESOURCE_LOG("CancelRequest: " << i->second->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(i->second); 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); } i->second->Cancel(); } // 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( 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().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& uploads = request->get_upload()->elements(); std::vector::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(element_size); } } } #endif // Note that this expression will typically be dominated by: // |kAvgBytesPerOutstandingRequest|. return kAvgBytesPerOutstandingRequest + strings_cost + upload_cost; } void ResourceDispatcherHost::BeginRequestInternal(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 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 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 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)) { CancelRequest(info->child_id(), info->request_id(), false); return; } if (!defer_start) InsertIntoResourceQueue(request, *info); } void ResourceDispatcherHost::InsertIntoResourceQueue( 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; URLRequest* request = i->second; ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); if (!info->is_paused()) return; RESOURCE_LOG("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(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(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(URLRequest* request, int bytes_read) { DCHECK(request); RESOURCE_LOG("OnReadCompleted: " << request->url().spec()); ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); if (PauseRequestIfNeeded(info)) { info->set_paused_read_bytes(bytes_read); RESOURCE_LOG("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); RESOURCE_LOG("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(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)) { CancelRequest(info->child_id(), info->request_id(), false); // Our callers assume |request| is valid after we return. DCHECK(pending_requests_.find( GlobalRequestID(info->child_id(), info->request_id())) != pending_requests_.end()); return false; } return *bytes_read != 0; } void ResourceDispatcherHost::OnResponseCompleted(URLRequest* request) { RESOURCE_LOG("OnResponseCompleted: " << request->url().spec()); ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); std::string security_info; const net::SSLInfo& ssl_info = request->ssl_info(); if (ssl_info.cert != NULL) { int cert_id = CertStore::GetSharedInstance()->StoreCert(ssl_info.cert, info->child_id()); security_info = SSLManager::SerializeSecurityInfo(cert_id, ssl_info.cert_status, ssl_info.security_bits); } 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( URLRequest* request) { // Avoid writing this function twice by casting the cosnt version. const URLRequest* const_request = request; return const_cast( InfoForRequest(const_request)); } // static const ResourceDispatcherHostRequestInfo* ResourceDispatcherHost::InfoForRequest( const URLRequest* request) { const ResourceDispatcherHostRequestInfo* info = static_cast( request->GetUserData(NULL)); DLOG_IF(WARNING, !info) << "Request doesn't seem to have our data"; return info; } // static bool ResourceDispatcherHost::RenderViewForRequest(const 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) { const WorkerProcessHost::WorkerInstance* worker_instance = WorkerService::GetInstance()->FindWorkerInstance(info->child_id()); if (!worker_instance) { *render_process_host_id = -1; *render_view_host_id = -1; return false; } DCHECK(!worker_instance->worker_document_set()->IsEmpty()); const WorkerDocumentSet::DocumentInfoSet& parents = worker_instance->worker_document_set()->documents(); // Need to display some related UI for this network request - pick an // arbitrary parent to do so. *render_process_host_id = parents.begin()->renderer_id(); *render_view_host_id = parents.begin()->render_view_route_id(); } 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); } URLRequest* ResourceDispatcherHost::GetURLRequest( const GlobalRequestID& request_id) const { // This should be running in the IO loop. DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); PendingRequestList::const_iterator i = pending_requests_.find(request_id); if (i == pending_requests_.end()) return NULL; return i->second; } static int GetCertID(URLRequest* request, int child_id) { if (request->ssl_info().cert) { return CertStore::GetSharedInstance()->StoreCert(request->ssl_info().cert, child_id); } // If there is no SSL info attached to this request, we must either be a non // secure request, or the request has been canceled or failed (before the SSL // info was populated), or the response is an error (we have seen 403, 404, // and 501) made up by the proxy. DCHECK(!request->url().SchemeIsSecure() || (request->status().status() == URLRequestStatus::CANCELED) || (request->status().status() == URLRequestStatus::FAILED) || ((request->response_headers()->response_code() >= 400) && (request->response_headers()->response_code() <= 599))); return 0; } void ResourceDispatcherHost::NotifyResponseStarted(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. CallRenderViewHostResourceDelegate( render_process_id, render_view_id, &RenderViewHostDelegate::Resource::DidStartReceivingResourceResponse, ResourceRequestDetails(request, GetCertID(request, child_id))); } void ResourceDispatcherHost::NotifyResponseCompleted(URLRequest* request, int child_id) { // Notify the observers on the IO thread. FOR_EACH_OBSERVER(Observer, observer_list_, OnResponseCompleted(this, request)); } void ResourceDispatcherHost::NotifyReceivedRedirect(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. CallRenderViewHostResourceDelegate( render_process_id, render_view_id, &RenderViewHostDelegate::Resource::DidRedirectResource, ResourceRedirectDetails(request, GetCertID(request, child_id), new_url)); } 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, 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, uint64> largest_upload_size; for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { 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 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) { 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 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); ChromeThread::PostTask(ChromeThread::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, 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 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(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. 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; } // static bool ResourceDispatcherHost::IsResourceDispatcherHostMessage( const IPC::Message& message) { switch (message.type()) { case ViewHostMsg_RequestResource::ID: case ViewHostMsg_CancelRequest::ID: case ViewHostMsg_FollowRedirect::ID: case ViewHostMsg_ClosePage_ACK::ID: case ViewHostMsg_DataReceived_ACK::ID: case ViewHostMsg_DownloadProgress_ACK::ID: case ViewHostMsg_UploadProgress_ACK::ID: case ViewHostMsg_SyncLoad::ID: return true; default: break; } return false; } // static void ResourceDispatcherHost::ApplyExtensionMessageFilterPolicy( const GURL& url, const ResourceType::Type& resource_type, ResourceDispatcherHostRequestInfo* request_info) { // Apply filter only to chrome extension css files that don't have // security filter already set. if (url.SchemeIs(chrome::kExtensionScheme) && request_info->filter_policy() == FilterPolicy::DONT_FILTER && resource_type == ResourceType::STYLESHEET) { request_info->set_filter_policy(FilterPolicy::FILTER_EXTENSION_MESSAGES); } } // 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: 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; default: // When new resource types are added, their priority must be considered. NOTREACHED(); return net::LOW; } }