1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/loader/async_resource_handler.h" 6 7 #include <algorithm> 8 #include <vector> 9 10 #include "base/command_line.h" 11 #include "base/containers/hash_tables.h" 12 #include "base/debug/alias.h" 13 #include "base/logging.h" 14 #include "base/memory/shared_memory.h" 15 #include "base/metrics/histogram.h" 16 #include "base/strings/string_number_conversions.h" 17 #include "base/time/time.h" 18 #include "content/browser/devtools/devtools_netlog_observer.h" 19 #include "content/browser/host_zoom_map_impl.h" 20 #include "content/browser/loader/resource_buffer.h" 21 #include "content/browser/loader/resource_dispatcher_host_impl.h" 22 #include "content/browser/loader/resource_message_filter.h" 23 #include "content/browser/loader/resource_request_info_impl.h" 24 #include "content/browser/resource_context_impl.h" 25 #include "content/common/resource_messages.h" 26 #include "content/common/view_messages.h" 27 #include "content/public/browser/resource_dispatcher_host_delegate.h" 28 #include "content/public/common/resource_response.h" 29 #include "net/base/io_buffer.h" 30 #include "net/base/load_flags.h" 31 #include "net/base/net_log.h" 32 #include "net/base/net_util.h" 33 #include "net/url_request/redirect_info.h" 34 35 using base::TimeTicks; 36 37 namespace content { 38 namespace { 39 40 static int kBufferSize = 1024 * 512; 41 static int kMinAllocationSize = 1024 * 4; 42 static int kMaxAllocationSize = 1024 * 32; 43 44 void GetNumericArg(const std::string& name, int* result) { 45 const std::string& value = 46 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name); 47 if (!value.empty()) 48 base::StringToInt(value, result); 49 } 50 51 void InitializeResourceBufferConstants() { 52 static bool did_init = false; 53 if (did_init) 54 return; 55 did_init = true; 56 57 GetNumericArg("resource-buffer-size", &kBufferSize); 58 GetNumericArg("resource-buffer-min-allocation-size", &kMinAllocationSize); 59 GetNumericArg("resource-buffer-max-allocation-size", &kMaxAllocationSize); 60 } 61 62 int CalcUsedPercentage(int bytes_read, int buffer_size) { 63 double ratio = static_cast<double>(bytes_read) / buffer_size; 64 return static_cast<int>(ratio * 100.0 + 0.5); // Round to nearest integer. 65 } 66 67 } // namespace 68 69 class DependentIOBuffer : public net::WrappedIOBuffer { 70 public: 71 DependentIOBuffer(ResourceBuffer* backing, char* memory) 72 : net::WrappedIOBuffer(memory), 73 backing_(backing) { 74 } 75 private: 76 virtual ~DependentIOBuffer() {} 77 scoped_refptr<ResourceBuffer> backing_; 78 }; 79 80 AsyncResourceHandler::AsyncResourceHandler( 81 net::URLRequest* request, 82 ResourceDispatcherHostImpl* rdh) 83 : ResourceHandler(request), 84 ResourceMessageDelegate(request), 85 rdh_(rdh), 86 pending_data_count_(0), 87 allocation_size_(0), 88 did_defer_(false), 89 has_checked_for_sufficient_resources_(false), 90 sent_received_response_msg_(false), 91 sent_first_data_msg_(false), 92 reported_transfer_size_(0) { 93 InitializeResourceBufferConstants(); 94 } 95 96 AsyncResourceHandler::~AsyncResourceHandler() { 97 if (has_checked_for_sufficient_resources_) 98 rdh_->FinishedWithResourcesForRequest(request()); 99 } 100 101 bool AsyncResourceHandler::OnMessageReceived(const IPC::Message& message) { 102 bool handled = true; 103 IPC_BEGIN_MESSAGE_MAP(AsyncResourceHandler, message) 104 IPC_MESSAGE_HANDLER(ResourceHostMsg_FollowRedirect, OnFollowRedirect) 105 IPC_MESSAGE_HANDLER(ResourceHostMsg_DataReceived_ACK, OnDataReceivedACK) 106 IPC_MESSAGE_UNHANDLED(handled = false) 107 IPC_END_MESSAGE_MAP() 108 return handled; 109 } 110 111 void AsyncResourceHandler::OnFollowRedirect(int request_id) { 112 if (!request()->status().is_success()) { 113 DVLOG(1) << "OnFollowRedirect for invalid request"; 114 return; 115 } 116 117 if (!redirect_start_time_.is_null()) { 118 UMA_HISTOGRAM_TIMES("Net.AsyncResourceHandler_RedirectHopTime", 119 TimeTicks::Now() - redirect_start_time_); 120 // Reset start time. 121 redirect_start_time_ = TimeTicks(); 122 } 123 124 ResumeIfDeferred(); 125 } 126 127 void AsyncResourceHandler::OnDataReceivedACK(int request_id) { 128 if (pending_data_count_) { 129 --pending_data_count_; 130 131 buffer_->RecycleLeastRecentlyAllocated(); 132 if (buffer_->CanAllocate()) 133 ResumeIfDeferred(); 134 } 135 } 136 137 bool AsyncResourceHandler::OnUploadProgress(uint64 position, 138 uint64 size) { 139 ResourceMessageFilter* filter = GetFilter(); 140 if (!filter) 141 return false; 142 return filter->Send( 143 new ResourceMsg_UploadProgress(GetRequestID(), position, size)); 144 } 145 146 bool AsyncResourceHandler::OnRequestRedirected( 147 const net::RedirectInfo& redirect_info, 148 ResourceResponse* response, 149 bool* defer) { 150 const ResourceRequestInfoImpl* info = GetRequestInfo(); 151 if (!info->filter()) 152 return false; 153 154 redirect_start_time_ = TimeTicks::Now(); 155 156 *defer = did_defer_ = true; 157 OnDefer(); 158 159 if (rdh_->delegate()) { 160 rdh_->delegate()->OnRequestRedirected( 161 redirect_info.new_url, request(), info->GetContext(), response); 162 } 163 164 DevToolsNetLogObserver::PopulateResponseInfo(request(), response); 165 response->head.encoded_data_length = request()->GetTotalReceivedBytes(); 166 reported_transfer_size_ = 0; 167 response->head.request_start = request()->creation_time(); 168 response->head.response_start = TimeTicks::Now(); 169 // TODO(davidben): Is it necessary to pass the new first party URL for 170 // cookies? The only case where it can change is top-level navigation requests 171 // and hopefully those will eventually all be owned by the browser. It's 172 // possible this is still needed while renderer-owned ones exist. 173 return info->filter()->Send(new ResourceMsg_ReceivedRedirect( 174 GetRequestID(), redirect_info, response->head)); 175 } 176 177 bool AsyncResourceHandler::OnResponseStarted(ResourceResponse* response, 178 bool* defer) { 179 // For changes to the main frame, inform the renderer of the new URL's 180 // per-host settings before the request actually commits. This way the 181 // renderer will be able to set these precisely at the time the 182 // request commits, avoiding the possibility of e.g. zooming the old content 183 // or of having to layout the new content twice. 184 185 const ResourceRequestInfoImpl* info = GetRequestInfo(); 186 if (!info->filter()) 187 return false; 188 189 if (rdh_->delegate()) { 190 rdh_->delegate()->OnResponseStarted( 191 request(), info->GetContext(), response, info->filter()); 192 } 193 194 DevToolsNetLogObserver::PopulateResponseInfo(request(), response); 195 196 HostZoomMap* host_zoom_map = 197 GetHostZoomMapForResourceContext(info->GetContext()); 198 199 if (info->GetResourceType() == RESOURCE_TYPE_MAIN_FRAME && host_zoom_map) { 200 const GURL& request_url = request()->url(); 201 info->filter()->Send(new ViewMsg_SetZoomLevelForLoadingURL( 202 info->GetRouteID(), 203 request_url, host_zoom_map->GetZoomLevelForHostAndScheme( 204 request_url.scheme(), 205 net::GetHostOrSpecFromURL(request_url)))); 206 } 207 208 // If the parent handler downloaded the resource to a file, grant the child 209 // read permissions on it. 210 if (!response->head.download_file_path.empty()) { 211 rdh_->RegisterDownloadedTempFile( 212 info->GetChildID(), info->GetRequestID(), 213 response->head.download_file_path); 214 } 215 216 response->head.request_start = request()->creation_time(); 217 response->head.response_start = TimeTicks::Now(); 218 info->filter()->Send(new ResourceMsg_ReceivedResponse(GetRequestID(), 219 response->head)); 220 sent_received_response_msg_ = true; 221 222 if (request()->response_info().metadata.get()) { 223 std::vector<char> copy(request()->response_info().metadata->data(), 224 request()->response_info().metadata->data() + 225 request()->response_info().metadata->size()); 226 info->filter()->Send(new ResourceMsg_ReceivedCachedMetadata(GetRequestID(), 227 copy)); 228 } 229 230 return true; 231 } 232 233 bool AsyncResourceHandler::OnWillStart(const GURL& url, bool* defer) { 234 return true; 235 } 236 237 bool AsyncResourceHandler::OnBeforeNetworkStart(const GURL& url, bool* defer) { 238 return true; 239 } 240 241 bool AsyncResourceHandler::OnWillRead(scoped_refptr<net::IOBuffer>* buf, 242 int* buf_size, 243 int min_size) { 244 DCHECK_EQ(-1, min_size); 245 246 if (!EnsureResourceBufferIsInitialized()) 247 return false; 248 249 DCHECK(buffer_->CanAllocate()); 250 char* memory = buffer_->Allocate(&allocation_size_); 251 CHECK(memory); 252 253 *buf = new DependentIOBuffer(buffer_.get(), memory); 254 *buf_size = allocation_size_; 255 256 UMA_HISTOGRAM_CUSTOM_COUNTS( 257 "Net.AsyncResourceHandler_SharedIOBuffer_Alloc", 258 *buf_size, 0, kMaxAllocationSize, 100); 259 return true; 260 } 261 262 bool AsyncResourceHandler::OnReadCompleted(int bytes_read, bool* defer) { 263 DCHECK_GE(bytes_read, 0); 264 265 if (!bytes_read) 266 return true; 267 268 ResourceMessageFilter* filter = GetFilter(); 269 if (!filter) 270 return false; 271 272 buffer_->ShrinkLastAllocation(bytes_read); 273 274 UMA_HISTOGRAM_CUSTOM_COUNTS( 275 "Net.AsyncResourceHandler_SharedIOBuffer_Used", 276 bytes_read, 0, kMaxAllocationSize, 100); 277 UMA_HISTOGRAM_PERCENTAGE( 278 "Net.AsyncResourceHandler_SharedIOBuffer_UsedPercentage", 279 CalcUsedPercentage(bytes_read, allocation_size_)); 280 281 if (!sent_first_data_msg_) { 282 base::SharedMemoryHandle handle; 283 int size; 284 if (!buffer_->ShareToProcess(filter->PeerHandle(), &handle, &size)) 285 return false; 286 filter->Send(new ResourceMsg_SetDataBuffer( 287 GetRequestID(), handle, size, filter->peer_pid())); 288 sent_first_data_msg_ = true; 289 } 290 291 int data_offset = buffer_->GetLastAllocationOffset(); 292 293 int64_t current_transfer_size = request()->GetTotalReceivedBytes(); 294 int encoded_data_length = current_transfer_size - reported_transfer_size_; 295 reported_transfer_size_ = current_transfer_size; 296 297 filter->Send(new ResourceMsg_DataReceived( 298 GetRequestID(), data_offset, bytes_read, encoded_data_length)); 299 ++pending_data_count_; 300 UMA_HISTOGRAM_CUSTOM_COUNTS( 301 "Net.AsyncResourceHandler_PendingDataCount", 302 pending_data_count_, 0, 100, 100); 303 304 if (!buffer_->CanAllocate()) { 305 UMA_HISTOGRAM_CUSTOM_COUNTS( 306 "Net.AsyncResourceHandler_PendingDataCount_WhenFull", 307 pending_data_count_, 0, 100, 100); 308 *defer = did_defer_ = true; 309 OnDefer(); 310 } 311 312 return true; 313 } 314 315 void AsyncResourceHandler::OnDataDownloaded(int bytes_downloaded) { 316 int64_t current_transfer_size = request()->GetTotalReceivedBytes(); 317 int encoded_data_length = current_transfer_size - reported_transfer_size_; 318 reported_transfer_size_ = current_transfer_size; 319 320 ResourceMessageFilter* filter = GetFilter(); 321 if (filter) { 322 filter->Send(new ResourceMsg_DataDownloaded( 323 GetRequestID(), bytes_downloaded, encoded_data_length)); 324 } 325 } 326 327 void AsyncResourceHandler::OnResponseCompleted( 328 const net::URLRequestStatus& status, 329 const std::string& security_info, 330 bool* defer) { 331 const ResourceRequestInfoImpl* info = GetRequestInfo(); 332 if (!info->filter()) 333 return; 334 335 // If we crash here, figure out what URL the renderer was requesting. 336 // http://crbug.com/107692 337 char url_buf[128]; 338 base::strlcpy(url_buf, request()->url().spec().c_str(), arraysize(url_buf)); 339 base::debug::Alias(url_buf); 340 341 // TODO(gavinp): Remove this CHECK when we figure out the cause of 342 // http://crbug.com/124680 . This check mirrors closely check in 343 // WebURLLoaderImpl::OnCompletedRequest that routes this message to a WebCore 344 // ResourceHandleInternal which asserts on its state and crashes. By crashing 345 // when the message is sent, we should get better crash reports. 346 CHECK(status.status() != net::URLRequestStatus::SUCCESS || 347 sent_received_response_msg_); 348 349 int error_code = status.error(); 350 bool was_ignored_by_handler = info->WasIgnoredByHandler(); 351 352 DCHECK(status.status() != net::URLRequestStatus::IO_PENDING); 353 // If this check fails, then we're in an inconsistent state because all 354 // requests ignored by the handler should be canceled (which should result in 355 // the ERR_ABORTED error code). 356 DCHECK(!was_ignored_by_handler || error_code == net::ERR_ABORTED); 357 358 // TODO(mkosiba): Fix up cases where we create a URLRequestStatus 359 // with a status() != SUCCESS and an error_code() == net::OK. 360 if (status.status() == net::URLRequestStatus::CANCELED && 361 error_code == net::OK) { 362 error_code = net::ERR_ABORTED; 363 } else if (status.status() == net::URLRequestStatus::FAILED && 364 error_code == net::OK) { 365 error_code = net::ERR_FAILED; 366 } 367 368 ResourceMsg_RequestCompleteData request_complete_data; 369 request_complete_data.error_code = error_code; 370 request_complete_data.was_ignored_by_handler = was_ignored_by_handler; 371 request_complete_data.exists_in_cache = request()->response_info().was_cached; 372 request_complete_data.security_info = security_info; 373 request_complete_data.completion_time = TimeTicks::Now(); 374 request_complete_data.encoded_data_length = 375 request()->GetTotalReceivedBytes(); 376 info->filter()->Send( 377 new ResourceMsg_RequestComplete(GetRequestID(), request_complete_data)); 378 } 379 380 bool AsyncResourceHandler::EnsureResourceBufferIsInitialized() { 381 if (buffer_.get() && buffer_->IsInitialized()) 382 return true; 383 384 if (!has_checked_for_sufficient_resources_) { 385 has_checked_for_sufficient_resources_ = true; 386 if (!rdh_->HasSufficientResourcesForRequest(request())) { 387 controller()->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES); 388 return false; 389 } 390 } 391 392 buffer_ = new ResourceBuffer(); 393 return buffer_->Initialize(kBufferSize, 394 kMinAllocationSize, 395 kMaxAllocationSize); 396 } 397 398 void AsyncResourceHandler::ResumeIfDeferred() { 399 if (did_defer_) { 400 did_defer_ = false; 401 request()->LogUnblocked(); 402 controller()->Resume(); 403 } 404 } 405 406 void AsyncResourceHandler::OnDefer() { 407 request()->LogBlockedBy("AsyncResourceHandler"); 408 } 409 410 } // namespace content 411