Home | History | Annotate | Download | only in loader
      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