Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8694)

Unified Diff: chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc

Issue 1480263002: Revert of MD Downloads: track downloads in C++, dispatch discrete JS updates (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld 408576698