| // Copyright (c) 2006-2008 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 <vector> |
| |
| #include "base/message_loop.h" |
| #include "base/scoped_ptr.h" |
| #include "base/time.h" |
| #include "chrome/browser/cert_store.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/external_protocol_handler.h" |
| #include "chrome/browser/plugin_service.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/media_resource_handler.h" |
| #include "chrome/browser/renderer_host/render_view_host.h" |
| #include "chrome/browser/renderer_host/renderer_security_policy.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/sync_resource_handler.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "chrome/browser/tab_contents/tab_contents.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/render_messages.h" |
| #include "chrome/common/stl_util-inl.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/url_request/url_request.h" |
| #include "webkit/glue/webappcachecontext.h" |
| |
| // TODO(port): Move these includes to the above section when porting is done. |
| #if defined(OS_POSIX) |
| #include "chrome/common/temp_scaffolding_stubs.h" |
| #elif defined(OS_WIN) |
| #include "chrome/browser/login_prompt.h" |
| #include "chrome/browser/renderer_host/render_view_host_delegate.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #endif |
| |
| // 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; |
| |
| // ---------------------------------------------------------------------------- |
| |
| bool ResourceDispatcherHost::g_is_http_prioritization_enabled = true; |
| |
| // 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 restrive the request URL or |
| // if the renderer is attempting to upload an unauthorized file. |
| bool ShouldServiceRequest(ChildProcessInfo::ProcessType process_type, |
| int process_id, |
| const ViewHostMsg_Resource_Request& request_data) { |
| if (process_type != ChildProcessInfo::RENDER_PROCESS) |
| return true; |
| |
| RendererSecurityPolicy* policy = RendererSecurityPolicy::GetInstance(); |
| |
| // Check if the renderer is permitted to request the requested URL. |
| if (!policy->CanRequestURL(process_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<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->CanUploadFile(process_id, iter->file_path())) { |
| NOTREACHED() << "Denied unauthorized upload of " |
| << iter->file_path().value(); |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| ResourceDispatcherHost::ResourceDispatcherHost(MessageLoop* io_loop) |
| : ui_loop_(MessageLoop::current()), |
| io_loop_(io_loop), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| download_file_manager_(new DownloadFileManager(ui_loop_, this))), |
| download_request_manager_(new DownloadRequestManager(io_loop, ui_loop_)), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| save_file_manager_(new SaveFileManager(ui_loop_, io_loop, this))), |
| safe_browsing_(new SafeBrowsingService), |
| request_id_(-1), |
| plugin_service_(PluginService::GetInstance()), |
| ALLOW_THIS_IN_INITIALIZER_LIST(method_runner_(this)), |
| is_shutdown_(false), |
| max_outstanding_requests_cost_per_process_( |
| kMaxOutstandingRequestsCostPerProcess), |
| receiver_(NULL) { |
| } |
| |
| ResourceDispatcherHost::~ResourceDispatcherHost() { |
| AsyncResourceHandler::GlobalCleanup(); |
| STLDeleteValues(&pending_requests_); |
| |
| // 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); |
| } |
| } |
| |
| void ResourceDispatcherHost::Initialize() { |
| DCHECK(MessageLoop::current() == ui_loop_); |
| download_file_manager_->Initialize(); |
| safe_browsing_->Initialize(io_loop_); |
| } |
| |
| void ResourceDispatcherHost::Shutdown() { |
| DCHECK(MessageLoop::current() == ui_loop_); |
| io_loop_->PostTask(FROM_HERE, new ShutdownTask(this)); |
| } |
| |
| void ResourceDispatcherHost::OnShutdown() { |
| DCHECK(MessageLoop::current() == io_loop_); |
| is_shutdown_ = true; |
| 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(); |
| } |
| |
| bool ResourceDispatcherHost::HandleExternalProtocol(int request_id, |
| int process_id, |
| int tab_contents_id, |
| const GURL& url, |
| ResourceType::Type type, |
| ResourceHandler* handler) { |
| if (!ResourceType::IsFrame(type) || URLRequest::IsHandledURL(url)) |
| return false; |
| |
| ui_loop_->PostTask(FROM_HERE, NewRunnableFunction( |
| &ExternalProtocolHandler::LaunchUrl, url, process_id, tab_contents_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 false; |
| |
| *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_DownloadProgress_ACK, OnDownloadProgressACK) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_UploadProgress_ACK, OnUploadProgressACK) |
| IPC_MESSAGE_HANDLER(ViewHostMsg_CancelRequest, OnCancelRequest) |
| 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, 0); |
| } |
| |
| 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 process_id = receiver_->GetProcessId(); |
| URLRequestContext* context = |
| receiver_->GetRequestContext(request_id, request_data); |
| if (!context) |
| context = Profile::GetDefaultRequestContext(); |
| |
| if (is_shutdown_ || |
| !ShouldServiceRequest(process_type, process_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. |
| plugin_service_->LoadChromePlugins(this); |
| |
| // Construct the event handler. |
| scoped_refptr<ResourceHandler> handler; |
| if (sync_result) { |
| handler = new SyncResourceHandler(receiver_, request_data.url, sync_result); |
| } else { |
| handler = new AsyncResourceHandler(receiver_, |
| process_id, |
| route_id, |
| receiver_->handle(), |
| request_data.url, |
| this); |
| // If the resource type is ResourceType::MEDIA and LOAD_ENABLE_DOWNLOAD_FILE |
| // is enabled we insert a media resource handler. |
| if (request_data.resource_type == ResourceType::MEDIA && |
| (request_data.load_flags & net::LOAD_ENABLE_DOWNLOAD_FILE)) { |
| handler = new MediaResourceHandler(handler, |
| receiver_, |
| process_id, |
| route_id, |
| receiver_->handle(), |
| this); |
| } |
| } |
| |
| if (HandleExternalProtocol(request_id, process_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_policy_url(request_data.policy_url); |
| request->set_referrer(request_data.referrer.spec()); |
| request->SetExtraRequestHeaders(request_data.headers); |
| request->set_load_flags(request_data.load_flags); |
| request->set_context(context); |
| request->set_origin_pid(request_data.origin_pid); |
| |
| if (IsHttpPrioritizationEnabled()) { |
| // If the request is for the top level page or a frame/iframe, then we |
| // should prioritize it higher than other resource types. Currently, we |
| // just use priorities 1 and 0. |
| if (request_data.resource_type == ResourceType::MAIN_FRAME || |
| request_data.resource_type == ResourceType::SUB_FRAME) { |
| request->set_priority(1); |
| } else { |
| request->set_priority(0); |
| } |
| } |
| |
| // 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. |
| if (request_data.resource_type == ResourceType::MAIN_FRAME && |
| process_type == ChildProcessInfo::RENDER_PROCESS && |
| Singleton<CrossSiteRequestManager>::get()-> |
| HasPendingCrossSiteRequest(process_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, |
| process_id, |
| route_id, |
| this); |
| } |
| |
| if (safe_browsing_->enabled() && |
| safe_browsing_->CanCheckUrl(request_data.url)) { |
| handler = new SafeBrowsingResourceHandler(handler, |
| process_id, |
| route_id, |
| request_data.url, |
| request_data.resource_type, |
| safe_browsing_, |
| this, |
| receiver_); |
| } |
| |
| // Insert a buffered event handler before the actual one. |
| handler = new BufferedResourceHandler(handler, this, request); |
| |
| // Make extra info and read footer (contains request ID). |
| ExtraRequestInfo* extra_info = |
| new ExtraRequestInfo(handler, |
| process_type, |
| process_id, |
| route_id, |
| request_id, |
| request_data.frame_origin, |
| request_data.main_frame_origin, |
| request_data.resource_type, |
| upload_size); |
| extra_info->allow_download = |
| ResourceType::IsFrame(request_data.resource_type); |
| SetExtraInfoForRequest(request, extra_info); // request takes ownership |
| |
| BeginRequestInternal(request); |
| } |
| |
| void ResourceDispatcherHost::OnDataReceivedACK(int request_id) { |
| DataReceivedACK(receiver_->GetProcessId(), request_id); |
| } |
| |
| void ResourceDispatcherHost::DataReceivedACK(int process_id, int request_id) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(process_id, request_id)); |
| if (i == pending_requests_.end()) |
| return; |
| |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| |
| // Decrement the number of pending data messages. |
| info->pending_data_count--; |
| |
| // 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->pending_data_count--; |
| |
| // Resume the request. |
| PauseRequest(process_id, request_id, false); |
| } |
| } |
| |
| void ResourceDispatcherHost::OnDownloadProgressACK(int request_id) { |
| // TODO(hclam): do something to help rate limiting the message. |
| } |
| |
| void ResourceDispatcherHost::OnUploadProgressACK(int request_id) { |
| int process_id = receiver_->GetProcessId(); |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(process_id, request_id)); |
| if (i == pending_requests_.end()) |
| return; |
| |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| info->waiting_for_upload_progress_ack = false; |
| } |
| |
| void ResourceDispatcherHost::OnCancelRequest(int request_id) { |
| CancelRequest(receiver_->GetProcessId(), request_id, true, true); |
| } |
| |
| void ResourceDispatcherHost::OnClosePageACK(int new_render_process_host_id, |
| int new_request_id) { |
| GlobalRequestID global_id(new_render_process_host_id, new_request_id); |
| PendingRequestList::iterator i = pending_requests_.find(global_id); |
| if (i == pending_requests_.end()) { |
| // If there are no matching pending requests, then this is not a |
| // cross-site navigation and we are just closing the tab/browser. |
| ui_loop_->PostTask(FROM_HERE, NewRunnableFunction( |
| &RenderViewHost::ClosePageIgnoringUnloadEvents, |
| new_render_process_host_id, |
| new_request_id)); |
| return; |
| } |
| |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| if (info->cross_site_handler) { |
| info->cross_site_handler->ResumeResponse(); |
| } |
| } |
| |
| // We are explicitly forcing the download of 'url'. |
| void ResourceDispatcherHost::BeginDownload(const GURL& url, |
| const GURL& referrer, |
| int process_id, |
| int route_id, |
| URLRequestContext* request_context) { |
| if (is_shutdown_) |
| return; |
| |
| // Check if the renderer is permitted to request the requested URL. |
| if (!RendererSecurityPolicy::GetInstance()-> |
| CanRequestURL(process_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. |
| plugin_service_->LoadChromePlugins(this); |
| URLRequest* request = new URLRequest(url, this); |
| |
| request_id_--; |
| |
| scoped_refptr<ResourceHandler> handler = |
| new DownloadResourceHandler(this, |
| process_id, |
| route_id, |
| request_id_, |
| url, |
| download_file_manager_.get(), |
| request, |
| true); |
| |
| |
| if (safe_browsing_->enabled() && safe_browsing_->CanCheckUrl(url)) { |
| handler = new SafeBrowsingResourceHandler(handler, |
| process_id, |
| route_id, |
| url, |
| ResourceType::MAIN_FRAME, |
| safe_browsing_, |
| this, |
| receiver_); |
| } |
| |
| bool known_proto = URLRequest::IsHandledURL(url); |
| if (!known_proto) { |
| CHECK(false); |
| } |
| |
| request->set_method("GET"); |
| request->set_referrer(referrer.spec()); |
| request->set_context(request_context); |
| request->set_load_flags(request->load_flags() | |
| net::LOAD_IS_DOWNLOAD); |
| |
| ExtraRequestInfo* extra_info = |
| new ExtraRequestInfo(handler, |
| ChildProcessInfo::RENDER_PROCESS, |
| process_id, |
| route_id, |
| request_id_, |
| "null", // frame_origin |
| "null", // main_frame_origin |
| ResourceType::SUB_RESOURCE, |
| 0 /* upload_size */); |
| extra_info->allow_download = true; |
| extra_info->is_download = true; |
| SetExtraInfoForRequest(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 process_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. |
| plugin_service_->LoadChromePlugins(this); |
| |
| scoped_refptr<ResourceHandler> handler = |
| new SaveFileResourceHandler(process_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(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_ONLY_FROM_CACHE); |
| request->set_context(request_context); |
| |
| ExtraRequestInfo* extra_info = |
| new ExtraRequestInfo(handler, |
| ChildProcessInfo::RENDER_PROCESS, |
| process_id, |
| route_id, |
| request_id_, |
| "null", // frame_origin |
| "null", // main_frame_origin |
| ResourceType::SUB_RESOURCE, |
| 0 /* upload_size */); |
| // Just saving some resources we need, disallow downloading. |
| extra_info->allow_download = false; |
| extra_info->is_download = false; |
| SetExtraInfoForRequest(request, extra_info); // request takes ownership |
| |
| BeginRequestInternal(request); |
| } |
| |
| void ResourceDispatcherHost::CancelRequest(int process_id, |
| int request_id, |
| bool from_renderer) { |
| CancelRequest(process_id, request_id, from_renderer, true); |
| } |
| |
| void ResourceDispatcherHost::CancelRequest(int process_id, |
| int request_id, |
| bool from_renderer, |
| bool allow_delete) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(process_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; |
| } |
| |
| // 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. |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| if (!from_renderer || !info->is_download) { |
| if (info->login_handler) { |
| info->login_handler->OnRequestCancelled(); |
| info->login_handler = NULL; |
| } |
| if (!i->second->is_pending() && allow_delete) { |
| // No io is pending, canceling the request won't notify us of anything, |
| // so we explicitly remove it. |
| // TODO: removing the request in this manner means we're not notifying |
| // anyone. We need make sure the event handlers and others are notified |
| // so that everything is cleaned up properly. |
| RemovePendingRequest(info->process_id, info->request_id); |
| } else { |
| 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. |
| } |
| |
| bool ResourceDispatcherHost::WillSendData(int process_id, |
| int request_id) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(process_id, request_id)); |
| if (i == pending_requests_.end()) { |
| NOTREACHED() << L"WillSendData for invalid request"; |
| return false; |
| } |
| |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| |
| info->pending_data_count++; |
| 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(process_id, request_id, true); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ResourceDispatcherHost::PauseRequest(int process_id, |
| int request_id, |
| bool pause) { |
| GlobalRequestID global_id(process_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; |
| } |
| |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| |
| int pause_count = info->pause_count + (pause ? 1 : -1); |
| if (pause_count < 0) { |
| NOTREACHED(); // Unbalanced call to pause. |
| return; |
| } |
| info->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 process_id) const { |
| OutstandingRequestsMemoryCostMap::const_iterator entry = |
| outstanding_requests_memory_cost_map_.find(process_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 process_id) { |
| CancelRequestsForRoute(process_id, -1 /* cancel all */); |
| } |
| |
| void ResourceDispatcherHost::CancelRequestsForRoute( |
| int process_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.process_id == process_id) { |
| ExtraRequestInfo* info = ExtraInfoForRequest(i->second); |
| if (!info->is_download && (route_id == -1 || |
| route_id == info->route_id)) { |
| matching_requests.push_back( |
| GlobalRequestID(process_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<int, int>(process_id, route_id)) != |
| blocked_requests_map_.end()) { |
| CancelBlockedRequestsForRoute(process_id, route_id); |
| } |
| } else { |
| // We have to do all render views for the process |process_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 == process_id) |
| route_ids.insert(iter->first.second); |
| } |
| for (std::set<int>::const_iterator iter = route_ids.begin(); |
| iter != route_ids.end(); ++iter) { |
| CancelBlockedRequestsForRoute(process_id, *iter); |
| } |
| } |
| } |
| |
| // Cancels the request and removes it from the list. |
| void ResourceDispatcherHost::RemovePendingRequest(int process_id, |
| int request_id) { |
| PendingRequestList::iterator i = pending_requests_.find( |
| GlobalRequestID(process_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) { |
| ExtraRequestInfo* info = ExtraInfoForRequest(iter->second); |
| |
| // Remove the memory credit that we added when pushing the request onto |
| // the pending list. |
| IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost, |
| info->process_id); |
| |
| // Notify the login handler that this request object is going away. |
| if (info && info->login_handler) |
| info->login_handler->OnRequestCancelled(); |
| |
| 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) { |
| RESOURCE_LOG("OnReceivedRedirect: " << request->url().spec()); |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| |
| DCHECK(request->status().is_success()); |
| |
| if (info->process_type == ChildProcessInfo::RENDER_PROCESS && |
| !RendererSecurityPolicy::GetInstance()-> |
| CanRequestURL(info->process_id, new_url)) { |
| LOG(INFO) << "Denied unauthorized request for " << |
| new_url.possibly_invalid_spec(); |
| |
| // Tell the renderer that this request was disallowed. |
| CancelRequest(info->process_id, info->request_id, false); |
| return; |
| } |
| |
| NofityReceivedRedirect(request, info->process_id, new_url); |
| |
| if (HandleExternalProtocol(info->request_id, info->process_id, |
| info->route_id, new_url, |
| info->resource_type, info->resource_handler)) { |
| // The request is complete so we can remove it. |
| RemovePendingRequest(info->process_id, info->request_id); |
| return; |
| } |
| |
| if (!info->resource_handler->OnRequestRedirected(info->request_id, new_url)) |
| CancelRequest(info->process_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. |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| DCHECK(!info->login_handler) << |
| "OnAuthRequired called with login_handler pending"; |
| info->login_handler = CreateLoginPrompt(auth_info, request, ui_loop_); |
| } |
| |
| void ResourceDispatcherHost::OnSSLCertificateError( |
| URLRequest* request, |
| int cert_error, |
| net::X509Certificate* cert) { |
| DCHECK(request); |
| SSLManager::OnSSLCertificateError(this, request, cert_error, cert, ui_loop_); |
| } |
| |
| void ResourceDispatcherHost::OnResponseStarted(URLRequest* request) { |
| RESOURCE_LOG("OnResponseStarted: " << request->url().spec()); |
| ExtraRequestInfo* info = ExtraInfoForRequest(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->waiting_for_upload_progress_ack = false; |
| MaybeUpdateUploadProgress(info, request); |
| |
| if (!CompleteResponseStarted(request)) { |
| CancelRequest(info->process_id, info->request_id, false); |
| } else { |
| // Start reading. |
| int bytes_read = 0; |
| if (Read(request, &bytes_read)) { |
| OnReadCompleted(request, bytes_read); |
| } else if (!request->status().is_io_pending()) { |
| DCHECK(!info->is_paused); |
| // If the error is not an IO pending, then we're done reading. |
| OnResponseCompleted(request); |
| } |
| } |
| } else { |
| OnResponseCompleted(request); |
| } |
| } |
| |
| bool ResourceDispatcherHost::CompleteResponseStarted(URLRequest* request) { |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| |
| scoped_refptr<ResourceResponse> response(new ResourceResponse); |
| |
| 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 = info->filter_policy; |
| response->response_head.content_length = request->GetExpectedContentSize(); |
| response->response_head.app_cache_id = WebAppCacheContext::kNoAppCacheId; |
| request->GetMimeType(&response->response_head.mime_type); |
| |
| // Make sure we don't get a file handle if LOAD_ENABLE_DOWNLOAD_FILE is not |
| // set. |
| DCHECK((request->load_flags() & net::LOAD_ENABLE_DOWNLOAD_FILE) || |
| request->response_data_file() == base::kInvalidPlatformFileValue); |
| #if defined(OS_POSIX) |
| response->response_head.response_data_file.fd = request->response_data_file(); |
| #elif defined(OS_WIN) |
| response->response_head.response_data_file = request->response_data_file(); |
| #endif |
| |
| if (request->ssl_info().cert) { |
| int cert_id = |
| CertStore::GetSharedInstance()->StoreCert( |
| request->ssl_info().cert, |
| info->process_id); |
| int cert_status = request->ssl_info().cert_status; |
| // 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. So we call |
| // IsEV here rather than in the network layer because the network layer |
| // doesn't know the resource type. |
| if (info->resource_type == ResourceType::MAIN_FRAME && |
| request->ssl_info().cert->IsEV(cert_status)) |
| cert_status |= net::CERT_STATUS_IS_EV; |
| |
| response->response_head.security_info = |
| SSLManager::SerializeSecurityInfo(cert_id, |
| 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->process_id); |
| return info->resource_handler->OnResponseStarted(info->request_id, |
| response.get()); |
| } |
| |
| int ResourceDispatcherHost::IncrementOutstandingRequestsMemoryCost( |
| int cost, int process_id) { |
| // Retrieve the previous value (defaulting to 0 if not found). |
| OutstandingRequestsMemoryCostMap::iterator prev_entry = |
| outstanding_requests_memory_cost_map_.find(process_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_[process_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<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(URLRequest* request) { |
| DCHECK(!request->is_pending()); |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| |
| // Add the memory estimate that starting this request will consume. |
| info->memory_cost = CalculateApproximateMemoryCost(request); |
| int memory_cost = IncrementOutstandingRequestsMemoryCost( |
| info->memory_cost, |
| info->process_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 "CancelWithError()" 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->process_id, info->request_id); |
| pending_requests_[global_id] = request; |
| OnResponseCompleted(request); |
| return; |
| } |
| |
| std::pair<int, int> pair_id(info->process_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->process_id, info->request_id); |
| pending_requests_[global_id] = request; |
| if (!SSLManager::ShouldStartRequest(this, request, ui_loop_)) { |
| // The SSLManager has told us that we shouldn't start the request yet. The |
| // SSLManager will potentially change the request (potentially to indicate |
| // its content should be filtered) and start it itself. |
| return; |
| } |
| request->Start(); |
| |
| // 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); |
| } |
| } |
| |
| // This test mirrors the decision that WebKit makes in |
| // WebFrameLoaderClient::dispatchDecidePolicyForMIMEType. |
| // static. |
| bool ResourceDispatcherHost::ShouldDownload( |
| const std::string& mime_type, const std::string& content_disposition) { |
| std::string type = StringToLowerASCII(mime_type); |
| std::string disposition = StringToLowerASCII(content_disposition); |
| |
| // First, examine content-disposition. |
| if (!disposition.empty()) { |
| bool should_download = true; |
| |
| // Some broken sites just send ... |
| // Content-Disposition: ; filename="file" |
| // ... screen those out here. |
| if (disposition[0] == ';') |
| should_download = false; |
| |
| if (disposition.compare(0, 6, "inline") == 0) |
| should_download = false; |
| |
| // Some broken sites just send ... |
| // Content-Disposition: filename="file" |
| // ... without a disposition token... Screen those out. |
| if (disposition.compare(0, 8, "filename") == 0) |
| should_download = false; |
| |
| // Also in use is Content-Disposition: name="file" |
| if (disposition.compare(0, 4, "name") == 0) |
| should_download = false; |
| |
| // We have a content-disposition of "attachment" or unknown. |
| // RFC 2183, section 2.8 says that an unknown disposition |
| // value should be treated as "attachment". |
| if (should_download) |
| return true; |
| } |
| |
| // MIME type checking. |
| if (net::IsSupportedMimeType(type)) |
| return false; |
| |
| // Finally, check the plugin service. |
| bool allow_wildcard = false; |
| return !plugin_service_->HavePluginFor(type, allow_wildcard); |
| } |
| |
| bool ResourceDispatcherHost::PauseRequestIfNeeded(ExtraRequestInfo* info) { |
| if (info->pause_count > 0) |
| info->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; |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| if (!info->is_paused) |
| return; |
| |
| RESOURCE_LOG("Resuming: " << i->second->url().spec()); |
| |
| info->is_paused = false; |
| |
| if (info->has_started_reading) |
| OnReadCompleted(i->second, info->paused_read_bytes); |
| else |
| OnResponseStarted(i->second); |
| } |
| |
| bool ResourceDispatcherHost::Read(URLRequest* request, int* bytes_read) { |
| ExtraRequestInfo* info = ExtraInfoForRequest(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->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()); |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| if (PauseRequestIfNeeded(info)) { |
| info->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->paused_read_bytes = bytes_read; |
| info->is_paused = true; |
| GlobalRequestID id(info->process_id, info->request_id); |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| method_runner_.NewRunnableMethod( |
| &ResourceDispatcherHost::ResumeRequest, id)); |
| return; |
| } |
| } |
| } |
| |
| if (PauseRequestIfNeeded(info)) { |
| info->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->status().is_success()) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| |
| if (!info->resource_handler->OnReadCompleted(info->request_id, bytes_read)) { |
| // Pass in false as the last arg to indicate we don't want |request| |
| // deleted. We do this as callers of us assume |request| is valid after we |
| // return. |
| CancelRequest(info->process_id, info->request_id, false, false); |
| return false; |
| } |
| |
| return *bytes_read != 0; |
| } |
| |
| void ResourceDispatcherHost::OnResponseCompleted(URLRequest* request) { |
| RESOURCE_LOG("OnResponseCompleted: " << request->url().spec()); |
| ExtraRequestInfo* info = ExtraInfoForRequest(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->process_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->process_id); |
| |
| // The request is complete so we can remove it. |
| RemovePendingRequest(info->process_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. |
| } |
| |
| void ResourceDispatcherHost::AddObserver(Observer* obs) { |
| observer_list_.AddObserver(obs); |
| } |
| |
| void ResourceDispatcherHost::RemoveObserver(Observer* obs) { |
| observer_list_.RemoveObserver(obs); |
| } |
| |
| URLRequest* ResourceDispatcherHost::GetURLRequest( |
| GlobalRequestID request_id) const { |
| // This should be running in the IO loop. io_loop_ can be NULL during the |
| // unit_tests. |
| DCHECK(MessageLoop::current() == io_loop_ && io_loop_); |
| |
| PendingRequestList::const_iterator i = pending_requests_.find(request_id); |
| if (i == pending_requests_.end()) |
| return NULL; |
| |
| return i->second; |
| } |
| |
| // A NotificationTask proxies a resource dispatcher notification from the IO |
| // thread to the UI thread. It should be constructed on the IO thread and run |
| // in the UI thread. Takes ownership of |details|. |
| class NotificationTask : public Task { |
| public: |
| NotificationTask(NotificationType type, |
| URLRequest* request, |
| ResourceRequestDetails* details) |
| : type_(type), |
| details_(details) { |
| if (!tab_util::GetTabContentsID(request, &process_id_, &tab_contents_id_)) |
| NOTREACHED(); |
| } |
| |
| void Run() { |
| // Find the tab associated with this request. |
| TabContents* tab_contents = |
| tab_util::GetTabContentsByID(process_id_, tab_contents_id_); |
| |
| if (tab_contents) { |
| // Issue the notification. |
| NotificationService::current()->Notify( |
| type_, |
| Source<NavigationController>(&tab_contents->controller()), |
| Details<ResourceRequestDetails>(details_.get())); |
| } |
| } |
| |
| private: |
| // These IDs let us find the correct tab on the UI thread. |
| int process_id_; |
| int tab_contents_id_; |
| |
| // The type and details of the notification. |
| NotificationType type_; |
| scoped_ptr<ResourceRequestDetails> details_; |
| }; |
| |
| static int GetCertID(URLRequest* request, int process_id) { |
| if (request->ssl_info().cert) { |
| return CertStore::GetSharedInstance()->StoreCert(request->ssl_info().cert, |
| process_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 process_id) { |
| // Notify the observers on the IO thread. |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnRequestStarted(this, request)); |
| |
| // Notify the observers on the UI thread. |
| ui_loop_->PostTask(FROM_HERE, |
| new NotificationTask(NotificationType::RESOURCE_RESPONSE_STARTED, request, |
| new ResourceRequestDetails(request, |
| GetCertID(request, process_id)))); |
| } |
| |
| void ResourceDispatcherHost::NotifyResponseCompleted( |
| URLRequest* request, |
| int process_id) { |
| // Notify the observers on the IO thread. |
| FOR_EACH_OBSERVER(Observer, observer_list_, |
| OnResponseCompleted(this, request)); |
| |
| // Notify the observers on the UI thread. |
| ui_loop_->PostTask(FROM_HERE, |
| new NotificationTask(NotificationType::RESOURCE_RESPONSE_COMPLETED, |
| request, |
| new ResourceRequestDetails(request, |
| GetCertID(request, process_id)))); |
| } |
| |
| void ResourceDispatcherHost::NofityReceivedRedirect(URLRequest* request, |
| int process_id, |
| const GURL& new_url) { |
| // Notify the observers on the IO thread. |
| FOR_EACH_OBSERVER(Observer, observer_list_, |
| OnReceivedRedirect(this, request, new_url)); |
| |
| int cert_id = GetCertID(request, process_id); |
| |
| // Notify the observers on the UI thread. |
| ui_loop_->PostTask(FROM_HERE, |
| new NotificationTask(NotificationType::RESOURCE_RECEIVED_REDIRECT, |
| request, |
| new ResourceRedirectDetails(request, |
| cert_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; |
| }; |
| |
| // 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); |
| } |
| } |
| 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; |
| for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { |
| URLRequest* request = i->second; |
| net::LoadState load_state = request->GetLoadState(); |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| |
| // We also poll for upload progress on this timer and send upload |
| // progress ipc messages to the plugin process. |
| MaybeUpdateUploadProgress(info, request); |
| |
| if (info->last_load_state != load_state) { |
| info->last_load_state = load_state; |
| |
| std::pair<int, int> key(info->process_id, info->route_id); |
| 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; |
| } |
| } |
| |
| if (info_map.empty()) |
| return; |
| |
| LoadInfoUpdateTask* task = new LoadInfoUpdateTask; |
| task->info_map.swap(info_map); |
| ui_loop_->PostTask(FROM_HERE, task); |
| } |
| |
| void ResourceDispatcherHost::MaybeUpdateUploadProgress(ExtraRequestInfo *info, |
| URLRequest *request) { |
| if (!info->upload_size || info->waiting_for_upload_progress_ack || |
| !(request->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS)) |
| return; |
| |
| uint64 size = info->upload_size; |
| uint64 position = request->GetUploadProgress(); |
| if (position == info->last_upload_position) |
| return; // 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) { |
| info->resource_handler->OnUploadProgress(info->request_id, position, size); |
| info->waiting_for_upload_progress_ack = true; |
| info->last_upload_ticks = TimeTicks::Now(); |
| info->last_upload_position = position; |
| } |
| } |
| |
| void ResourceDispatcherHost::BlockRequestsForRoute( |
| int process_id, |
| int route_id) { |
| std::pair<int, int> key(process_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 process_id, |
| int route_id) { |
| ProcessBlockedRequestsForRoute(process_id, route_id, false); |
| } |
| |
| void ResourceDispatcherHost::CancelBlockedRequestsForRoute( |
| int process_id, |
| int route_id) { |
| ProcessBlockedRequestsForRoute(process_id, route_id, true); |
| } |
| |
| void ResourceDispatcherHost::ProcessBlockedRequestsForRoute( |
| int process_id, |
| int route_id, |
| bool cancel_requests) { |
| BlockedRequestMap::iterator iter = |
| blocked_requests_map_.find(std::pair<int, int>(process_id, route_id)); |
| if (iter == blocked_requests_map_.end()) { |
| NOTREACHED(); |
| 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; |
| ExtraRequestInfo* info = ExtraInfoForRequest(request); |
| IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost, |
| info->process_id); |
| if (cancel_requests) |
| delete request; |
| else |
| BeginRequestInternal(request); |
| } |
| |
| delete requests; |
| } |
| |
| bool ResourceDispatcherHost::IsResourceDispatcherHostMessage( |
| const IPC::Message& message) { |
| switch (message.type()) { |
| case ViewHostMsg_RequestResource::ID: |
| case ViewHostMsg_CancelRequest::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; |
| } |