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