Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/ui/webui/md_downloads/downloads_list_tracker.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/bind_helpers.h" | |
| 9 #include "base/i18n/rtl.h" | |
| 10 #include "base/strings/string16.h" | |
| 11 #include "base/strings/string_number_conversions.h" | |
| 12 #include "base/time/time.h" | |
| 13 #include "base/value_conversions.h" | |
| 14 #include "chrome/browser/download/all_download_item_notifier.h" | |
| 15 #include "chrome/browser/download/download_crx_util.h" | |
| 16 #include "chrome/browser/download/download_item_model.h" | |
| 17 #include "chrome/browser/download/download_query.h" | |
| 18 #include "chrome/browser/extensions/api/downloads/downloads_api.h" | |
| 19 #include "chrome/browser/extensions/extension_service.h" | |
| 20 #include "chrome/browser/profiles/profile.h" | |
| 21 #include "content/public/browser/browser_context.h" | |
| 22 #include "content/public/browser/download_item.h" | |
| 23 #include "content/public/browser/download_manager.h" | |
| 24 #include "content/public/browser/web_ui.h" | |
| 25 #include "extensions/browser/extension_system.h" | |
| 26 #include "net/base/filename_util.h" | |
| 27 #include "third_party/icu/source/i18n/unicode/datefmt.h" | |
| 28 #include "ui/base/l10n/time_format.h" | |
| 29 | |
| 30 using content::BrowserContext; | |
| 31 using content::DownloadItem; | |
| 32 using content::DownloadManager; | |
| 33 | |
| 34 using DownloadVector = std::vector<DownloadItem*>; | |
| 35 | |
| 36 namespace { | |
| 37 | |
| 38 // Returns a string constant to be used as the |danger_type| value in | |
| 39 // CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE, | |
| 40 // DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the | |
| 41 // |danger_type| value is only defined if the value of |state| is |DANGEROUS|. | |
| 42 const char* GetDangerTypeString(content::DownloadDangerType danger_type) { | |
| 43 switch (danger_type) { | |
| 44 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: | |
| 45 return "DANGEROUS_FILE"; | |
| 46 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: | |
| 47 return "DANGEROUS_URL"; | |
| 48 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: | |
| 49 return "DANGEROUS_CONTENT"; | |
| 50 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: | |
| 51 return "UNCOMMON_CONTENT"; | |
| 52 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: | |
| 53 return "DANGEROUS_HOST"; | |
| 54 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: | |
| 55 return "POTENTIALLY_UNWANTED"; | |
| 56 default: | |
| 57 // Don't return a danger type string if it is NOT_DANGEROUS or | |
| 58 // MAYBE_DANGEROUS_CONTENT. | |
| 59 NOTREACHED(); | |
| 60 return ""; | |
| 61 } | |
| 62 } | |
| 63 | |
| 64 // TODO(dbeam): if useful elsewhere, move to base/i18n/time_formatting.h? | |
| 65 base::string16 TimeFormatLongDate(const base::Time& time) { | |
| 66 scoped_ptr<icu::DateFormat> formatter( | |
| 67 icu::DateFormat::createDateInstance(icu::DateFormat::kLong)); | |
| 68 icu::UnicodeString date_string; | |
| 69 formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string); | |
| 70 return base::string16(date_string.getBuffer(), | |
| 71 static_cast<size_t>(date_string.length())); | |
| 72 } | |
| 73 | |
| 74 } // namespace | |
| 75 | |
| 76 DownloadsListTracker::DownloadsListTracker( | |
| 77 DownloadManager* download_manager, | |
| 78 content::WebUI* web_ui) | |
| 79 : main_notifier_(download_manager, this), | |
| 80 web_ui_(web_ui), | |
| 81 should_show_(base::Bind(&DownloadsListTracker::ShouldShow, | |
| 82 base::Unretained(this))) { | |
| 83 Init(); | |
| 84 } | |
| 85 | |
| 86 DownloadsListTracker::~DownloadsListTracker() {} | |
| 87 | |
| 88 void DownloadsListTracker::CallClearAll() { | |
| 89 if (sending_updates_) | |
| 90 web_ui_->CallJavascriptFunction("downloads.Manager.clearAll"); | |
| 91 } | |
| 92 | |
| 93 bool DownloadsListTracker::SetSearchTerms(const base::ListValue& search_terms) { | |
| 94 if (search_terms_.Equals(&search_terms)) | |
| 95 return false; | |
| 96 | |
| 97 search_terms_.Swap(search_terms.DeepCopy()); | |
| 98 RebuildSortedSet(); | |
| 99 return true; | |
| 100 } | |
| 101 | |
| 102 void DownloadsListTracker::Start() { | |
| 103 sending_updates_ = true; | |
| 104 // TODO(dbeam): paging logic. | |
| 105 InsertItems(sorted_visible_items_.begin(), sorted_visible_items_.end()); | |
| 106 } | |
| 107 | |
| 108 void DownloadsListTracker::Stop() { | |
| 109 sending_updates_ = false; | |
| 110 } | |
| 111 | |
| 112 DownloadManager* DownloadsListTracker::GetMainNotifierManager() const { | |
| 113 return main_notifier_.GetManager(); | |
| 114 } | |
| 115 | |
| 116 DownloadManager* DownloadsListTracker::GetOriginalNotifierManager() const { | |
| 117 return original_notifier_ ? original_notifier_->GetManager() : nullptr; | |
| 118 } | |
| 119 | |
| 120 void DownloadsListTracker::OnDownloadCreated(DownloadManager* manager, | |
| 121 DownloadItem* download_item) { | |
| 122 if (should_show_.Run(*download_item)) | |
| 123 InsertItem(sorted_visible_items_.insert(download_item).first); | |
| 124 } | |
| 125 | |
| 126 void DownloadsListTracker::OnDownloadUpdated(DownloadManager* manager, | |
| 127 DownloadItem* download_item) { | |
| 128 auto current_position = sorted_visible_items_.find(download_item); | |
| 129 bool is_showing = current_position != sorted_visible_items_.end(); | |
| 130 bool should_show = should_show_.Run(*download_item); | |
| 131 | |
| 132 if (!is_showing && should_show) | |
| 133 InsertItem(sorted_visible_items_.insert(download_item).first); | |
| 134 else if (is_showing && !should_show) | |
| 135 RemoveItem(current_position); | |
| 136 else if (is_showing) | |
| 137 CallUpdateItem(current_position); | |
| 138 } | |
| 139 | |
| 140 void DownloadsListTracker::OnDownloadRemoved(DownloadManager* manager, | |
| 141 DownloadItem* download_item) { | |
| 142 auto current_position = sorted_visible_items_.find(download_item); | |
| 143 if (current_position != sorted_visible_items_.end()) | |
| 144 RemoveItem(current_position); | |
| 145 } | |
| 146 | |
| 147 DownloadsListTracker::DownloadsListTracker( | |
| 148 DownloadManager* download_manager, | |
| 149 content::WebUI* web_ui, | |
| 150 base::Callback<bool(const DownloadItem&)> should_show) | |
| 151 : main_notifier_(download_manager, this), | |
| 152 web_ui_(web_ui), | |
| 153 should_show_(should_show) { | |
| 154 Init(); | |
| 155 } | |
| 156 | |
| 157 scoped_ptr<base::DictionaryValue> DownloadsListTracker::CreateDownloadItemValue( | |
| 158 content::DownloadItem* download_item) const { | |
| 159 // TODO(asanka): Move towards using download_model here for getting status and | |
| 160 // progress. The difference currently only matters to Drive downloads and | |
| 161 // those don't show up on the downloads page, but should. | |
| 162 DownloadItemModel download_model(download_item); | |
| 163 | |
| 164 // The items which are to be written into file_value are also described in | |
| 165 // chrome/browser/resources/downloads/downloads.js in @typedef for | |
| 166 // BackendDownloadObject. Please update it whenever you add or remove | |
| 167 // any keys in file_value. | |
| 168 scoped_ptr<base::DictionaryValue> file_value(new base::DictionaryValue); | |
| 169 | |
| 170 file_value->SetInteger( | |
| 171 "started", static_cast<int>(download_item->GetStartTime().ToTimeT())); | |
| 172 file_value->SetString( | |
| 173 "since_string", ui::TimeFormat::RelativeDate( | |
| 174 download_item->GetStartTime(), NULL)); | |
| 175 | |
| 176 base::Time start_time = download_item->GetStartTime(); | |
| 177 base::string16 date_string = TimeFormatLongDate(start_time); | |
| 178 file_value->SetString("date_string", date_string); | |
| 179 | |
| 180 file_value->SetString("id", base::Uint64ToString(download_item->GetId())); | |
| 181 | |
| 182 base::FilePath download_path(download_item->GetTargetFilePath()); | |
| 183 file_value->Set("file_path", base::CreateFilePathValue(download_path)); | |
| 184 file_value->SetString("file_url", | |
| 185 net::FilePathToFileURL(download_path).spec()); | |
| 186 | |
| 187 extensions::DownloadedByExtension* by_ext = | |
| 188 extensions::DownloadedByExtension::Get(download_item); | |
| 189 std::string by_ext_id; | |
| 190 std::string by_ext_name; | |
| 191 if (by_ext) { | |
| 192 by_ext_id = by_ext->id(); | |
| 193 // TODO(dbeam): why doesn't DownloadsByExtension::name() return a string16? | |
| 194 by_ext_name = by_ext->name(); | |
| 195 | |
| 196 // Lookup the extension's current name() in case the user changed their | |
| 197 // language. This won't work if the extension was uninstalled, so the name | |
| 198 // might be the wrong language. | |
| 199 bool include_disabled = true; | |
| 200 const extensions::Extension* extension = extensions::ExtensionSystem::Get( | |
| 201 Profile::FromBrowserContext(download_item->GetBrowserContext()))-> | |
| 202 extension_service()->GetExtensionById(by_ext->id(), include_disabled); | |
| 203 if (extension) | |
| 204 file_value->SetString("by_ext_name", extension->name()); | |
| 205 } | |
| 206 file_value->SetString("by_ext_id", by_ext_id); | |
| 207 file_value->SetString("by_ext_name", by_ext_name); | |
| 208 | |
| 209 // Keep file names as LTR. | |
| 210 base::string16 file_name = | |
| 211 download_item->GetFileNameToReportUser().LossyDisplayName(); | |
| 212 file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name); | |
| 213 file_value->SetString("file_name", file_name); | |
| 214 file_value->SetString("url", download_item->GetURL().spec()); | |
| 215 file_value->SetInteger("total", static_cast<int>( | |
| 216 download_item->GetTotalBytes())); | |
| 217 file_value->SetBoolean("file_externally_removed", | |
| 218 download_item->GetFileExternallyRemoved()); | |
| 219 file_value->SetBoolean("resume", download_item->CanResume()); | |
| 220 | |
| 221 bool incognito = false; | |
| 222 auto* original_manager = GetOriginalNotifierManager(); | |
| 223 if (original_manager) { | |
| 224 incognito = | |
| 225 original_manager->GetDownload(download_item->GetId()) == download_item; | |
| 226 } | |
| 227 file_value->SetBoolean("otr", incognito); | |
| 228 | |
| 229 const char* danger_type = ""; | |
| 230 base::string16 last_reason_text; | |
| 231 // -2 is invalid, -1 means indeterminate, and 0-100 are in-progress. | |
| 232 int percent = -2; | |
| 233 base::string16 progress_status_text; | |
| 234 bool retry = false; | |
| 235 const char* state = nullptr; | |
| 236 | |
| 237 switch (download_item->GetState()) { | |
| 238 case content::DownloadItem::IN_PROGRESS: { | |
| 239 if (download_item->IsDangerous()) { | |
| 240 state = "DANGEROUS"; | |
| 241 // These are the only danger states that the UI is equipped to handle. | |
| 242 DCHECK(download_item->GetDangerType() == | |
|
dpapad
2015/11/20 23:00:19
Is this DCHECK needed given that there is already
Dan Beam
2015/11/21 01:40:43
Done.
| |
| 243 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || | |
| 244 download_item->GetDangerType() == | |
| 245 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || | |
| 246 download_item->GetDangerType() == | |
| 247 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || | |
| 248 download_item->GetDangerType() == | |
| 249 content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || | |
| 250 download_item->GetDangerType() == | |
| 251 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || | |
| 252 download_item->GetDangerType() == | |
| 253 content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED); | |
| 254 danger_type = GetDangerTypeString(download_item->GetDangerType()); | |
| 255 } else if (download_item->IsPaused()) { | |
| 256 state = "PAUSED"; | |
| 257 } else { | |
| 258 state = "IN_PROGRESS"; | |
| 259 } | |
| 260 progress_status_text = download_model.GetTabProgressStatusText(); | |
| 261 percent = download_item->PercentComplete(); | |
| 262 break; | |
| 263 } | |
| 264 | |
| 265 case content::DownloadItem::INTERRUPTED: | |
| 266 state = "INTERRUPTED"; | |
| 267 progress_status_text = download_model.GetTabProgressStatusText(); | |
| 268 | |
| 269 if (download_item->CanResume()) | |
| 270 percent = download_item->PercentComplete(); | |
| 271 | |
| 272 last_reason_text = download_model.GetInterruptReasonText(); | |
| 273 if (content::DOWNLOAD_INTERRUPT_REASON_CRASH == | |
| 274 download_item->GetLastReason() && !download_item->CanResume()) { | |
| 275 retry = true; | |
| 276 } | |
| 277 break; | |
| 278 | |
| 279 case content::DownloadItem::CANCELLED: | |
| 280 state = "CANCELLED"; | |
| 281 retry = true; | |
| 282 break; | |
| 283 | |
| 284 case content::DownloadItem::COMPLETE: | |
| 285 DCHECK(!download_item->IsDangerous()); | |
| 286 state = "COMPLETE"; | |
| 287 break; | |
| 288 | |
| 289 case content::DownloadItem::MAX_DOWNLOAD_STATE: | |
| 290 NOTREACHED(); | |
| 291 } | |
| 292 | |
| 293 DCHECK(state); | |
| 294 | |
| 295 file_value->SetString("danger_type", danger_type); | |
| 296 file_value->SetString("last_reason_text", last_reason_text); | |
| 297 file_value->SetInteger("percent", percent); | |
| 298 file_value->SetString("progress_status_text", progress_status_text); | |
| 299 file_value->SetBoolean("retry", retry); | |
| 300 file_value->SetString("state", state); | |
| 301 | |
| 302 return file_value.Pass(); | |
| 303 } | |
| 304 | |
| 305 DownloadItem* DownloadsListTracker::GetItemForTesting(size_t index) { | |
| 306 if (index >= sorted_visible_items_.size()) | |
| 307 return nullptr; | |
| 308 | |
| 309 SortedSet::iterator it = sorted_visible_items_.begin(); | |
| 310 std::advance(it, index); | |
| 311 return *it; | |
| 312 } | |
| 313 | |
| 314 bool DownloadsListTracker::ShouldShow(const DownloadItem& item) const { | |
| 315 if (download_crx_util::IsExtensionDownload(item) || | |
| 316 item.IsTemporary() || | |
| 317 item.GetFileNameToReportUser().empty() || | |
| 318 item.GetTargetFilePath().empty()) { | |
| 319 return false; | |
| 320 } | |
| 321 | |
| 322 DownloadItem* mutable_item = const_cast<DownloadItem*>(&item); | |
| 323 | |
| 324 DownloadItemModel model(mutable_item); | |
| 325 if (!model.ShouldShowInShelf()) | |
| 326 return false; | |
| 327 | |
| 328 if (search_terms_.empty()) | |
| 329 return true; | |
| 330 | |
| 331 DownloadVector all_items, filtered_items; | |
| 332 all_items.push_back(mutable_item); | |
| 333 | |
| 334 DownloadQuery query; | |
| 335 query.AddFilter(DownloadQuery::FILTER_QUERY, search_terms_); | |
| 336 query.Search(all_items.begin(), all_items.end(), &filtered_items); | |
| 337 | |
| 338 return !filtered_items.empty(); | |
| 339 } | |
| 340 | |
| 341 bool DownloadsListTracker::StartTimeComparator::operator() ( | |
| 342 const content::DownloadItem* a, const content::DownloadItem* b) const { | |
| 343 return a->GetStartTime() > b->GetStartTime(); | |
| 344 } | |
| 345 | |
| 346 void DownloadsListTracker::Init() { | |
| 347 Profile* profile = Profile::FromBrowserContext( | |
| 348 GetMainNotifierManager()->GetBrowserContext()); | |
| 349 if (profile->IsOffTheRecord()) { | |
| 350 original_notifier_.reset(new AllDownloadItemNotifier( | |
| 351 BrowserContext::GetDownloadManager(profile->GetOriginalProfile()), | |
| 352 this)); | |
| 353 } | |
| 354 | |
| 355 RebuildSortedSet(); | |
| 356 } | |
| 357 | |
| 358 void DownloadsListTracker::RebuildSortedSet() { | |
| 359 DownloadVector all_items, visible_items; | |
| 360 | |
| 361 GetMainNotifierManager()->GetAllDownloads(&all_items); | |
| 362 | |
| 363 if (GetOriginalNotifierManager()) | |
| 364 GetOriginalNotifierManager()->GetAllDownloads(&all_items); | |
| 365 | |
| 366 DownloadQuery query; | |
| 367 query.AddFilter(should_show_); | |
| 368 query.Search(all_items.begin(), all_items.end(), &visible_items); | |
| 369 | |
| 370 SortedSet sorted_visible_items(visible_items.begin(), visible_items.end()); | |
| 371 sorted_visible_items_.swap(sorted_visible_items); | |
| 372 } | |
| 373 | |
| 374 void DownloadsListTracker::InsertItem(const SortedSet::iterator& insert) { | |
| 375 // TODO(dbeam): use std::next() when C++11 library functions are allowed. | |
| 376 SortedSet::iterator next = insert; | |
| 377 std::advance(next, 1); | |
| 378 InsertItems(insert, next); | |
| 379 } | |
| 380 | |
| 381 void DownloadsListTracker::InsertItems(const SortedSet::iterator& start, | |
|
dpapad
2015/11/20 23:00:19
Nit: Can we improve a bit the consistency of funct
Dan Beam
2015/11/21 01:40:43
Done.
| |
| 382 const SortedSet::iterator& end) { | |
| 383 if (!sending_updates_) | |
| 384 return; | |
| 385 | |
| 386 base::ListValue insert; | |
| 387 for (auto it = start; it != end; ++it) { | |
| 388 insert.Append(CreateDownloadItemValue(*it).Pass()); | |
| 389 } | |
| 390 | |
| 391 web_ui_->CallJavascriptFunction("downloads.Manager.insertItems", | |
| 392 base::FundamentalValue(GetIndex(start)), | |
| 393 insert); | |
| 394 } | |
| 395 | |
| 396 void DownloadsListTracker::CallUpdateItem(const SortedSet::iterator& update) { | |
| 397 if (!sending_updates_) | |
| 398 return; | |
| 399 | |
| 400 web_ui_->CallJavascriptFunction("downloads.Manager.updateItem", | |
| 401 base::FundamentalValue(GetIndex(update)), | |
| 402 *CreateDownloadItemValue(*update)); | |
| 403 } | |
| 404 | |
| 405 int DownloadsListTracker::GetIndex(const SortedSet::iterator& position) const { | |
| 406 // TODO(dbeam): this could be log(N) if |position| was random access. | |
| 407 return std::distance(sorted_visible_items_.begin(), position); | |
| 408 } | |
| 409 | |
| 410 void DownloadsListTracker::RemoveItem(const SortedSet::iterator& remove) { | |
| 411 if (sending_updates_) { | |
| 412 web_ui_->CallJavascriptFunction("downloads.Manager.removeItem", | |
| 413 base::FundamentalValue(GetIndex(remove))); | |
| 414 } | |
| 415 sorted_visible_items_.erase(remove); | |
| 416 } | |
| OLD | NEW |