Index: chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc |
diff --git a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc |
index 39d3cc3f462c678aebfff654073563253d9166aa..403cbbf3ca063f06b9ae59dbd7a9447482af0352 100644 |
--- a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc |
+++ b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc |
@@ -11,7 +11,10 @@ |
#include "base/bind.h" |
#include "base/bind_helpers.h" |
#include "base/i18n/rtl.h" |
+#include "base/i18n/time_formatting.h" |
#include "base/logging.h" |
+#include "base/memory/singleton.h" |
+#include "base/metrics/field_trial.h" |
#include "base/metrics/histogram.h" |
#include "base/prefs/pref_service.h" |
#include "base/strings/string_number_conversions.h" |
@@ -19,8 +22,10 @@ |
#include "base/strings/utf_string_conversions.h" |
#include "base/supports_user_data.h" |
#include "base/threading/thread.h" |
+#include "base/value_conversions.h" |
#include "base/values.h" |
#include "chrome/browser/browser_process.h" |
+#include "chrome/browser/download/download_crx_util.h" |
#include "chrome/browser/download/download_danger_prompt.h" |
#include "chrome/browser/download/download_history.h" |
#include "chrome/browser/download/download_item_model.h" |
@@ -29,6 +34,8 @@ |
#include "chrome/browser/download/download_service.h" |
#include "chrome/browser/download/download_service_factory.h" |
#include "chrome/browser/download/drag_download_item.h" |
+#include "chrome/browser/extensions/api/downloads/downloads_api.h" |
+#include "chrome/browser/extensions/extension_service.h" |
#include "chrome/browser/platform_util.h" |
#include "chrome/browser/profiles/profile.h" |
#include "chrome/browser/ui/webui/fileicon_source.h" |
@@ -37,19 +44,25 @@ |
#include "chrome/common/url_constants.h" |
#include "content/public/browser/browser_thread.h" |
#include "content/public/browser/download_item.h" |
-#include "content/public/browser/download_manager.h" |
#include "content/public/browser/url_data_source.h" |
#include "content/public/browser/user_metrics.h" |
#include "content/public/browser/web_contents.h" |
#include "content/public/browser/web_ui.h" |
+#include "extensions/browser/extension_system.h" |
#include "net/base/filename_util.h" |
+#include "third_party/icu/source/i18n/unicode/datefmt.h" |
#include "ui/base/l10n/time_format.h" |
#include "ui/gfx/image/image.h" |
using base::UserMetricsAction; |
+using content::BrowserContext; |
using content::BrowserThread; |
namespace { |
+ |
+// Maximum number of downloads to show. TODO(glen): Remove this and instead |
+// stuff the downloads down the pipe slowly. |
+size_t kMaxNumberOfDownloads = 150; |
enum DownloadsDOMEvent { |
DOWNLOADS_DOM_EVENT_GET_DOWNLOADS = 0, |
@@ -73,11 +86,203 @@ |
DOWNLOADS_DOM_EVENT_MAX); |
} |
+// Returns a string constant to be used as the |danger_type| value in |
+// CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE, |
+// DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the |
+// |danger_type| value is only defined if the value of |state| is |DANGEROUS|. |
+const char* GetDangerTypeString(content::DownloadDangerType danger_type) { |
+ switch (danger_type) { |
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: |
+ return "DANGEROUS_FILE"; |
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: |
+ return "DANGEROUS_URL"; |
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
+ return "DANGEROUS_CONTENT"; |
+ case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: |
+ return "UNCOMMON_CONTENT"; |
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: |
+ return "DANGEROUS_HOST"; |
+ case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: |
+ return "POTENTIALLY_UNWANTED"; |
+ default: |
+ // Don't return a danger type string if it is NOT_DANGEROUS or |
+ // MAYBE_DANGEROUS_CONTENT. |
+ NOTREACHED(); |
+ return ""; |
+ } |
+} |
+ |
+// TODO(dbeam): if useful elsewhere, move to base/i18n/time_formatting.h? |
+base::string16 TimeFormatLongDate(const base::Time& time) { |
+ scoped_ptr<icu::DateFormat> formatter( |
+ icu::DateFormat::createDateInstance(icu::DateFormat::kLong)); |
+ icu::UnicodeString date_string; |
+ formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string); |
+ return base::string16(date_string.getBuffer(), |
+ static_cast<size_t>(date_string.length())); |
+} |
+ |
+// Returns a JSON dictionary containing some of the attributes of |download|. |
+// The JSON dictionary will also have a field "id" set to |id|, and a field |
+// "otr" set to |incognito|. |
+base::DictionaryValue* CreateDownloadItemValue( |
+ content::DownloadItem* download_item, |
+ bool incognito) { |
+ // TODO(asanka): Move towards using download_model here for getting status and |
+ // progress. The difference currently only matters to Drive downloads and |
+ // those don't show up on the downloads page, but should. |
+ DownloadItemModel download_model(download_item); |
+ |
+ // The items which are to be written into file_value are also described in |
+ // chrome/browser/resources/downloads/downloads.js in @typedef for |
+ // BackendDownloadObject. Please update it whenever you add or remove |
+ // any keys in file_value. |
+ base::DictionaryValue* file_value = new base::DictionaryValue(); |
+ |
+ file_value->SetInteger( |
+ "started", static_cast<int>(download_item->GetStartTime().ToTimeT())); |
+ file_value->SetString( |
+ "since_string", ui::TimeFormat::RelativeDate( |
+ download_item->GetStartTime(), NULL)); |
+ |
+ base::Time start_time = download_item->GetStartTime(); |
+ base::string16 date_string = TimeFormatLongDate(start_time); |
+ file_value->SetString("date_string", date_string); |
+ |
+ file_value->SetString("id", base::Uint64ToString(download_item->GetId())); |
+ |
+ base::FilePath download_path(download_item->GetTargetFilePath()); |
+ file_value->Set("file_path", base::CreateFilePathValue(download_path)); |
+ file_value->SetString("file_url", |
+ net::FilePathToFileURL(download_path).spec()); |
+ |
+ extensions::DownloadedByExtension* by_ext = |
+ extensions::DownloadedByExtension::Get(download_item); |
+ std::string by_ext_id; |
+ std::string by_ext_name; |
+ if (by_ext) { |
+ by_ext_id = by_ext->id(); |
+ // TODO(dbeam): why doesn't DownloadsByExtension::name() return a string16? |
+ by_ext_name = by_ext->name(); |
+ |
+ // Lookup the extension's current name() in case the user changed their |
+ // language. This won't work if the extension was uninstalled, so the name |
+ // might be the wrong language. |
+ bool include_disabled = true; |
+ const extensions::Extension* extension = extensions::ExtensionSystem::Get( |
+ Profile::FromBrowserContext(download_item->GetBrowserContext()))-> |
+ extension_service()->GetExtensionById(by_ext->id(), include_disabled); |
+ if (extension) |
+ file_value->SetString("by_ext_name", extension->name()); |
+ } |
+ file_value->SetString("by_ext_id", by_ext_id); |
+ file_value->SetString("by_ext_name", by_ext_name); |
+ |
+ // Keep file names as LTR. |
+ base::string16 file_name = |
+ download_item->GetFileNameToReportUser().LossyDisplayName(); |
+ file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name); |
+ file_value->SetString("file_name", file_name); |
+ file_value->SetString("url", download_item->GetURL().spec()); |
+ file_value->SetBoolean("otr", incognito); |
+ file_value->SetInteger("total", static_cast<int>( |
+ download_item->GetTotalBytes())); |
+ file_value->SetBoolean("file_externally_removed", |
+ download_item->GetFileExternallyRemoved()); |
+ file_value->SetBoolean("resume", download_item->CanResume()); |
+ |
+ const char* danger_type = ""; |
+ base::string16 last_reason_text; |
+ // -2 is invalid, -1 means indeterminate, and 0-100 are in-progress. |
+ int percent = -2; |
+ base::string16 progress_status_text; |
+ bool retry = false; |
+ const char* state = nullptr; |
+ |
+ switch (download_item->GetState()) { |
+ case content::DownloadItem::IN_PROGRESS: { |
+ if (download_item->IsDangerous()) { |
+ state = "DANGEROUS"; |
+ // These are the only danger states that the UI is equipped to handle. |
+ DCHECK(download_item->GetDangerType() == |
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || |
+ download_item->GetDangerType() == |
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || |
+ download_item->GetDangerType() == |
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || |
+ download_item->GetDangerType() == |
+ content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || |
+ download_item->GetDangerType() == |
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || |
+ download_item->GetDangerType() == |
+ content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED); |
+ danger_type = GetDangerTypeString(download_item->GetDangerType()); |
+ } else if (download_item->IsPaused()) { |
+ state = "PAUSED"; |
+ } else { |
+ state = "IN_PROGRESS"; |
+ } |
+ progress_status_text = download_model.GetTabProgressStatusText(); |
+ percent = download_item->PercentComplete(); |
+ break; |
+ } |
+ |
+ case content::DownloadItem::INTERRUPTED: |
+ state = "INTERRUPTED"; |
+ progress_status_text = download_model.GetTabProgressStatusText(); |
+ |
+ if (download_item->CanResume()) |
+ percent = download_item->PercentComplete(); |
+ |
+ last_reason_text = download_model.GetInterruptReasonText(); |
+ if (content::DOWNLOAD_INTERRUPT_REASON_CRASH == |
+ download_item->GetLastReason() && !download_item->CanResume()) { |
+ retry = true; |
+ } |
+ break; |
+ |
+ case content::DownloadItem::CANCELLED: |
+ state = "CANCELLED"; |
+ retry = true; |
+ break; |
+ |
+ case content::DownloadItem::COMPLETE: |
+ DCHECK(!download_item->IsDangerous()); |
+ state = "COMPLETE"; |
+ break; |
+ |
+ case content::DownloadItem::MAX_DOWNLOAD_STATE: |
+ NOTREACHED(); |
+ } |
+ |
+ DCHECK(state); |
+ |
+ file_value->SetString("danger_type", danger_type); |
+ file_value->SetString("last_reason_text", last_reason_text); |
+ file_value->SetInteger("percent", percent); |
+ file_value->SetString("progress_status_text", progress_status_text); |
+ file_value->SetBoolean("retry", retry); |
+ file_value->SetString("state", state); |
+ |
+ return file_value; |
+} |
+ |
+// Filters out extension downloads and downloads that don't have a filename yet. |
+bool IsDownloadDisplayable(const content::DownloadItem& item) { |
+ return !download_crx_util::IsExtensionDownload(item) && |
+ !item.IsTemporary() && |
+ !item.GetFileNameToReportUser().empty() && |
+ !item.GetTargetFilePath().empty() && |
+ DownloadItemModel( |
+ const_cast<content::DownloadItem*>(&item)).ShouldShowInShelf(); |
+} |
+ |
} // namespace |
MdDownloadsDOMHandler::MdDownloadsDOMHandler( |
- content::DownloadManager* download_manager, content::WebUI* web_ui) |
- : list_tracker_(download_manager, web_ui), |
+ content::DownloadManager* download_manager) |
+ : download_manager_(download_manager), |
+ update_scheduled_(false), |
weak_ptr_factory_(this) { |
// Create our fileicon data source. |
Profile* profile = Profile::FromBrowserContext( |
@@ -133,19 +338,95 @@ |
weak_ptr_factory_.GetWeakPtr())); |
} |
-void MdDownloadsDOMHandler::RenderViewReused( |
- content::RenderViewHost* render_view_host) { |
- list_tracker_.Stop(); |
+void MdDownloadsDOMHandler::OnDownloadCreated( |
+ content::DownloadManager* manager, content::DownloadItem* download_item) { |
+ if (IsDownloadDisplayable(*download_item)) |
+ ScheduleSendCurrentDownloads(); |
+ else |
+ new_downloads_.insert(download_item->GetId()); |
+} |
+ |
+void MdDownloadsDOMHandler::OnDownloadUpdated( |
+ content::DownloadManager* manager, |
+ content::DownloadItem* download_item) { |
+ if (update_scheduled_) |
+ return; |
+ |
+ bool showing_new_item = false; |
+ |
+ if (new_downloads_.count(download_item->GetId())) { |
+ // A new download (that the page doesn't know about yet) has been updated. |
+ if (!IsDownloadDisplayable(*download_item)) { |
+ // Item isn't ready to be displayed yet. Wait until it is. |
+ return; |
+ } |
+ |
+ new_downloads_.erase(download_item->GetId()); |
+ showing_new_item = true; |
+ } |
+ |
+ if (showing_new_item || DownloadItemModel(download_item).IsBeingRevived() || |
+ !IsDownloadDisplayable(*download_item)) { |
+ // A download will be shown or hidden by this update. Resend the list. |
+ ScheduleSendCurrentDownloads(); |
+ return; |
+ } |
+ |
+ if (search_terms_ && !search_terms_->empty()) { |
+ // Don't CallUpdateItem() if download_item doesn't match |
+ // search_terms_. |
+ // TODO(benjhayden): Consider splitting MatchesQuery() out to a function. |
+ content::DownloadManager::DownloadVector all_items, filtered_items; |
+ all_items.push_back(download_item); |
+ DownloadQuery query; |
+ query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_); |
+ query.Search(all_items.begin(), all_items.end(), &filtered_items); |
+ if (filtered_items.empty()) |
+ return; |
+ } |
+ |
+ DCHECK(manager); |
+ scoped_ptr<base::DictionaryValue> item(CreateDownloadItemValue( |
+ download_item, |
+ original_notifier_ && manager == GetMainNotifierManager())); |
+ CallUpdateItem(*item); |
+} |
+ |
+void MdDownloadsDOMHandler::OnDownloadRemoved( |
+ content::DownloadManager* manager, |
+ content::DownloadItem* download_item) { |
+ if (!DownloadItemModel(download_item).ShouldShowInShelf()) |
+ return; |
+ |
+ // This relies on |download_item| being removed from DownloadManager in this |
+ // MessageLoop iteration. |download_item| may not have been removed from |
+ // DownloadManager when OnDownloadRemoved() is fired, so bounce off the |
+ // MessageLoop to give it a chance to be removed. SendCurrentDownloads() looks |
+ // at all downloads, and we do not tell it that |download_item| is being |
+ // removed. If DownloadManager is ever changed to not immediately remove |
+ // |download_item| from its map when OnDownloadRemoved is sent, then |
+ // MdDownloadsDOMHandler::OnDownloadRemoved() will need to explicitly tell |
+ // SendCurrentDownloads() that |download_item| was removed. A |
+ // SupportsUserData::Data would be the correct way to do this. |
+ ScheduleSendCurrentDownloads(); |
} |
void MdDownloadsDOMHandler::HandleGetDownloads(const base::ListValue* args) { |
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS); |
- |
- bool terms_changed = list_tracker_.SetSearchTerms(*args); |
- if (terms_changed) |
- list_tracker_.CallClearAll(); |
- |
- list_tracker_.Start(); |
+ search_terms_.reset(args && !args->empty() ? args->DeepCopy() : NULL); |
+ ScheduleSendCurrentDownloads(); |
+ |
+ if (!main_notifier_) { |
+ main_notifier_.reset(new AllDownloadItemNotifier(download_manager_, this)); |
+ |
+ Profile* profile = Profile::FromBrowserContext( |
+ download_manager_->GetBrowserContext()); |
+ if (profile->IsOffTheRecord()) { |
+ original_notifier_.reset(new AllDownloadItemNotifier( |
+ BrowserContext::GetDownloadManager(profile->GetOriginalProfile()), |
+ this)); |
+ } |
+ } |
} |
void MdDownloadsDOMHandler::HandleOpenFile(const base::ListValue* args) { |
@@ -268,17 +549,12 @@ |
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL); |
- list_tracker_.CallClearAll(); |
- list_tracker_.Stop(); |
- |
DownloadVector downloads; |
if (GetMainNotifierManager()) |
GetMainNotifierManager()->GetAllDownloads(&downloads); |
if (GetOriginalNotifierManager()) |
GetOriginalNotifierManager()->GetAllDownloads(&downloads); |
RemoveDownloads(downloads); |
- |
- list_tracker_.Start(); |
} |
void MdDownloadsDOMHandler::RemoveDownloads(const DownloadVector& to_remove) { |
@@ -314,14 +590,29 @@ |
// MdDownloadsDOMHandler, private: -------------------------------------------- |
+void MdDownloadsDOMHandler::ScheduleSendCurrentDownloads() { |
+ // Don't call SendCurrentDownloads() every time anything changes. Batch them |
+ // together instead. This may handle hundreds of OnDownloadDestroyed() calls |
+ // in a single UI message loop iteration when the user Clears All downloads. |
+ if (update_scheduled_) |
+ return; |
+ |
+ update_scheduled_ = true; |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&MdDownloadsDOMHandler::SendCurrentDownloads, |
+ weak_ptr_factory_.GetWeakPtr())); |
+} |
+ |
content::DownloadManager* MdDownloadsDOMHandler::GetMainNotifierManager() |
const { |
- return list_tracker_.GetMainNotifierManager(); |
+ return main_notifier_ ? main_notifier_->GetManager() : nullptr; |
} |
content::DownloadManager* MdDownloadsDOMHandler::GetOriginalNotifierManager() |
const { |
- return list_tracker_.GetOriginalNotifierManager(); |
+ return original_notifier_ ? original_notifier_->GetManager() : nullptr; |
} |
void MdDownloadsDOMHandler::FinalizeRemovals() { |
@@ -335,6 +626,37 @@ |
download->Remove(); |
} |
} |
+} |
+ |
+void MdDownloadsDOMHandler::SendCurrentDownloads() { |
+ update_scheduled_ = false; |
+ |
+ content::DownloadManager::DownloadVector all_items, filtered_items; |
+ if (GetMainNotifierManager()) { |
+ GetMainNotifierManager()->GetAllDownloads(&all_items); |
+ GetMainNotifierManager()->CheckForHistoryFilesRemoval(); |
+ } |
+ if (GetOriginalNotifierManager()) { |
+ GetOriginalNotifierManager()->GetAllDownloads(&all_items); |
+ GetOriginalNotifierManager()->CheckForHistoryFilesRemoval(); |
+ } |
+ |
+ DownloadQuery query; |
+ if (search_terms_ && !search_terms_->empty()) |
+ query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_); |
+ query.AddFilter(base::Bind(&IsDownloadDisplayable)); |
+ query.AddSorter(DownloadQuery::SORT_START_TIME, DownloadQuery::DESCENDING); |
+ query.Limit(kMaxNumberOfDownloads); |
+ query.Search(all_items.begin(), all_items.end(), &filtered_items); |
+ |
+ base::ListValue results_value; |
+ for (auto* item : filtered_items) { |
+ results_value.Append(CreateDownloadItemValue( |
+ item, |
+ original_notifier_ && GetMainNotifierManager() && |
+ GetMainNotifierManager()->GetDownload(item->GetId()) == item)); |
+ } |
+ CallUpdateAll(results_value); |
} |
void MdDownloadsDOMHandler::ShowDangerPrompt( |
@@ -400,3 +722,11 @@ |
content::WebContents* MdDownloadsDOMHandler::GetWebUIWebContents() { |
return web_ui()->GetWebContents(); |
} |
+ |
+void MdDownloadsDOMHandler::CallUpdateAll(const base::ListValue& list) { |
+ web_ui()->CallJavascriptFunction("downloads.Manager.updateAll", list); |
+} |
+ |
+void MdDownloadsDOMHandler::CallUpdateItem(const base::DictionaryValue& item) { |
+ web_ui()->CallJavascriptFunction("downloads.Manager.updateItem", item); |
+} |