[Downloads] Always call DM::StartDownload() for explicit downloads.
Every explicitly initiated download should be represented in the UI with
the exception of downloads which are hidden or downloads which were
cancelled by the user or were blocked by a throttle. This is currently
not true for downloads whose requests fail before a response is
available (e.g. because of the hostname not being resolved) because the
DownloadManager is only notified during
DownloadResourceHandler::OnResponseStarted(). Failure to notify the
DownloadManager results in no DownloadItem being created, and
subsequently no UI being presented to the user.
The current behavior is also non-ideal for downloads resumption. When a
partial download request receives a HTTP 412 response, the current logic
invokes DownloadManager::StartDownload() with no indication that the
Content-* headers received do not apply to the desired entity. Therefore
the DownloadItem's ETag and Last-Modified headers can be incorrectly
reset after a pre-condition failure.
This CL makes the following changes:
- DownloadItem::Start() now consumes a DownloadCreateInfo since multiple
requests can be associated with a single DownloadItem due to
resumption. Each new request results in a Start() call.
- DownloadResourceHandler invokes DownloadManager::StartDownload() even
when no OnResponseStarted is received. The only exception is if the
associated renderer goes away between the time the download request is
sent and the time the response is received.
- UrlDownloader always invokes DownloadManager::StartDownload() for all
initiated download requests without exception.
- DownloadItemImpl invokes the delegate and requests a filename whenever
one is needed.
- DownloadTargetDeterminer generates a valid filename even if the
DownloadItem is not in-progress.
- DownloadUrlParameters::OnStartedCallback could now receive a valid
DownloadItem even when the request fails.
- ResourceDispatcherHost::BeginDownload() is no longer a public API. All
wannabe downloaders must now go through DownloadManager to initiate a
download.
- ResourceDispatcherHostImpl is no longer responsible for invoking
OnStartedCallback or pass through download parameters.
DownloadRequestCore attaches the data direcly to explicit downloads.
Ideally, explicit download initiation would first start by creating a
DownloadItem. We aren't quite there yet due to the fact that consumers
of the download system make assumptions about the existence of a
download item. I.e. the fallacy that the existence of a download item
must mean that that download is a candidate to be presented to the UI or
other consumers. Once these consumers are fixed, then we can move all
explicit download request initiated into DownloadItem, thus simplifying
much of this logic.
BUG=7648
Review URL: https://codereview.chromium.org/148133007
Cr-Commit-Position: refs/heads/master@{#376487}
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index 69967fa..9f7029d 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -55,90 +55,47 @@
uint32_t download_id,
base::WeakPtr<DownloadManagerImpl> download_manager) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
- // ResourceDispatcherHost{Base} is-not-a URLRequest::Delegate, and
- // DownloadUrlParameters can-not include resource_dispatcher_host_impl.h, so
- // we must down cast. RDHI is the only subclass of RDH as of 2012 May 4.
- scoped_ptr<net::URLRequest> request(
- params->resource_context()->GetRequestContext()->CreateRequest(
- params->url(), net::DEFAULT_PRIORITY, NULL));
- request->set_method(params->method());
- if (!params->post_body().empty()) {
- const std::string& body = params->post_body();
- scoped_ptr<net::UploadElementReader> reader(
- net::UploadOwnedBytesElementReader::CreateWithString(body));
- request->set_upload(
- net::ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
- }
- if (params->post_id() >= 0) {
- // The POST in this case does not have an actual body, and only works
- // when retrieving data from cache. This is done because we don't want
- // to do a re-POST without user consent, and currently don't have a good
- // plan on how to display the UI for that.
- DCHECK(params->prefer_cache());
- DCHECK_EQ("POST", params->method());
- std::vector<scoped_ptr<net::UploadElementReader>> element_readers;
- request->set_upload(make_scoped_ptr(new net::ElementsUploadDataStream(
- std::move(element_readers), params->post_id())));
- }
- // If we're not at the beginning of the file, retrieve only the remaining
- // portion.
- bool has_last_modified = !params->last_modified().empty();
- bool has_etag = !params->etag().empty();
+ scoped_ptr<net::URLRequest> url_request =
+ DownloadRequestCore::CreateRequestOnIOThread(download_id, params.get());
- // If we've asked for a range, we want to make sure that we only
- // get that range if our current copy of the information is good.
- // We shouldn't be asked to continue if we don't have a verifier.
- DCHECK(params->offset() == 0 || has_etag || has_last_modified);
-
- if (params->offset() > 0 && (has_etag || has_last_modified)) {
- request->SetExtraRequestHeaderByName(
- "Range",
- base::StringPrintf("bytes=%" PRId64 "-", params->offset()),
- true);
-
- // In accordance with RFC 2616 Section 14.27, use If-Range to specify that
- // the server return the entire entity if the validator doesn't match.
- // Last-Modified can be used in the absence of ETag as a validator if the
- // response headers satisfied the HttpUtil::HasStrongValidators() predicate.
- //
- // This function assumes that HasStrongValidators() was true and that the
- // ETag and Last-Modified header values supplied are valid.
- request->SetExtraRequestHeaderByName(
- "If-Range", has_etag ? params->etag() : params->last_modified(), true);
- }
-
- for (DownloadUrlParameters::RequestHeadersType::const_iterator iter
- = params->request_headers_begin();
- iter != params->request_headers_end();
- ++iter) {
- request->SetExtraRequestHeaderByName(
- iter->first, iter->second, false /*overwrite*/);
- }
-
- scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo());
- save_info->file_path = params->file_path();
- save_info->suggested_name = params->suggested_name();
- save_info->offset = params->offset();
- save_info->hash_state = params->hash_state();
- save_info->prompt_for_save_location = params->prompt();
- save_info->file = params->GetFile();
-
+ // If there's a valid renderer process associated with the request, then the
+ // request should be driven by the ResourceLoader. Pass it over to the
+ // ResourceDispatcherHostImpl which will in turn pass it along to the
+ // ResourceLoader.
if (params->render_process_host_id() != -1) {
- ResourceDispatcherHost::Get()->BeginDownload(
- std::move(request), params->referrer(), params->content_initiated(),
- params->resource_context(), params->render_process_host_id(),
- params->render_view_host_routing_id(),
- params->render_frame_host_routing_id(), params->prefer_cache(),
- params->do_not_prompt_for_login(), std::move(save_info), download_id,
- params->callback());
+ DownloadInterruptReason reason =
+ ResourceDispatcherHostImpl::Get()->BeginDownload(
+ std::move(url_request), params->referrer(),
+ params->content_initiated(), params->resource_context(),
+ params->render_process_host_id(),
+ params->render_view_host_routing_id(),
+ params->render_frame_host_routing_id(),
+ params->do_not_prompt_for_login());
+
+ // If the download was accepted, the DownloadResourceHandler is now
+ // responsible for driving the request to completion.
+ if (reason == DOWNLOAD_INTERRUPT_REASON_NONE)
+ return nullptr;
+
+ // Otherwise, create an interrupted download.
+ scoped_ptr<DownloadCreateInfo> failed_created_info(
+ new DownloadCreateInfo(base::Time::Now(), net::BoundNetLog(),
+ make_scoped_ptr(new DownloadSaveInfo)));
+ failed_created_info->url_chain.push_back(params->url());
+ failed_created_info->result = reason;
+ scoped_ptr<ByteStreamReader> empty_byte_stream;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DownloadManager::StartDownload, download_manager,
+ base::Passed(&failed_created_info),
+ base::Passed(&empty_byte_stream), params->callback()));
return nullptr;
}
+
return scoped_ptr<UrlDownloader, BrowserThread::DeleteOnIOThread>(
- UrlDownloader::BeginDownload(download_manager, std::move(request),
- params->referrer(), params->prefer_cache(),
- std::move(save_info), download_id,
- params->callback())
+ UrlDownloader::BeginDownload(download_manager, std::move(url_request),
+ params->referrer())
.release());
}
@@ -344,6 +301,11 @@
const DownloadUrlParameters::OnStartedCallback& on_started) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(info);
+ // |stream| is only non-nil if the download request was successful.
+ DCHECK((info->result == DOWNLOAD_INTERRUPT_REASON_NONE && stream.get()) ||
+ (info->result != DOWNLOAD_INTERRUPT_REASON_NONE && !stream.get()));
+ DVLOG(20) << __FUNCTION__ << "()"
+ << " result=" << DownloadInterruptReasonToString(info->result);
uint32_t download_id = info->download_id;
const bool new_download = (download_id == content::DownloadItem::kInvalidId);
base::Callback<void(uint32_t)> got_id(base::Bind(
@@ -379,13 +341,12 @@
if (!on_started.is_null())
on_started.Run(NULL, DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
// The ByteStreamReader lives and dies on the FILE thread.
- BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE,
- stream.release());
+ if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE)
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE,
+ stream.release());
return;
}
download = item_iterator->second;
- DCHECK_EQ(download->GetState(), DownloadItem::IN_PROGRESS);
- download->MergeOriginInfoOnResume(*info);
}
base::FilePath default_download_directory;
@@ -396,20 +357,24 @@
&default_download_directory, &skip_dir_check);
}
- // Create the download file and start the download.
- scoped_ptr<DownloadFile> download_file(file_factory_->CreateFile(
- std::move(info->save_info), default_download_directory, info->url(),
- info->referrer_url, delegate_ && delegate_->GenerateFileHash(),
- std::move(stream), download->GetBoundNetLog(),
- download->DestinationObserverAsWeakPtr()));
+ scoped_ptr<DownloadFile> download_file;
- // Attach the client ID identifying the app to the AV system.
- if (download_file.get() && delegate_) {
- download_file->SetClientGuid(
- delegate_->ApplicationClientIdForFileScanning());
+ if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE) {
+ DCHECK(stream.get());
+ download_file.reset(file_factory_->CreateFile(
+ *info->save_info, default_download_directory, info->url(),
+ info->referrer_url, delegate_ && delegate_->GenerateFileHash(),
+ std::move(info->save_info->file), std::move(stream),
+ download->GetBoundNetLog(), download->DestinationObserverAsWeakPtr()));
+
+ if (download_file.get() && delegate_) {
+ download_file->SetClientGuid(
+ delegate_->ApplicationClientIdForFileScanning());
+ }
}
- download->Start(std::move(download_file), std::move(info->request_handle));
+ download->Start(std::move(download_file), std::move(info->request_handle),
+ *info);
// For interrupted downloads, Start() will transition the state to
// IN_PROGRESS and consumers will be notified via OnDownloadUpdated().