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