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
35using base::TimeTicks;
36
37namespace content {
38namespace {
39
40static int kBufferSize = 1024 * 512;
41static int kMinAllocationSize = 1024 * 4;
42static int kMaxAllocationSize = 1024 * 32;
43
44void 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
51void 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
62int 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
69class 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
80AsyncResourceHandler::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
96AsyncResourceHandler::~AsyncResourceHandler() {
97  if (has_checked_for_sufficient_resources_)
98    rdh_->FinishedWithResourcesForRequest(request());
99}
100
101bool 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
111void 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
127void 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
137bool 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
146bool 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
177bool 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
233bool AsyncResourceHandler::OnWillStart(const GURL& url, bool* defer) {
234  return true;
235}
236
237bool AsyncResourceHandler::OnBeforeNetworkStart(const GURL& url, bool* defer) {
238  return true;
239}
240
241bool 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
262bool 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
315void 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
327void 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
380bool 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
398void AsyncResourceHandler::ResumeIfDeferred() {
399  if (did_defer_) {
400    did_defer_ = false;
401    request()->LogUnblocked();
402    controller()->Resume();
403  }
404}
405
406void AsyncResourceHandler::OnDefer() {
407  request()->LogBlockedBy("AsyncResourceHandler");
408}
409
410}  // namespace content
411