Index: content/browser/download/download_resource_handler.cc |
diff --git a/content/browser/download/download_resource_handler.cc b/content/browser/download/download_resource_handler.cc |
index 5cc4ee8123ec222aca709871feec52415e2252ae..e4b82bf50be6e8178e2df7a8d2d207148b7306af 100644 |
--- a/content/browser/download/download_resource_handler.cc |
+++ b/content/browser/download/download_resource_handler.cc |
@@ -7,34 +7,22 @@ |
#include <string> |
#include "base/bind.h" |
+#include "base/callback_helpers.h" |
#include "base/location.h" |
#include "base/logging.h" |
-#include "base/metrics/histogram_macros.h" |
-#include "base/metrics/sparse_histogram.h" |
-#include "base/single_thread_task_runner.h" |
#include "base/strings/stringprintf.h" |
-#include "base/thread_task_runner_handle.h" |
#include "content/browser/byte_stream.h" |
#include "content/browser/download/download_create_info.h" |
#include "content/browser/download/download_interrupt_reasons_impl.h" |
#include "content/browser/download/download_manager_impl.h" |
#include "content/browser/download/download_request_handle.h" |
-#include "content/browser/download/download_stats.h" |
#include "content/browser/loader/resource_dispatcher_host_impl.h" |
#include "content/browser/loader/resource_request_info_impl.h" |
#include "content/public/browser/browser_thread.h" |
#include "content/public/browser/download_interrupt_reasons.h" |
-#include "content/public/browser/download_item.h" |
-#include "content/public/browser/download_manager_delegate.h" |
#include "content/public/browser/navigation_entry.h" |
-#include "content/public/browser/power_save_blocker.h" |
#include "content/public/browser/web_contents.h" |
#include "content/public/common/resource_response.h" |
-#include "net/base/io_buffer.h" |
-#include "net/base/net_errors.h" |
-#include "net/http/http_response_headers.h" |
-#include "net/http/http_status_code.h" |
-#include "net/url_request/url_request_context.h" |
namespace content { |
@@ -45,17 +33,6 @@ struct DownloadResourceHandler::DownloadTabInfo { |
namespace { |
-void CallStartedCBOnUIThread( |
- const DownloadUrlParameters::OnStartedCallback& started_cb, |
- DownloadItem* item, |
- DownloadInterruptReason interrupt_reason) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- |
- if (started_cb.is_null()) |
- return; |
- started_cb.Run(item, interrupt_reason); |
-} |
- |
// Static function in order to prevent any accidental accesses to |
// DownloadResourceHandler members from the UI thread. |
static void StartOnUIThread( |
@@ -65,7 +42,8 @@ static void StartOnUIThread( |
const DownloadUrlParameters::OnStartedCallback& started_cb) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- DownloadManager* download_manager = info->request_handle.GetDownloadManager(); |
+ DownloadManager* download_manager = |
+ info->request_handle->GetDownloadManager(); |
if (!download_manager) { |
// NULL in unittests or if the page closed right after starting the |
// download. |
@@ -104,8 +82,6 @@ void DeleteOnUIThread( |
} // namespace |
-const int DownloadResourceHandler::kDownloadByteStreamSize = 100 * 1024; |
- |
DownloadResourceHandler::DownloadResourceHandler( |
uint32 id, |
net::URLRequest* request, |
@@ -114,15 +90,11 @@ DownloadResourceHandler::DownloadResourceHandler( |
: ResourceHandler(request), |
download_id_(id), |
started_cb_(started_cb), |
- save_info_(save_info.Pass()), |
tab_info_(new DownloadTabInfo()), |
- last_buffer_size_(0), |
- bytes_read_(0), |
- pause_count_(0), |
- was_deferred_(false), |
- on_response_started_called_(false) { |
- RecordDownloadCount(UNTHROTTLED_COUNT); |
- |
+ core_(request, |
+ save_info.Pass(), |
+ base::Bind(&DownloadResourceHandler::OnCoreReadyToRead, |
+ base::Unretained(this))) { |
// Do UI thread initialization for tab_info_ asap after |
// DownloadResourceHandler creation since the tab could be navigated |
// before StartOnUIThread gets called. This is safe because deletion |
@@ -137,9 +109,19 @@ DownloadResourceHandler::DownloadResourceHandler( |
request_info->GetRequestID(), |
request_info->frame_tree_node_id()), |
tab_info_.get())); |
- power_save_blocker_ = PowerSaveBlocker::Create( |
- PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, |
- PowerSaveBlocker::kReasonOther, "Download in progress"); |
+} |
+ |
+DownloadResourceHandler::~DownloadResourceHandler() { |
+ // This won't do anything if the callback was called before. |
+ // If it goes through, it will likely be because OnWillStart() returned |
+ // false somewhere in the chain of resource handlers. |
+ CallStartedCB(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); |
+ |
+ if (tab_info_) { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&DeleteOnUIThread, base::Passed(&tab_info_))); |
+ } |
} |
bool DownloadResourceHandler::OnRequestRedirected( |
@@ -153,127 +135,39 @@ bool DownloadResourceHandler::OnRequestRedirected( |
bool DownloadResourceHandler::OnResponseStarted( |
ResourceResponse* response, |
bool* defer) { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- // There can be only one (call) |
- DCHECK(!on_response_started_called_); |
- on_response_started_called_ = true; |
- |
- DVLOG(20) << __FUNCTION__ << "()" << DebugString(); |
- download_start_time_ = base::TimeTicks::Now(); |
- |
- // If it's a download, we don't want to poison the cache with it. |
- request()->StopCaching(); |
- |
- // Lower priority as well, so downloads don't contend for resources |
- // with main frames. |
- request()->SetPriority(net::IDLE); |
+ scoped_ptr<DownloadCreateInfo> create_info; |
+ scoped_ptr<ByteStreamReader> stream_reader; |
- // If the content-length header is not present (or contains something other |
- // than numbers), the incoming content_length is -1 (unknown size). |
- // Set the content length to 0 to indicate unknown size to DownloadManager. |
- int64 content_length = |
- response->head.content_length > 0 ? response->head.content_length : 0; |
+ core_.OnResponseStarted(&create_info, &stream_reader); |
const ResourceRequestInfoImpl* request_info = GetRequestInfo(); |
- |
- // Deleted in DownloadManager. |
- scoped_ptr<DownloadCreateInfo> info( |
- new DownloadCreateInfo(base::Time::Now(), |
- content_length, |
- request()->net_log(), |
- request_info->HasUserGesture(), |
- request_info->GetPageTransition(), |
- save_info_.Pass())); |
- |
- // Create the ByteStream for sending data to the download sink. |
- scoped_ptr<ByteStreamReader> stream_reader; |
- CreateByteStream( |
- base::ThreadTaskRunnerHandle::Get(), |
- BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), |
- kDownloadByteStreamSize, &stream_writer_, &stream_reader); |
- stream_writer_->RegisterCallback( |
- base::Bind(&DownloadResourceHandler::ResumeRequest, AsWeakPtr())); |
- |
- info->download_id = download_id_; |
- info->url_chain = request()->url_chain(); |
- info->referrer_url = GURL(request()->referrer()); |
- info->mime_type = response->head.mime_type; |
- info->remote_address = request()->GetSocketAddress().host(); |
- if (request()->response_headers()) { |
- // Grab the first content-disposition header. There may be more than one, |
- // though as of this writing, the network stack ensures if there are, they |
- // are all duplicates. |
- request()->response_headers()->EnumerateHeader( |
- nullptr, "content-disposition", &info->content_disposition); |
- } |
- RecordDownloadMimeType(info->mime_type); |
- RecordDownloadContentDisposition(info->content_disposition); |
- |
- info->request_handle = DownloadRequestHandle( |
+ create_info->download_id = download_id_; |
+ create_info->has_user_gesture = request_info->HasUserGesture(); |
+ create_info->transition_type = request_info->GetPageTransition(); |
+ create_info->request_handle.reset(new DownloadRequestHandle( |
AsWeakPtr(), request_info->GetChildID(), request_info->GetRouteID(), |
- request_info->GetRequestID(), request_info->frame_tree_node_id()); |
- |
- // Get the last modified time and etag. |
- const net::HttpResponseHeaders* headers = request()->response_headers(); |
- if (headers) { |
- if (headers->HasStrongValidators()) { |
- // If we don't have strong validators as per RFC 2616 section 13.3.3, then |
- // we neither store nor use them for range requests. |
- if (!headers->EnumerateHeader(NULL, "Last-Modified", |
- &info->last_modified)) |
- info->last_modified.clear(); |
- if (!headers->EnumerateHeader(NULL, "ETag", &info->etag)) |
- info->etag.clear(); |
- } |
+ request_info->GetRequestID(), request_info->frame_tree_node_id())); |
- int status = headers->response_code(); |
- if (2 == status / 100 && status != net::HTTP_PARTIAL_CONTENT) { |
- // Success & not range response; if we asked for a range, we didn't |
- // get it--reset the file pointers to reflect that. |
- info->save_info->offset = 0; |
- info->save_info->hash_state = ""; |
- } |
- |
- if (!headers->GetMimeType(&info->original_mime_type)) |
- info->original_mime_type.clear(); |
- } |
- |
- // Blink verifies that the requester of this download is allowed to set a |
- // suggested name for the security origin of the downlaod URL. However, this |
- // assumption doesn't hold if there were cross origin redirects. Therefore, |
- // clear the suggested_name for such requests. |
- if (info->url_chain.size() > 1 && |
- info->url_chain.front().GetOrigin() != info->url_chain.back().GetOrigin()) |
- info->save_info->suggested_name.clear(); |
+ // The MIME type in ResourceResponse is the product of |
+ // MimeTypeResourceHandler. |
+ create_info->mime_type = response->head.mime_type; |
BrowserThread::PostTask( |
BrowserThread::UI, FROM_HERE, |
- base::Bind(&StartOnUIThread, |
- base::Passed(&info), |
- base::Passed(&tab_info_), |
- base::Passed(&stream_reader), |
- // Pass to StartOnUIThread so that variable |
- // access is always on IO thread but function |
- // is called on UI thread. |
- started_cb_)); |
- // Guaranteed to be called in StartOnUIThread |
- started_cb_.Reset(); |
- |
+ base::Bind(&StartOnUIThread, base::Passed(&create_info), |
+ base::Passed(&tab_info_), base::Passed(&stream_reader), |
+ base::ResetAndReturn(&started_cb_))); |
return true; |
} |
void DownloadResourceHandler::CallStartedCB( |
- DownloadItem* item, |
DownloadInterruptReason interrupt_reason) { |
DCHECK_CURRENTLY_ON(BrowserThread::IO); |
if (started_cb_.is_null()) |
return; |
- BrowserThread::PostTask( |
- BrowserThread::UI, |
- FROM_HERE, |
- base::Bind( |
- &CallStartedCBOnUIThread, started_cb_, item, interrupt_reason)); |
- started_cb_.Reset(); |
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
+ base::Bind(base::ResetAndReturn(&started_cb_), |
+ nullptr, interrupt_reason)); |
} |
bool DownloadResourceHandler::OnWillStart(const GURL& url, bool* defer) { |
@@ -290,175 +184,20 @@ bool DownloadResourceHandler::OnBeforeNetworkStart(const GURL& url, |
bool DownloadResourceHandler::OnWillRead(scoped_refptr<net::IOBuffer>* buf, |
int* buf_size, |
int min_size) { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- DCHECK(buf && buf_size); |
- DCHECK(!read_buffer_.get()); |
- |
- *buf_size = min_size < 0 ? kReadBufSize : min_size; |
- last_buffer_size_ = *buf_size; |
- read_buffer_ = new net::IOBuffer(*buf_size); |
- *buf = read_buffer_.get(); |
- return true; |
+ return core_.OnWillRead(buf, buf_size, min_size); |
} |
// Pass the buffer to the download file writer. |
bool DownloadResourceHandler::OnReadCompleted(int bytes_read, bool* defer) { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- DCHECK(read_buffer_.get()); |
- |
- base::TimeTicks now(base::TimeTicks::Now()); |
- if (!last_read_time_.is_null()) { |
- double seconds_since_last_read = (now - last_read_time_).InSecondsF(); |
- if (now == last_read_time_) |
- // Use 1/10 ms as a "very small number" so that we avoid |
- // divide-by-zero error and still record a very high potential bandwidth. |
- seconds_since_last_read = 0.00001; |
- |
- double actual_bandwidth = (bytes_read)/seconds_since_last_read; |
- double potential_bandwidth = last_buffer_size_/seconds_since_last_read; |
- RecordBandwidth(actual_bandwidth, potential_bandwidth); |
- } |
- last_read_time_ = now; |
- |
- if (!bytes_read) |
- return true; |
- bytes_read_ += bytes_read; |
- DCHECK(read_buffer_.get()); |
- |
- // Take the data ship it down the stream. If the stream is full, pause the |
- // request; the stream callback will resume it. |
- if (!stream_writer_->Write(read_buffer_, bytes_read)) { |
- PauseRequest(); |
- *defer = was_deferred_ = true; |
- last_stream_pause_time_ = now; |
- } |
- |
- read_buffer_ = NULL; // Drop our reference. |
- |
- if (pause_count_ > 0) |
- *defer = was_deferred_ = true; |
- |
- return true; |
+ return core_.OnReadCompleted(bytes_read, defer); |
} |
void DownloadResourceHandler::OnResponseCompleted( |
const net::URLRequestStatus& status, |
const std::string& security_info, |
bool* defer) { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- int response_code = status.is_success() ? request()->GetResponseCode() : 0; |
- DVLOG(20) << __FUNCTION__ << "()" << DebugString() |
- << " status.status() = " << status.status() |
- << " status.error() = " << status.error() |
- << " response_code = " << response_code; |
- |
- net::Error error_code = net::OK; |
- if (status.status() == net::URLRequestStatus::FAILED || |
- // Note cancels as failures too. |
- status.status() == net::URLRequestStatus::CANCELED) { |
- error_code = static_cast<net::Error>(status.error()); // Normal case. |
- // Make sure that at least the fact of failure comes through. |
- if (error_code == net::OK) |
- error_code = net::ERR_FAILED; |
- } |
- |
- // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are |
- // allowed since a number of servers in the wild close the connection too |
- // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 - |
- // treat downloads as complete in both cases, so we follow their lead. |
- if (error_code == net::ERR_CONTENT_LENGTH_MISMATCH || |
- error_code == net::ERR_INCOMPLETE_CHUNKED_ENCODING) { |
- error_code = net::OK; |
- } |
- DownloadInterruptReason reason = |
- ConvertNetErrorToInterruptReason( |
- error_code, DOWNLOAD_INTERRUPT_FROM_NETWORK); |
- |
- if (status.status() == net::URLRequestStatus::CANCELED && |
- status.error() == net::ERR_ABORTED) { |
- // CANCELED + ERR_ABORTED == something outside of the network |
- // stack cancelled the request. There aren't that many things that |
- // could do this to a download request (whose lifetime is separated from |
- // the tab from which it came). We map this to USER_CANCELLED as the |
- // case we know about (system suspend because of laptop close) corresponds |
- // to a user action. |
- // TODO(ahendrickson) -- Find a better set of codes to use here, as |
- // CANCELED/ERR_ABORTED can occur for reasons other than user cancel. |
- if (net::IsCertStatusError(request()->ssl_info().cert_status)) |
- reason = DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM; |
- else |
- reason = DOWNLOAD_INTERRUPT_REASON_USER_CANCELED; |
- } |
- |
- if (status.is_success() && |
- reason == DOWNLOAD_INTERRUPT_REASON_NONE && |
- request()->response_headers()) { |
- // Handle server's response codes. |
- switch(response_code) { |
- case -1: // Non-HTTP request. |
- case net::HTTP_OK: |
- case net::HTTP_CREATED: |
- case net::HTTP_ACCEPTED: |
- case net::HTTP_NON_AUTHORITATIVE_INFORMATION: |
- case net::HTTP_RESET_CONTENT: |
- case net::HTTP_PARTIAL_CONTENT: |
- // Expected successful codes. |
- break; |
- case net::HTTP_NO_CONTENT: |
- case net::HTTP_NOT_FOUND: |
- reason = DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT; |
- break; |
- case net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: |
- // Retry by downloading from the start automatically: |
- // If we haven't received data when we get this error, we won't. |
- reason = DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE; |
- break; |
- case net::HTTP_UNAUTHORIZED: |
- // Server didn't authorize this request. |
- reason = DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED; |
- break; |
- case net::HTTP_FORBIDDEN: |
- // Server forbids access to this resource. |
- reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN; |
- break; |
- default: // All other errors. |
- // Redirection and informational codes should have been handled earlier |
- // in the stack. |
- DCHECK_NE(3, response_code / 100); |
- DCHECK_NE(1, response_code / 100); |
- reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED; |
- break; |
- } |
- } |
- |
- std::string accept_ranges; |
- bool has_strong_validators = false; |
- if (request()->response_headers()) { |
- request()->response_headers()->EnumerateHeader( |
- NULL, "Accept-Ranges", &accept_ranges); |
- has_strong_validators = |
- request()->response_headers()->HasStrongValidators(); |
- } |
- RecordAcceptsRanges(accept_ranges, bytes_read_, has_strong_validators); |
- RecordNetworkBlockage(base::TimeTicks::Now() - download_start_time_, |
- total_pause_time_); |
- |
- CallStartedCB(NULL, reason); |
- |
- // Send the info down the stream. Conditional is in case we get |
- // OnResponseCompleted without OnResponseStarted. |
- if (stream_writer_) |
- stream_writer_->Close(reason); |
- |
- // If the error mapped to something unknown, record it so that |
- // we can drill down. |
- if (reason == DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED) { |
- UMA_HISTOGRAM_SPARSE_SLOWLY("Download.MapErrorNetworkFailed", |
- std::abs(status.error())); |
- } |
- |
- stream_writer_.reset(); // We no longer need the stream. |
- read_buffer_ = NULL; |
+ DownloadInterruptReason result = core_.OnResponseCompleted(status); |
+ CallStartedCB(result); |
} |
void DownloadResourceHandler::OnDataDownloaded(int bytes_downloaded) { |
@@ -466,28 +205,15 @@ void DownloadResourceHandler::OnDataDownloaded(int bytes_downloaded) { |
} |
void DownloadResourceHandler::PauseRequest() { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- |
- ++pause_count_; |
+ core_.PauseRequest(); |
} |
void DownloadResourceHandler::ResumeRequest() { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- DCHECK_LT(0, pause_count_); |
- |
- --pause_count_; |
- |
- if (!was_deferred_) |
- return; |
- if (pause_count_ > 0) |
- return; |
- |
- was_deferred_ = false; |
- if (!last_stream_pause_time_.is_null()) { |
- total_pause_time_ += (base::TimeTicks::Now() - last_stream_pause_time_); |
- last_stream_pause_time_ = base::TimeTicks(); |
- } |
+ core_.ResumeRequest(); |
+} |
+void DownloadResourceHandler::OnCoreReadyToRead() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
controller()->Resume(); |
} |
@@ -519,28 +245,4 @@ std::string DownloadResourceHandler::DebugString() const { |
info->GetRouteID()); |
} |
-DownloadResourceHandler::~DownloadResourceHandler() { |
- DCHECK_CURRENTLY_ON(BrowserThread::IO); |
- |
- // This won't do anything if the callback was called before. |
- // If it goes through, it will likely be because OnWillStart() returned |
- // false somewhere in the chain of resource handlers. |
- CallStartedCB(NULL, DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); |
- |
- // Remove output stream callback if a stream exists. |
- if (stream_writer_) |
- stream_writer_->RegisterCallback(base::Closure()); |
- |
- // tab_info_ must be destroyed on UI thread, since |
- // InitializeDownloadTabInfoOnUIThread might still be using it. |
- if (tab_info_.get()) { |
- BrowserThread::PostTask( |
- BrowserThread::UI, FROM_HERE, |
- base::Bind(&DeleteOnUIThread, base::Passed(&tab_info_))); |
- } |
- |
- UMA_HISTOGRAM_TIMES("SB2.DownloadDuration", |
- base::TimeTicks::Now() - download_start_time_); |
-} |
- |
} // namespace content |