| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/downloads_dom_handler.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 #include <functional> | |
| 11 | |
| 12 #include "base/bind.h" | |
| 13 #include "base/bind_helpers.h" | |
| 14 #include "base/i18n/rtl.h" | |
| 15 #include "base/i18n/time_formatting.h" | |
| 16 #include "base/logging.h" | |
| 17 #include "base/memory/singleton.h" | |
| 18 #include "base/metrics/field_trial.h" | |
| 19 #include "base/metrics/histogram.h" | |
| 20 #include "base/strings/string_number_conversions.h" | |
| 21 #include "base/strings/string_piece.h" | |
| 22 #include "base/strings/utf_string_conversions.h" | |
| 23 #include "base/supports_user_data.h" | |
| 24 #include "base/threading/thread.h" | |
| 25 #include "base/value_conversions.h" | |
| 26 #include "base/values.h" | |
| 27 #include "chrome/browser/browser_process.h" | |
| 28 #include "chrome/browser/download/download_crx_util.h" | |
| 29 #include "chrome/browser/download/download_danger_prompt.h" | |
| 30 #include "chrome/browser/download/download_history.h" | |
| 31 #include "chrome/browser/download/download_item_model.h" | |
| 32 #include "chrome/browser/download/download_prefs.h" | |
| 33 #include "chrome/browser/download/download_query.h" | |
| 34 #include "chrome/browser/download/download_service.h" | |
| 35 #include "chrome/browser/download/download_service_factory.h" | |
| 36 #include "chrome/browser/download/drag_download_item.h" | |
| 37 #include "chrome/browser/extensions/api/downloads/downloads_api.h" | |
| 38 #include "chrome/browser/extensions/extension_service.h" | |
| 39 #include "chrome/browser/platform_util.h" | |
| 40 #include "chrome/browser/profiles/profile.h" | |
| 41 #include "chrome/browser/ui/webui/fileicon_source.h" | |
| 42 #include "chrome/common/chrome_switches.h" | |
| 43 #include "chrome/common/pref_names.h" | |
| 44 #include "chrome/common/url_constants.h" | |
| 45 #include "components/prefs/pref_service.h" | |
| 46 #include "content/public/browser/browser_thread.h" | |
| 47 #include "content/public/browser/download_item.h" | |
| 48 #include "content/public/browser/url_data_source.h" | |
| 49 #include "content/public/browser/user_metrics.h" | |
| 50 #include "content/public/browser/web_contents.h" | |
| 51 #include "content/public/browser/web_ui.h" | |
| 52 #include "extensions/browser/extension_system.h" | |
| 53 #include "net/base/filename_util.h" | |
| 54 #include "third_party/icu/source/i18n/unicode/datefmt.h" | |
| 55 #include "ui/base/l10n/time_format.h" | |
| 56 #include "ui/gfx/image/image.h" | |
| 57 | |
| 58 using base::UserMetricsAction; | |
| 59 using content::BrowserContext; | |
| 60 using content::BrowserThread; | |
| 61 | |
| 62 namespace { | |
| 63 | |
| 64 // Maximum number of downloads to show. TODO(glen): Remove this and instead | |
| 65 // stuff the downloads down the pipe slowly. | |
| 66 size_t kMaxNumberOfDownloads = 150; | |
| 67 | |
| 68 enum DownloadsDOMEvent { | |
| 69 DOWNLOADS_DOM_EVENT_GET_DOWNLOADS = 0, | |
| 70 DOWNLOADS_DOM_EVENT_OPEN_FILE = 1, | |
| 71 DOWNLOADS_DOM_EVENT_DRAG = 2, | |
| 72 DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS = 3, | |
| 73 DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS = 4, | |
| 74 DOWNLOADS_DOM_EVENT_SHOW = 5, | |
| 75 DOWNLOADS_DOM_EVENT_PAUSE = 6, | |
| 76 DOWNLOADS_DOM_EVENT_REMOVE = 7, | |
| 77 DOWNLOADS_DOM_EVENT_CANCEL = 8, | |
| 78 DOWNLOADS_DOM_EVENT_CLEAR_ALL = 9, | |
| 79 DOWNLOADS_DOM_EVENT_OPEN_FOLDER = 10, | |
| 80 DOWNLOADS_DOM_EVENT_RESUME = 11, | |
| 81 DOWNLOADS_DOM_EVENT_MAX | |
| 82 }; | |
| 83 | |
| 84 void CountDownloadsDOMEvents(DownloadsDOMEvent event) { | |
| 85 UMA_HISTOGRAM_ENUMERATION("Download.DOMEvent", | |
| 86 event, | |
| 87 DOWNLOADS_DOM_EVENT_MAX); | |
| 88 } | |
| 89 | |
| 90 // Returns a string constant to be used as the |danger_type| value in | |
| 91 // CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE, | |
| 92 // DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the | |
| 93 // |danger_type| value is only defined if the value of |state| is |DANGEROUS|. | |
| 94 const char* GetDangerTypeString(content::DownloadDangerType danger_type) { | |
| 95 switch (danger_type) { | |
| 96 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: | |
| 97 return "DANGEROUS_FILE"; | |
| 98 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: | |
| 99 return "DANGEROUS_URL"; | |
| 100 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: | |
| 101 return "DANGEROUS_CONTENT"; | |
| 102 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: | |
| 103 return "UNCOMMON_CONTENT"; | |
| 104 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: | |
| 105 return "DANGEROUS_HOST"; | |
| 106 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: | |
| 107 return "POTENTIALLY_UNWANTED"; | |
| 108 default: | |
| 109 // Don't return a danger type string if it is NOT_DANGEROUS or | |
| 110 // MAYBE_DANGEROUS_CONTENT. | |
| 111 NOTREACHED(); | |
| 112 return ""; | |
| 113 } | |
| 114 } | |
| 115 | |
| 116 // Returns a JSON dictionary containing some of the attributes of |download|. | |
| 117 // The JSON dictionary will also have a field "id" set to |id|, and a field | |
| 118 // "otr" set to |incognito|. | |
| 119 base::DictionaryValue* CreateDownloadItemValue( | |
| 120 content::DownloadItem* download_item, | |
| 121 bool incognito) { | |
| 122 // TODO(asanka): Move towards using download_model here for getting status and | |
| 123 // progress. The difference currently only matters to Drive downloads and | |
| 124 // those don't show up on the downloads page, but should. | |
| 125 DownloadItemModel download_model(download_item); | |
| 126 | |
| 127 // The items which are to be written into file_value are also described in | |
| 128 // chrome/browser/resources/downloads/downloads.js in @typedef for | |
| 129 // BackendDownloadObject. Please update it whenever you add or remove | |
| 130 // any keys in file_value. | |
| 131 base::DictionaryValue* file_value = new base::DictionaryValue(); | |
| 132 | |
| 133 file_value->SetInteger( | |
| 134 "started", static_cast<int>(download_item->GetStartTime().ToTimeT())); | |
| 135 file_value->SetString( | |
| 136 "since_string", ui::TimeFormat::RelativeDate( | |
| 137 download_item->GetStartTime(), NULL)); | |
| 138 | |
| 139 base::Time start_time = download_item->GetStartTime(); | |
| 140 base::string16 date_string = base::TimeFormatShortDate(start_time); | |
| 141 file_value->SetString("date_string", date_string); | |
| 142 | |
| 143 file_value->SetString("id", base::Uint64ToString(download_item->GetId())); | |
| 144 | |
| 145 base::FilePath download_path(download_item->GetTargetFilePath()); | |
| 146 file_value->Set("file_path", base::CreateFilePathValue(download_path)); | |
| 147 file_value->SetString("file_url", | |
| 148 net::FilePathToFileURL(download_path).spec()); | |
| 149 | |
| 150 extensions::DownloadedByExtension* by_ext = | |
| 151 extensions::DownloadedByExtension::Get(download_item); | |
| 152 std::string by_ext_id; | |
| 153 std::string by_ext_name; | |
| 154 if (by_ext) { | |
| 155 by_ext_id = by_ext->id(); | |
| 156 // TODO(dbeam): why doesn't DownloadsByExtension::name() return a string16? | |
| 157 by_ext_name = by_ext->name(); | |
| 158 | |
| 159 // Lookup the extension's current name() in case the user changed their | |
| 160 // language. This won't work if the extension was uninstalled, so the name | |
| 161 // might be the wrong language. | |
| 162 bool include_disabled = true; | |
| 163 const extensions::Extension* extension = extensions::ExtensionSystem::Get( | |
| 164 Profile::FromBrowserContext(download_item->GetBrowserContext()))-> | |
| 165 extension_service()->GetExtensionById(by_ext->id(), include_disabled); | |
| 166 if (extension) | |
| 167 file_value->SetString("by_ext_name", extension->name()); | |
| 168 } | |
| 169 file_value->SetString("by_ext_id", by_ext_id); | |
| 170 file_value->SetString("by_ext_name", by_ext_name); | |
| 171 | |
| 172 // Keep file names as LTR. | |
| 173 base::string16 file_name = | |
| 174 download_item->GetFileNameToReportUser().LossyDisplayName(); | |
| 175 file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name); | |
| 176 file_value->SetString("file_name", file_name); | |
| 177 file_value->SetString("url", download_item->GetURL().spec()); | |
| 178 file_value->SetBoolean("otr", incognito); | |
| 179 file_value->SetInteger("total", static_cast<int>( | |
| 180 download_item->GetTotalBytes())); | |
| 181 file_value->SetBoolean("file_externally_removed", | |
| 182 download_item->GetFileExternallyRemoved()); | |
| 183 file_value->SetBoolean("resume", download_item->CanResume()); | |
| 184 | |
| 185 const char* danger_type = ""; | |
| 186 base::string16 last_reason_text; | |
| 187 // -2 is invalid, -1 means indeterminate, and 0-100 are in-progress. | |
| 188 int percent = -2; | |
| 189 base::string16 progress_status_text; | |
| 190 bool retry = false; | |
| 191 const char* state = nullptr; | |
| 192 | |
| 193 switch (download_item->GetState()) { | |
| 194 case content::DownloadItem::IN_PROGRESS: { | |
| 195 if (download_item->IsDangerous()) { | |
| 196 state = "DANGEROUS"; | |
| 197 // These are the only danger states that the UI is equipped to handle. | |
| 198 DCHECK(download_item->GetDangerType() == | |
| 199 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || | |
| 200 download_item->GetDangerType() == | |
| 201 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || | |
| 202 download_item->GetDangerType() == | |
| 203 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || | |
| 204 download_item->GetDangerType() == | |
| 205 content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || | |
| 206 download_item->GetDangerType() == | |
| 207 content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || | |
| 208 download_item->GetDangerType() == | |
| 209 content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED); | |
| 210 danger_type = GetDangerTypeString(download_item->GetDangerType()); | |
| 211 } else if (download_item->IsPaused()) { | |
| 212 state = "PAUSED"; | |
| 213 } else { | |
| 214 state = "IN_PROGRESS"; | |
| 215 } | |
| 216 progress_status_text = download_model.GetTabProgressStatusText(); | |
| 217 percent = std::max(0, download_item->PercentComplete()); | |
| 218 break; | |
| 219 } | |
| 220 | |
| 221 case content::DownloadItem::INTERRUPTED: | |
| 222 state = "INTERRUPTED"; | |
| 223 progress_status_text = download_model.GetTabProgressStatusText(); | |
| 224 | |
| 225 if (download_item->CanResume()) | |
| 226 percent = download_item->PercentComplete(); | |
| 227 | |
| 228 last_reason_text = download_model.GetInterruptReasonText(); | |
| 229 if (content::DOWNLOAD_INTERRUPT_REASON_CRASH == | |
| 230 download_item->GetLastReason() && !download_item->CanResume()) { | |
| 231 retry = true; | |
| 232 } | |
| 233 break; | |
| 234 | |
| 235 case content::DownloadItem::CANCELLED: | |
| 236 state = "CANCELLED"; | |
| 237 retry = true; | |
| 238 break; | |
| 239 | |
| 240 case content::DownloadItem::COMPLETE: | |
| 241 DCHECK(!download_item->IsDangerous()); | |
| 242 state = "COMPLETE"; | |
| 243 break; | |
| 244 | |
| 245 case content::DownloadItem::MAX_DOWNLOAD_STATE: | |
| 246 NOTREACHED(); | |
| 247 } | |
| 248 | |
| 249 DCHECK(state); | |
| 250 | |
| 251 file_value->SetString("danger_type", danger_type); | |
| 252 file_value->SetString("last_reason_text", last_reason_text); | |
| 253 file_value->SetInteger("percent", percent); | |
| 254 file_value->SetString("progress_status_text", progress_status_text); | |
| 255 file_value->SetBoolean("retry", retry); | |
| 256 file_value->SetString("state", state); | |
| 257 | |
| 258 return file_value; | |
| 259 } | |
| 260 | |
| 261 // Filters out extension downloads and downloads that don't have a filename yet. | |
| 262 bool IsDownloadDisplayable(const content::DownloadItem& item) { | |
| 263 return !download_crx_util::IsExtensionDownload(item) && | |
| 264 !item.IsTemporary() && | |
| 265 !item.GetFileNameToReportUser().empty() && | |
| 266 !item.GetTargetFilePath().empty() && | |
| 267 DownloadItemModel( | |
| 268 const_cast<content::DownloadItem*>(&item)).ShouldShowInShelf(); | |
| 269 } | |
| 270 | |
| 271 } // namespace | |
| 272 | |
| 273 DownloadsDOMHandler::DownloadsDOMHandler( | |
| 274 content::DownloadManager* download_manager) | |
| 275 : download_manager_(download_manager), | |
| 276 update_scheduled_(false), | |
| 277 weak_ptr_factory_(this) { | |
| 278 // Create our fileicon data source. | |
| 279 Profile* profile = Profile::FromBrowserContext( | |
| 280 download_manager->GetBrowserContext()); | |
| 281 content::URLDataSource::Add(profile, new FileIconSource()); | |
| 282 } | |
| 283 | |
| 284 DownloadsDOMHandler::~DownloadsDOMHandler() { | |
| 285 FinalizeRemovals(); | |
| 286 } | |
| 287 | |
| 288 // DownloadsDOMHandler, public: ----------------------------------------------- | |
| 289 | |
| 290 void DownloadsDOMHandler::RegisterMessages() { | |
| 291 web_ui()->RegisterMessageCallback("getDownloads", | |
| 292 base::Bind(&DownloadsDOMHandler::HandleGetDownloads, | |
| 293 weak_ptr_factory_.GetWeakPtr())); | |
| 294 web_ui()->RegisterMessageCallback("openFile", | |
| 295 base::Bind(&DownloadsDOMHandler::HandleOpenFile, | |
| 296 weak_ptr_factory_.GetWeakPtr())); | |
| 297 web_ui()->RegisterMessageCallback("drag", | |
| 298 base::Bind(&DownloadsDOMHandler::HandleDrag, | |
| 299 weak_ptr_factory_.GetWeakPtr())); | |
| 300 web_ui()->RegisterMessageCallback("saveDangerous", | |
| 301 base::Bind(&DownloadsDOMHandler::HandleSaveDangerous, | |
| 302 weak_ptr_factory_.GetWeakPtr())); | |
| 303 web_ui()->RegisterMessageCallback("discardDangerous", | |
| 304 base::Bind(&DownloadsDOMHandler::HandleDiscardDangerous, | |
| 305 weak_ptr_factory_.GetWeakPtr())); | |
| 306 web_ui()->RegisterMessageCallback("show", | |
| 307 base::Bind(&DownloadsDOMHandler::HandleShow, | |
| 308 weak_ptr_factory_.GetWeakPtr())); | |
| 309 web_ui()->RegisterMessageCallback("pause", | |
| 310 base::Bind(&DownloadsDOMHandler::HandlePause, | |
| 311 weak_ptr_factory_.GetWeakPtr())); | |
| 312 web_ui()->RegisterMessageCallback("resume", | |
| 313 base::Bind(&DownloadsDOMHandler::HandleResume, | |
| 314 weak_ptr_factory_.GetWeakPtr())); | |
| 315 web_ui()->RegisterMessageCallback("remove", | |
| 316 base::Bind(&DownloadsDOMHandler::HandleRemove, | |
| 317 weak_ptr_factory_.GetWeakPtr())); | |
| 318 web_ui()->RegisterMessageCallback("undo", | |
| 319 base::Bind(&DownloadsDOMHandler::HandleUndo, | |
| 320 weak_ptr_factory_.GetWeakPtr())); | |
| 321 web_ui()->RegisterMessageCallback("cancel", | |
| 322 base::Bind(&DownloadsDOMHandler::HandleCancel, | |
| 323 weak_ptr_factory_.GetWeakPtr())); | |
| 324 web_ui()->RegisterMessageCallback("clearAll", | |
| 325 base::Bind(&DownloadsDOMHandler::HandleClearAll, | |
| 326 weak_ptr_factory_.GetWeakPtr())); | |
| 327 web_ui()->RegisterMessageCallback("openDownloadsFolder", | |
| 328 base::Bind(&DownloadsDOMHandler::HandleOpenDownloadsFolder, | |
| 329 weak_ptr_factory_.GetWeakPtr())); | |
| 330 } | |
| 331 | |
| 332 void DownloadsDOMHandler::OnDownloadCreated( | |
| 333 content::DownloadManager* manager, content::DownloadItem* download_item) { | |
| 334 if (IsDownloadDisplayable(*download_item)) | |
| 335 ScheduleSendCurrentDownloads(); | |
| 336 else | |
| 337 new_downloads_.insert(download_item->GetId()); | |
| 338 } | |
| 339 | |
| 340 void DownloadsDOMHandler::OnDownloadUpdated( | |
| 341 content::DownloadManager* manager, | |
| 342 content::DownloadItem* download_item) { | |
| 343 if (update_scheduled_) | |
| 344 return; | |
| 345 | |
| 346 bool showing_new_item = false; | |
| 347 | |
| 348 if (new_downloads_.count(download_item->GetId())) { | |
| 349 // A new download (that the page doesn't know about yet) has been updated. | |
| 350 if (!IsDownloadDisplayable(*download_item)) { | |
| 351 // Item isn't ready to be displayed yet. Wait until it is. | |
| 352 return; | |
| 353 } | |
| 354 | |
| 355 new_downloads_.erase(download_item->GetId()); | |
| 356 showing_new_item = true; | |
| 357 } | |
| 358 | |
| 359 if (showing_new_item || DownloadItemModel(download_item).IsBeingRevived() || | |
| 360 !IsDownloadDisplayable(*download_item)) { | |
| 361 // A download will be shown or hidden by this update. Resend the list. | |
| 362 ScheduleSendCurrentDownloads(); | |
| 363 return; | |
| 364 } | |
| 365 | |
| 366 if (search_terms_ && !search_terms_->empty()) { | |
| 367 // Don't CallUpdateItem() if download_item doesn't match | |
| 368 // search_terms_. | |
| 369 // TODO(benjhayden): Consider splitting MatchesQuery() out to a function. | |
| 370 content::DownloadManager::DownloadVector all_items, filtered_items; | |
| 371 all_items.push_back(download_item); | |
| 372 DownloadQuery query; | |
| 373 query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_); | |
| 374 query.Search(all_items.begin(), all_items.end(), &filtered_items); | |
| 375 if (filtered_items.empty()) | |
| 376 return; | |
| 377 } | |
| 378 | |
| 379 DCHECK(manager); | |
| 380 scoped_ptr<base::DictionaryValue> item(CreateDownloadItemValue( | |
| 381 download_item, | |
| 382 original_notifier_ && manager == GetMainNotifierManager())); | |
| 383 CallUpdateItem(*item); | |
| 384 } | |
| 385 | |
| 386 void DownloadsDOMHandler::OnDownloadRemoved( | |
| 387 content::DownloadManager* manager, | |
| 388 content::DownloadItem* download_item) { | |
| 389 if (!DownloadItemModel(download_item).ShouldShowInShelf()) | |
| 390 return; | |
| 391 | |
| 392 // This relies on |download_item| being removed from DownloadManager in this | |
| 393 // MessageLoop iteration. |download_item| may not have been removed from | |
| 394 // DownloadManager when OnDownloadRemoved() is fired, so bounce off the | |
| 395 // MessageLoop to give it a chance to be removed. SendCurrentDownloads() looks | |
| 396 // at all downloads, and we do not tell it that |download_item| is being | |
| 397 // removed. If DownloadManager is ever changed to not immediately remove | |
| 398 // |download_item| from its map when OnDownloadRemoved is sent, then | |
| 399 // DownloadsDOMHandler::OnDownloadRemoved() will need to explicitly tell | |
| 400 // SendCurrentDownloads() that |download_item| was removed. A | |
| 401 // SupportsUserData::Data would be the correct way to do this. | |
| 402 ScheduleSendCurrentDownloads(); | |
| 403 } | |
| 404 | |
| 405 void DownloadsDOMHandler::HandleGetDownloads(const base::ListValue* args) { | |
| 406 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS); | |
| 407 search_terms_.reset(args && !args->empty() ? args->DeepCopy() : NULL); | |
| 408 ScheduleSendCurrentDownloads(); | |
| 409 | |
| 410 if (!main_notifier_) { | |
| 411 main_notifier_.reset(new AllDownloadItemNotifier(download_manager_, this)); | |
| 412 | |
| 413 Profile* profile = Profile::FromBrowserContext( | |
| 414 download_manager_->GetBrowserContext()); | |
| 415 if (profile->IsOffTheRecord()) { | |
| 416 original_notifier_.reset(new AllDownloadItemNotifier( | |
| 417 BrowserContext::GetDownloadManager(profile->GetOriginalProfile()), | |
| 418 this)); | |
| 419 } | |
| 420 } | |
| 421 } | |
| 422 | |
| 423 void DownloadsDOMHandler::HandleOpenFile(const base::ListValue* args) { | |
| 424 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE); | |
| 425 content::DownloadItem* file = GetDownloadByValue(args); | |
| 426 if (file) | |
| 427 file->OpenDownload(); | |
| 428 } | |
| 429 | |
| 430 void DownloadsDOMHandler::HandleDrag(const base::ListValue* args) { | |
| 431 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG); | |
| 432 content::DownloadItem* file = GetDownloadByValue(args); | |
| 433 if (!file) | |
| 434 return; | |
| 435 | |
| 436 content::WebContents* web_contents = GetWebUIWebContents(); | |
| 437 // |web_contents| is only NULL in the test. | |
| 438 if (!web_contents) | |
| 439 return; | |
| 440 | |
| 441 if (file->GetState() != content::DownloadItem::COMPLETE) | |
| 442 return; | |
| 443 | |
| 444 gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath( | |
| 445 file->GetTargetFilePath(), IconLoader::NORMAL); | |
| 446 gfx::NativeView view = web_contents->GetNativeView(); | |
| 447 { | |
| 448 // Enable nested tasks during DnD, while |DragDownload()| blocks. | |
| 449 base::MessageLoop::ScopedNestableTaskAllower allow( | |
| 450 base::MessageLoop::current()); | |
| 451 DragDownloadItem(file, icon, view); | |
| 452 } | |
| 453 } | |
| 454 | |
| 455 void DownloadsDOMHandler::HandleSaveDangerous(const base::ListValue* args) { | |
| 456 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); | |
| 457 content::DownloadItem* file = GetDownloadByValue(args); | |
| 458 if (file) | |
| 459 ShowDangerPrompt(file); | |
| 460 } | |
| 461 | |
| 462 void DownloadsDOMHandler::HandleDiscardDangerous(const base::ListValue* args) { | |
| 463 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS); | |
| 464 content::DownloadItem* file = GetDownloadByValue(args); | |
| 465 if (file) | |
| 466 file->Remove(); | |
| 467 } | |
| 468 | |
| 469 void DownloadsDOMHandler::HandleShow(const base::ListValue* args) { | |
| 470 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW); | |
| 471 content::DownloadItem* file = GetDownloadByValue(args); | |
| 472 if (file) | |
| 473 file->ShowDownloadInShell(); | |
| 474 } | |
| 475 | |
| 476 void DownloadsDOMHandler::HandlePause(const base::ListValue* args) { | |
| 477 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE); | |
| 478 content::DownloadItem* file = GetDownloadByValue(args); | |
| 479 if (file) | |
| 480 file->Pause(); | |
| 481 } | |
| 482 | |
| 483 void DownloadsDOMHandler::HandleResume(const base::ListValue* args) { | |
| 484 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RESUME); | |
| 485 content::DownloadItem* file = GetDownloadByValue(args); | |
| 486 if (file) | |
| 487 file->Resume(); | |
| 488 } | |
| 489 | |
| 490 void DownloadsDOMHandler::HandleRemove(const base::ListValue* args) { | |
| 491 if (!IsDeletingHistoryAllowed()) | |
| 492 return; | |
| 493 | |
| 494 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REMOVE); | |
| 495 content::DownloadItem* file = GetDownloadByValue(args); | |
| 496 if (!file) | |
| 497 return; | |
| 498 | |
| 499 std::vector<content::DownloadItem*> downloads; | |
| 500 downloads.push_back(file); | |
| 501 RemoveDownloads(downloads); | |
| 502 } | |
| 503 | |
| 504 void DownloadsDOMHandler::HandleUndo(const base::ListValue* args) { | |
| 505 // TODO(dbeam): handle more than removed downloads someday? | |
| 506 if (removals_.empty()) | |
| 507 return; | |
| 508 | |
| 509 const std::set<uint32_t> last_removed_ids = removals_.back(); | |
| 510 removals_.pop_back(); | |
| 511 | |
| 512 for (auto id : last_removed_ids) { | |
| 513 content::DownloadItem* download = GetDownloadById(id); | |
| 514 if (!download) | |
| 515 continue; | |
| 516 | |
| 517 DownloadItemModel model(download); | |
| 518 model.SetShouldShowInShelf(true); | |
| 519 model.SetIsBeingRevived(true); | |
| 520 | |
| 521 download->UpdateObservers(); | |
| 522 | |
| 523 model.SetIsBeingRevived(false); | |
| 524 } | |
| 525 } | |
| 526 | |
| 527 void DownloadsDOMHandler::HandleCancel(const base::ListValue* args) { | |
| 528 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL); | |
| 529 content::DownloadItem* file = GetDownloadByValue(args); | |
| 530 if (file) | |
| 531 file->Cancel(true); | |
| 532 } | |
| 533 | |
| 534 void DownloadsDOMHandler::HandleClearAll(const base::ListValue* args) { | |
| 535 if (!IsDeletingHistoryAllowed()) { | |
| 536 // This should only be reached during tests. | |
| 537 return; | |
| 538 } | |
| 539 | |
| 540 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL); | |
| 541 | |
| 542 std::vector<content::DownloadItem*> downloads; | |
| 543 if (GetMainNotifierManager()) | |
| 544 GetMainNotifierManager()->GetAllDownloads(&downloads); | |
| 545 if (GetOriginalNotifierManager()) | |
| 546 GetOriginalNotifierManager()->GetAllDownloads(&downloads); | |
| 547 RemoveDownloads(downloads); | |
| 548 } | |
| 549 | |
| 550 void DownloadsDOMHandler::RemoveDownloads( | |
| 551 const std::vector<content::DownloadItem*>& to_remove) { | |
| 552 std::set<uint32_t> ids; | |
| 553 | |
| 554 for (auto* download : to_remove) { | |
| 555 DownloadItemModel item_model(download); | |
| 556 if (!item_model.ShouldShowInShelf() || | |
| 557 download->GetState() == content::DownloadItem::IN_PROGRESS) { | |
| 558 continue; | |
| 559 } | |
| 560 | |
| 561 item_model.SetShouldShowInShelf(false); | |
| 562 ids.insert(download->GetId()); | |
| 563 download->UpdateObservers(); | |
| 564 } | |
| 565 | |
| 566 if (!ids.empty()) | |
| 567 removals_.push_back(ids); | |
| 568 } | |
| 569 | |
| 570 void DownloadsDOMHandler::HandleOpenDownloadsFolder( | |
| 571 const base::ListValue* args) { | |
| 572 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FOLDER); | |
| 573 content::DownloadManager* manager = GetMainNotifierManager(); | |
| 574 if (manager) { | |
| 575 platform_util::OpenItem( | |
| 576 Profile::FromBrowserContext(manager->GetBrowserContext()), | |
| 577 DownloadPrefs::FromDownloadManager(manager)->DownloadPath(), | |
| 578 platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback()); | |
| 579 } | |
| 580 } | |
| 581 | |
| 582 // DownloadsDOMHandler, private: ---------------------------------------------- | |
| 583 | |
| 584 void DownloadsDOMHandler::ScheduleSendCurrentDownloads() { | |
| 585 // Don't call SendCurrentDownloads() every time anything changes. Batch them | |
| 586 // together instead. This may handle hundreds of OnDownloadDestroyed() calls | |
| 587 // in a single UI message loop iteration when the user Clears All downloads. | |
| 588 if (update_scheduled_) | |
| 589 return; | |
| 590 | |
| 591 update_scheduled_ = true; | |
| 592 | |
| 593 BrowserThread::PostTask( | |
| 594 BrowserThread::UI, FROM_HERE, | |
| 595 base::Bind(&DownloadsDOMHandler::SendCurrentDownloads, | |
| 596 weak_ptr_factory_.GetWeakPtr())); | |
| 597 } | |
| 598 | |
| 599 content::DownloadManager* DownloadsDOMHandler::GetMainNotifierManager() const { | |
| 600 return main_notifier_ ? main_notifier_->GetManager() : nullptr; | |
| 601 } | |
| 602 | |
| 603 content::DownloadManager* DownloadsDOMHandler::GetOriginalNotifierManager() | |
| 604 const { | |
| 605 return original_notifier_ ? original_notifier_->GetManager() : nullptr; | |
| 606 } | |
| 607 | |
| 608 void DownloadsDOMHandler::FinalizeRemovals() { | |
| 609 while (!removals_.empty()) { | |
| 610 const std::set<uint32_t> remove = removals_.back(); | |
| 611 removals_.pop_back(); | |
| 612 | |
| 613 for (const auto id : remove) { | |
| 614 content::DownloadItem* download = GetDownloadById(id); | |
| 615 if (download) | |
| 616 download->Remove(); | |
| 617 } | |
| 618 } | |
| 619 } | |
| 620 | |
| 621 void DownloadsDOMHandler::SendCurrentDownloads() { | |
| 622 update_scheduled_ = false; | |
| 623 | |
| 624 content::DownloadManager::DownloadVector all_items, filtered_items; | |
| 625 if (GetMainNotifierManager()) { | |
| 626 GetMainNotifierManager()->GetAllDownloads(&all_items); | |
| 627 GetMainNotifierManager()->CheckForHistoryFilesRemoval(); | |
| 628 } | |
| 629 if (GetOriginalNotifierManager()) { | |
| 630 GetOriginalNotifierManager()->GetAllDownloads(&all_items); | |
| 631 GetOriginalNotifierManager()->CheckForHistoryFilesRemoval(); | |
| 632 } | |
| 633 | |
| 634 DownloadQuery query; | |
| 635 if (search_terms_ && !search_terms_->empty()) | |
| 636 query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_); | |
| 637 query.AddFilter(base::Bind(&IsDownloadDisplayable)); | |
| 638 query.AddSorter(DownloadQuery::SORT_START_TIME, DownloadQuery::DESCENDING); | |
| 639 query.Limit(kMaxNumberOfDownloads); | |
| 640 query.Search(all_items.begin(), all_items.end(), &filtered_items); | |
| 641 | |
| 642 base::ListValue results_value; | |
| 643 for (auto* item : filtered_items) { | |
| 644 results_value.Append(CreateDownloadItemValue( | |
| 645 item, | |
| 646 original_notifier_ && GetMainNotifierManager() && | |
| 647 GetMainNotifierManager()->GetDownload(item->GetId()) == item)); | |
| 648 } | |
| 649 CallUpdateAll(results_value); | |
| 650 } | |
| 651 | |
| 652 void DownloadsDOMHandler::ShowDangerPrompt( | |
| 653 content::DownloadItem* dangerous_item) { | |
| 654 DownloadDangerPrompt* danger_prompt = DownloadDangerPrompt::Create( | |
| 655 dangerous_item, | |
| 656 GetWebUIWebContents(), | |
| 657 false, | |
| 658 base::Bind(&DownloadsDOMHandler::DangerPromptDone, | |
| 659 weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId())); | |
| 660 // danger_prompt will delete itself. | |
| 661 DCHECK(danger_prompt); | |
| 662 } | |
| 663 | |
| 664 void DownloadsDOMHandler::DangerPromptDone( | |
| 665 int download_id, DownloadDangerPrompt::Action action) { | |
| 666 if (action != DownloadDangerPrompt::ACCEPT) | |
| 667 return; | |
| 668 content::DownloadItem* item = NULL; | |
| 669 if (GetMainNotifierManager()) | |
| 670 item = GetMainNotifierManager()->GetDownload(download_id); | |
| 671 if (!item && GetOriginalNotifierManager()) | |
| 672 item = GetOriginalNotifierManager()->GetDownload(download_id); | |
| 673 if (!item || item->IsDone()) | |
| 674 return; | |
| 675 CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); | |
| 676 item->ValidateDangerousDownload(); | |
| 677 } | |
| 678 | |
| 679 bool DownloadsDOMHandler::IsDeletingHistoryAllowed() { | |
| 680 content::DownloadManager* manager = GetMainNotifierManager(); | |
| 681 return manager && | |
| 682 Profile::FromBrowserContext(manager->GetBrowserContext())-> | |
| 683 GetPrefs()->GetBoolean(prefs::kAllowDeletingBrowserHistory); | |
| 684 } | |
| 685 | |
| 686 content::DownloadItem* DownloadsDOMHandler::GetDownloadByValue( | |
| 687 const base::ListValue* args) { | |
| 688 std::string download_id; | |
| 689 if (!args->GetString(0, &download_id)) { | |
| 690 NOTREACHED(); | |
| 691 return nullptr; | |
| 692 } | |
| 693 | |
| 694 uint64_t id; | |
| 695 if (!base::StringToUint64(download_id, &id)) { | |
| 696 NOTREACHED(); | |
| 697 return nullptr; | |
| 698 } | |
| 699 | |
| 700 return GetDownloadById(static_cast<uint32_t>(id)); | |
| 701 } | |
| 702 | |
| 703 content::DownloadItem* DownloadsDOMHandler::GetDownloadById(uint32_t id) { | |
| 704 content::DownloadItem* item = NULL; | |
| 705 if (GetMainNotifierManager()) | |
| 706 item = GetMainNotifierManager()->GetDownload(id); | |
| 707 if (!item && GetOriginalNotifierManager()) | |
| 708 item = GetOriginalNotifierManager()->GetDownload(id); | |
| 709 return item; | |
| 710 } | |
| 711 | |
| 712 content::WebContents* DownloadsDOMHandler::GetWebUIWebContents() { | |
| 713 return web_ui()->GetWebContents(); | |
| 714 } | |
| 715 | |
| 716 void DownloadsDOMHandler::CallUpdateAll(const base::ListValue& list) { | |
| 717 web_ui()->CallJavascriptFunction("downloads.Manager.updateAll", list); | |
| 718 } | |
| 719 | |
| 720 void DownloadsDOMHandler::CallUpdateItem(const base::DictionaryValue& item) { | |
| 721 web_ui()->CallJavascriptFunction("downloads.Manager.updateItem", item); | |
| 722 } | |
| OLD | NEW |