| 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);
|
| +}
|
|
|