// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/renderer_host/async_resource_handler.h" #include #include #include "base/hash_tables.h" #include "base/logging.h" #include "base/shared_memory.h" #include "chrome/browser/debugger/devtools_netlog_observer.h" #include "chrome/browser/net/chrome_url_request_context.h" #include "chrome/browser/net/load_timing_observer.h" #include "chrome/browser/renderer_host/global_request_id.h" #include "chrome/browser/renderer_host/resource_dispatcher_host.h" #include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" #include "chrome/browser/renderer_host/resource_message_filter.h" #include "chrome/common/render_messages.h" #include "chrome/common/resource_response.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/net_log.h" #include "webkit/glue/resource_loader_bridge.h" using base::Time; using base::TimeTicks; namespace { // When reading, we don't know if we are going to get EOF (0 bytes read), so // we typically have a buffer that we allocated but did not use. We keep // this buffer around for the next read as a small optimization. SharedIOBuffer* g_spare_read_buffer = NULL; // The initial size of the shared memory buffer. (32 kilobytes). const int kInitialReadBufSize = 32768; // The maximum size of the shared memory buffer. (512 kilobytes). const int kMaxReadBufSize = 524288; } // namespace // Our version of IOBuffer that uses shared memory. class SharedIOBuffer : public net::IOBuffer { public: explicit SharedIOBuffer(int buffer_size) : net::IOBuffer(), ok_(false), buffer_size_(buffer_size) {} bool Init() { if (shared_memory_.CreateAndMapAnonymous(buffer_size_)) { data_ = reinterpret_cast(shared_memory_.memory()); DCHECK(data_); ok_ = true; } return ok_; } base::SharedMemory* shared_memory() { return &shared_memory_; } bool ok() { return ok_; } int buffer_size() { return buffer_size_; } private: ~SharedIOBuffer() { DCHECK(g_spare_read_buffer != this); data_ = NULL; } base::SharedMemory shared_memory_; bool ok_; int buffer_size_; }; AsyncResourceHandler::AsyncResourceHandler( ResourceMessageFilter* filter, int routing_id, const GURL& url, ResourceDispatcherHost* resource_dispatcher_host) : filter_(filter), routing_id_(routing_id), rdh_(resource_dispatcher_host), next_buffer_size_(kInitialReadBufSize) { } AsyncResourceHandler::~AsyncResourceHandler() { } bool AsyncResourceHandler::OnUploadProgress(int request_id, uint64 position, uint64 size) { return filter_->Send(new ViewMsg_Resource_UploadProgress(routing_id_, request_id, position, size)); } bool AsyncResourceHandler::OnRequestRedirected(int request_id, const GURL& new_url, ResourceResponse* response, bool* defer) { *defer = true; net::URLRequest* request = rdh_->GetURLRequest( GlobalRequestID(filter_->child_id(), request_id)); LoadTimingObserver::PopulateTimingInfo(request, response); DevToolsNetLogObserver::PopulateResponseInfo(request, response); return filter_->Send(new ViewMsg_Resource_ReceivedRedirect( routing_id_, request_id, new_url, response->response_head)); } bool AsyncResourceHandler::OnResponseStarted(int request_id, ResourceResponse* response) { // For changes to the main frame, inform the renderer of the new URL's // per-host settings before the request actually commits. This way the // renderer will be able to set these precisely at the time the // request commits, avoiding the possibility of e.g. zooming the old content // or of having to layout the new content twice. net::URLRequest* request = rdh_->GetURLRequest( GlobalRequestID(filter_->child_id(), request_id)); LoadTimingObserver::PopulateTimingInfo(request, response); DevToolsNetLogObserver::PopulateResponseInfo(request, response); ResourceDispatcherHostRequestInfo* info = rdh_->InfoForRequest(request); if (info->resource_type() == ResourceType::MAIN_FRAME) { GURL request_url(request->url()); ChromeURLRequestContext* context = static_cast(request->context()); if (context) { filter_->Send(new ViewMsg_SetContentSettingsForLoadingURL( info->route_id(), request_url, context->host_content_settings_map()->GetContentSettings( request_url))); filter_->Send(new ViewMsg_SetZoomLevelForLoadingURL(info->route_id(), request_url, context->host_zoom_map()->GetZoomLevel(request_url))); } } filter_->Send(new ViewMsg_Resource_ReceivedResponse( routing_id_, request_id, response->response_head)); if (request->response_info().metadata) { std::vector copy(request->response_info().metadata->data(), request->response_info().metadata->data() + request->response_info().metadata->size()); filter_->Send(new ViewMsg_Resource_ReceivedCachedMetadata( routing_id_, request_id, copy)); } return true; } bool AsyncResourceHandler::OnWillStart(int request_id, const GURL& url, bool* defer) { return true; } bool AsyncResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, int min_size) { DCHECK_EQ(-1, min_size); if (g_spare_read_buffer) { DCHECK(!read_buffer_); read_buffer_.swap(&g_spare_read_buffer); DCHECK(read_buffer_->data()); *buf = read_buffer_.get(); *buf_size = read_buffer_->buffer_size(); } else { read_buffer_ = new SharedIOBuffer(next_buffer_size_); if (!read_buffer_->Init()) { DLOG(ERROR) << "Couldn't allocate shared io buffer"; read_buffer_ = NULL; return false; } DCHECK(read_buffer_->data()); *buf = read_buffer_.get(); *buf_size = next_buffer_size_; } return true; } bool AsyncResourceHandler::OnReadCompleted(int request_id, int* bytes_read) { if (!*bytes_read) return true; DCHECK(read_buffer_.get()); if (read_buffer_->buffer_size() == *bytes_read) { // The network layer has saturated our buffer. Next time, we should give it // a bigger buffer for it to fill, to minimize the number of round trips we // do with the renderer process. next_buffer_size_ = std::min(next_buffer_size_ * 2, kMaxReadBufSize); } if (!rdh_->WillSendData(filter_->child_id(), request_id)) { // We should not send this data now, we have too many pending requests. return true; } base::SharedMemoryHandle handle; if (!read_buffer_->shared_memory()->GiveToProcess( filter_->peer_handle(), &handle)) { // We wrongfully incremented the pending data count. Fake an ACK message // to fix this. We can't move this call above the WillSendData because // it's killing our read_buffer_, and we don't want that when we pause // the request. rdh_->DataReceivedACK(filter_->child_id(), request_id); // We just unmapped the memory. read_buffer_ = NULL; return false; } // We just unmapped the memory. read_buffer_ = NULL; filter_->Send(new ViewMsg_Resource_DataReceived( routing_id_, request_id, handle, *bytes_read)); return true; } void AsyncResourceHandler::OnDataDownloaded( int request_id, int bytes_downloaded) { filter_->Send(new ViewMsg_Resource_DataDownloaded( routing_id_, request_id, bytes_downloaded)); } bool AsyncResourceHandler::OnResponseCompleted( int request_id, const net::URLRequestStatus& status, const std::string& security_info) { Time completion_time = Time::Now(); filter_->Send(new ViewMsg_Resource_RequestComplete(routing_id_, request_id, status, security_info, completion_time)); // If we still have a read buffer, then see about caching it for later... // Note that we have to make sure the buffer is not still being used, so we // have to perform an explicit check on the status code. if (g_spare_read_buffer || net::URLRequestStatus::SUCCESS != status.status()) { read_buffer_ = NULL; } else if (read_buffer_.get()) { DCHECK(read_buffer_->data()); read_buffer_.swap(&g_spare_read_buffer); } return true; } void AsyncResourceHandler::OnRequestClosed() { } // static void AsyncResourceHandler::GlobalCleanup() { if (g_spare_read_buffer) { // Avoid the CHECK in SharedIOBuffer::~SharedIOBuffer(). SharedIOBuffer* tmp = g_spare_read_buffer; g_spare_read_buffer = NULL; tmp->Release(); } }