Home | History | Annotate | Download | only in webui
      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 <algorithm>
      8 #include <functional>
      9 
     10 #include "base/basictypes.h"
     11 #include "base/bind.h"
     12 #include "base/bind_helpers.h"
     13 #include "base/i18n/rtl.h"
     14 #include "base/i18n/time_formatting.h"
     15 #include "base/memory/singleton.h"
     16 #include "base/metrics/field_trial.h"
     17 #include "base/metrics/histogram.h"
     18 #include "base/prefs/pref_service.h"
     19 #include "base/strings/string_piece.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "base/threading/thread.h"
     22 #include "base/value_conversions.h"
     23 #include "base/values.h"
     24 #include "chrome/browser/browser_process.h"
     25 #include "chrome/browser/download/download_crx_util.h"
     26 #include "chrome/browser/download/download_danger_prompt.h"
     27 #include "chrome/browser/download/download_history.h"
     28 #include "chrome/browser/download/download_item_model.h"
     29 #include "chrome/browser/download/download_prefs.h"
     30 #include "chrome/browser/download/download_query.h"
     31 #include "chrome/browser/download/download_service.h"
     32 #include "chrome/browser/download/download_service_factory.h"
     33 #include "chrome/browser/download/download_util.h"
     34 #include "chrome/browser/extensions/api/downloads/downloads_api.h"
     35 #include "chrome/browser/extensions/extension_service.h"
     36 #include "chrome/browser/extensions/extension_system.h"
     37 #include "chrome/browser/platform_util.h"
     38 #include "chrome/browser/profiles/profile.h"
     39 #include "chrome/browser/ui/webui/fileicon_source.h"
     40 #include "chrome/common/pref_names.h"
     41 #include "chrome/common/url_constants.h"
     42 #include "content/public/browser/browser_thread.h"
     43 #include "content/public/browser/download_item.h"
     44 #include "content/public/browser/url_data_source.h"
     45 #include "content/public/browser/user_metrics.h"
     46 #include "content/public/browser/web_contents.h"
     47 #include "content/public/browser/web_contents_view.h"
     48 #include "content/public/browser/web_ui.h"
     49 #include "grit/generated_resources.h"
     50 #include "net/base/net_util.h"
     51 #include "ui/base/l10n/time_format.h"
     52 #include "ui/gfx/image/image.h"
     53 
     54 using content::BrowserContext;
     55 using content::BrowserThread;
     56 using content::UserMetricsAction;
     57 
     58 namespace {
     59 
     60 // Maximum number of downloads to show. TODO(glen): Remove this and instead
     61 // stuff the downloads down the pipe slowly.
     62 static const size_t kMaxDownloads = 150;
     63 
     64 enum DownloadsDOMEvent {
     65   DOWNLOADS_DOM_EVENT_GET_DOWNLOADS = 0,
     66   DOWNLOADS_DOM_EVENT_OPEN_FILE = 1,
     67   DOWNLOADS_DOM_EVENT_DRAG = 2,
     68   DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS = 3,
     69   DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS = 4,
     70   DOWNLOADS_DOM_EVENT_SHOW = 5,
     71   DOWNLOADS_DOM_EVENT_PAUSE = 6,
     72   DOWNLOADS_DOM_EVENT_REMOVE = 7,
     73   DOWNLOADS_DOM_EVENT_CANCEL = 8,
     74   DOWNLOADS_DOM_EVENT_CLEAR_ALL = 9,
     75   DOWNLOADS_DOM_EVENT_OPEN_FOLDER = 10,
     76   DOWNLOADS_DOM_EVENT_RESUME = 11,
     77   DOWNLOADS_DOM_EVENT_MAX
     78 };
     79 
     80 void CountDownloadsDOMEvents(DownloadsDOMEvent event) {
     81   UMA_HISTOGRAM_ENUMERATION("Download.DOMEvent",
     82                             event,
     83                             DOWNLOADS_DOM_EVENT_MAX);
     84 }
     85 
     86 // Returns a string constant to be used as the |danger_type| value in
     87 // CreateDownloadItemValue().  Only return strings for DANGEROUS_FILE,
     88 // DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the
     89 // |danger_type| value is only defined if the value of |state| is |DANGEROUS|.
     90 const char* GetDangerTypeString(content::DownloadDangerType danger_type) {
     91   switch (danger_type) {
     92     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
     93       return "DANGEROUS_FILE";
     94     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
     95       return "DANGEROUS_URL";
     96     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
     97       return "DANGEROUS_CONTENT";
     98     case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
     99       return "UNCOMMON_CONTENT";
    100     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
    101       return "DANGEROUS_HOST";
    102     case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
    103       return "POTENTIALLY_UNWANTED";
    104     default:
    105       // Don't return a danger type string if it is NOT_DANGEROUS or
    106       // MAYBE_DANGEROUS_CONTENT.
    107       NOTREACHED();
    108       return "";
    109   }
    110 }
    111 
    112 // Returns a JSON dictionary containing some of the attributes of |download|.
    113 // The JSON dictionary will also have a field "id" set to |id|, and a field
    114 // "otr" set to |incognito|.
    115 DictionaryValue* CreateDownloadItemValue(
    116     content::DownloadItem* download_item,
    117     bool incognito) {
    118   // TODO(asanka): Move towards using download_model here for getting status and
    119   // progress. The difference currently only matters to Drive downloads and
    120   // those don't show up on the downloads page, but should.
    121   DownloadItemModel download_model(download_item);
    122   DictionaryValue* file_value = new DictionaryValue();
    123 
    124   file_value->SetInteger(
    125       "started", static_cast<int>(download_item->GetStartTime().ToTimeT()));
    126   file_value->SetString(
    127       "since_string", ui::TimeFormat::RelativeDate(
    128           download_item->GetStartTime(), NULL));
    129   file_value->SetString(
    130       "date_string", base::TimeFormatShortDate(download_item->GetStartTime()));
    131   file_value->SetInteger("id", download_item->GetId());
    132 
    133   base::FilePath download_path(download_item->GetTargetFilePath());
    134   file_value->Set("file_path", base::CreateFilePathValue(download_path));
    135   file_value->SetString("file_url",
    136                         net::FilePathToFileURL(download_path).spec());
    137 
    138   DownloadedByExtension* by_ext = DownloadedByExtension::Get(download_item);
    139   if (by_ext) {
    140     file_value->SetString("by_ext_id", by_ext->id());
    141     file_value->SetString("by_ext_name", by_ext->name());
    142     // Lookup the extension's current name() in case the user changed their
    143     // language. This won't work if the extension was uninstalled, so the name
    144     // might be the wrong language.
    145     bool include_disabled = true;
    146     const extensions::Extension* extension = extensions::ExtensionSystem::Get(
    147         Profile::FromBrowserContext(download_item->GetBrowserContext()))->
    148       extension_service()->GetExtensionById(by_ext->id(), include_disabled);
    149     if (extension)
    150       file_value->SetString("by_ext_name", extension->name());
    151   }
    152 
    153   // Keep file names as LTR.
    154   string16 file_name =
    155     download_item->GetFileNameToReportUser().LossyDisplayName();
    156   file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name);
    157   file_value->SetString("file_name", file_name);
    158   file_value->SetString("url", download_item->GetURL().spec());
    159   file_value->SetBoolean("otr", incognito);
    160   file_value->SetInteger("total", static_cast<int>(
    161       download_item->GetTotalBytes()));
    162   file_value->SetBoolean("file_externally_removed",
    163                          download_item->GetFileExternallyRemoved());
    164   file_value->SetBoolean("retry", false); // Overridden below if needed.
    165   file_value->SetBoolean("resume", download_item->CanResume());
    166 
    167   switch (download_item->GetState()) {
    168     case content::DownloadItem::IN_PROGRESS:
    169       if (download_item->IsDangerous()) {
    170         file_value->SetString("state", "DANGEROUS");
    171         // These are the only danger states that the UI is equipped to handle.
    172         DCHECK(download_item->GetDangerType() ==
    173                    content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
    174                download_item->GetDangerType() ==
    175                    content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
    176                download_item->GetDangerType() ==
    177                    content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
    178                download_item->GetDangerType() ==
    179                    content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT ||
    180                download_item->GetDangerType() ==
    181                    content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST ||
    182                download_item->GetDangerType() ==
    183                    content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED);
    184         std::string trial_condition =
    185             base::FieldTrialList::FindFullName(download_util::kFinchTrialName);
    186         const char* danger_type_value =
    187             GetDangerTypeString(download_item->GetDangerType());
    188         file_value->SetString("danger_type", danger_type_value);
    189         if (!trial_condition.empty()) {
    190           base::string16 finch_string;
    191           content::DownloadDangerType danger_type =
    192               download_item->GetDangerType();
    193           if (danger_type == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
    194               danger_type == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
    195               danger_type == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST) {
    196             finch_string = download_util::AssembleMalwareFinchString(
    197                 trial_condition, file_name);
    198           }
    199           file_value->SetString("finch_string", finch_string);
    200         }
    201       } else if (download_item->IsPaused()) {
    202         file_value->SetString("state", "PAUSED");
    203       } else {
    204         file_value->SetString("state", "IN_PROGRESS");
    205       }
    206 
    207       file_value->SetString("progress_status_text",
    208           download_util::GetProgressStatusText(download_item));
    209 
    210       file_value->SetInteger("percent",
    211           static_cast<int>(download_item->PercentComplete()));
    212       file_value->SetInteger("received",
    213           static_cast<int>(download_item->GetReceivedBytes()));
    214       break;
    215 
    216     case content::DownloadItem::INTERRUPTED:
    217       file_value->SetString("state", "INTERRUPTED");
    218 
    219       file_value->SetString("progress_status_text",
    220           download_util::GetProgressStatusText(download_item));
    221 
    222       file_value->SetInteger("percent",
    223           static_cast<int>(download_item->PercentComplete()));
    224       file_value->SetInteger("received",
    225           static_cast<int>(download_item->GetReceivedBytes()));
    226       file_value->SetString("last_reason_text",
    227                             download_model.GetInterruptReasonText());
    228       if (content::DOWNLOAD_INTERRUPT_REASON_CRASH ==
    229           download_item->GetLastReason() && !download_item->CanResume())
    230         file_value->SetBoolean("retry", true);
    231       break;
    232 
    233     case content::DownloadItem::CANCELLED:
    234       file_value->SetString("state", "CANCELLED");
    235       file_value->SetBoolean("retry", true);
    236       break;
    237 
    238     case content::DownloadItem::COMPLETE:
    239       DCHECK(!download_item->IsDangerous());
    240       file_value->SetString("state", "COMPLETE");
    241       break;
    242 
    243     case content::DownloadItem::MAX_DOWNLOAD_STATE:
    244       NOTREACHED() << "state undefined";
    245   }
    246 
    247   return file_value;
    248 }
    249 
    250 // Filters out extension downloads and downloads that don't have a filename yet.
    251 bool IsDownloadDisplayable(const content::DownloadItem& item) {
    252   return (!download_crx_util::IsExtensionDownload(item) &&
    253           !item.IsTemporary() &&
    254           !item.GetFileNameToReportUser().empty() &&
    255           !item.GetTargetFilePath().empty());
    256 }
    257 
    258 }  // namespace
    259 
    260 DownloadsDOMHandler::DownloadsDOMHandler(content::DownloadManager* dlm)
    261     : main_notifier_(dlm, this),
    262       update_scheduled_(false),
    263       weak_ptr_factory_(this) {
    264   // Create our fileicon data source.
    265   Profile* profile = Profile::FromBrowserContext(dlm->GetBrowserContext());
    266   content::URLDataSource::Add(profile, new FileIconSource());
    267 
    268   if (profile->IsOffTheRecord()) {
    269     original_notifier_.reset(new AllDownloadItemNotifier(
    270         BrowserContext::GetDownloadManager(profile->GetOriginalProfile()),
    271         this));
    272   }
    273 }
    274 
    275 DownloadsDOMHandler::~DownloadsDOMHandler() {
    276 }
    277 
    278 // DownloadsDOMHandler, public: -----------------------------------------------
    279 
    280 void DownloadsDOMHandler::OnPageLoaded(const base::ListValue* args) {
    281   SendCurrentDownloads();
    282 }
    283 
    284 void DownloadsDOMHandler::RegisterMessages() {
    285   web_ui()->RegisterMessageCallback("onPageLoaded",
    286       base::Bind(&DownloadsDOMHandler::OnPageLoaded,
    287                  weak_ptr_factory_.GetWeakPtr()));
    288   web_ui()->RegisterMessageCallback("getDownloads",
    289       base::Bind(&DownloadsDOMHandler::HandleGetDownloads,
    290                  weak_ptr_factory_.GetWeakPtr()));
    291   web_ui()->RegisterMessageCallback("openFile",
    292       base::Bind(&DownloadsDOMHandler::HandleOpenFile,
    293                  weak_ptr_factory_.GetWeakPtr()));
    294   web_ui()->RegisterMessageCallback("drag",
    295       base::Bind(&DownloadsDOMHandler::HandleDrag,
    296                  weak_ptr_factory_.GetWeakPtr()));
    297   web_ui()->RegisterMessageCallback("saveDangerous",
    298       base::Bind(&DownloadsDOMHandler::HandleSaveDangerous,
    299                  weak_ptr_factory_.GetWeakPtr()));
    300   web_ui()->RegisterMessageCallback("discardDangerous",
    301       base::Bind(&DownloadsDOMHandler::HandleDiscardDangerous,
    302                  weak_ptr_factory_.GetWeakPtr()));
    303   web_ui()->RegisterMessageCallback("show",
    304       base::Bind(&DownloadsDOMHandler::HandleShow,
    305                  weak_ptr_factory_.GetWeakPtr()));
    306   web_ui()->RegisterMessageCallback("pause",
    307       base::Bind(&DownloadsDOMHandler::HandlePause,
    308                  weak_ptr_factory_.GetWeakPtr()));
    309   web_ui()->RegisterMessageCallback("resume",
    310       base::Bind(&DownloadsDOMHandler::HandleResume,
    311                  weak_ptr_factory_.GetWeakPtr()));
    312   web_ui()->RegisterMessageCallback("remove",
    313       base::Bind(&DownloadsDOMHandler::HandleRemove,
    314                  weak_ptr_factory_.GetWeakPtr()));
    315   web_ui()->RegisterMessageCallback("cancel",
    316       base::Bind(&DownloadsDOMHandler::HandleCancel,
    317                  weak_ptr_factory_.GetWeakPtr()));
    318   web_ui()->RegisterMessageCallback("clearAll",
    319       base::Bind(&DownloadsDOMHandler::HandleClearAll,
    320                  weak_ptr_factory_.GetWeakPtr()));
    321   web_ui()->RegisterMessageCallback("openDownloadsFolder",
    322       base::Bind(&DownloadsDOMHandler::HandleOpenDownloadsFolder,
    323                  weak_ptr_factory_.GetWeakPtr()));
    324 }
    325 
    326 void DownloadsDOMHandler::OnDownloadCreated(
    327     content::DownloadManager* manager, content::DownloadItem* download_item) {
    328   if (IsDownloadDisplayable(*download_item))
    329     ScheduleSendCurrentDownloads();
    330 }
    331 
    332 void DownloadsDOMHandler::OnDownloadUpdated(
    333     content::DownloadManager* manager,
    334     content::DownloadItem* download_item) {
    335   if (IsDownloadDisplayable(*download_item)) {
    336     if (search_terms_ && !search_terms_->empty()) {
    337       // Don't CallDownloadUpdated() if download_item doesn't match
    338       // search_terms_.
    339       // TODO(benjhayden): Consider splitting MatchesQuery() out to a function.
    340       content::DownloadManager::DownloadVector all_items, filtered_items;
    341       all_items.push_back(download_item);
    342       DownloadQuery query;
    343       query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_.get());
    344       query.Search(all_items.begin(), all_items.end(), &filtered_items);
    345       if (filtered_items.empty())
    346         return;
    347     }
    348     base::ListValue results_value;
    349     results_value.Append(CreateDownloadItemValue(
    350         download_item,
    351         (original_notifier_.get() &&
    352           (manager == main_notifier_.GetManager()))));
    353     CallDownloadUpdated(results_value);
    354   }
    355 }
    356 
    357 void DownloadsDOMHandler::OnDownloadRemoved(
    358     content::DownloadManager* manager,
    359     content::DownloadItem* download_item) {
    360   // This relies on |download_item| being removed from DownloadManager in this
    361   // MessageLoop iteration. |download_item| may not have been removed from
    362   // DownloadManager when OnDownloadRemoved() is fired, so bounce off the
    363   // MessageLoop to give it a chance to be removed. SendCurrentDownloads() looks
    364   // at all downloads, and we do not tell it that |download_item| is being
    365   // removed. If DownloadManager is ever changed to not immediately remove
    366   // |download_item| from its map when OnDownloadRemoved is sent, then
    367   // DownloadsDOMHandler::OnDownloadRemoved() will need to explicitly tell
    368   // SendCurrentDownloads() that |download_item| was removed. A
    369   // SupportsUserData::Data would be the correct way to do this.
    370   ScheduleSendCurrentDownloads();
    371 }
    372 
    373 void DownloadsDOMHandler::HandleGetDownloads(const base::ListValue* args) {
    374   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS);
    375   search_terms_.reset((args && !args->empty()) ? args->DeepCopy() : NULL);
    376   SendCurrentDownloads();
    377 }
    378 
    379 void DownloadsDOMHandler::HandleOpenFile(const base::ListValue* args) {
    380   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE);
    381   content::DownloadItem* file = GetDownloadByValue(args);
    382   if (file)
    383     file->OpenDownload();
    384 }
    385 
    386 void DownloadsDOMHandler::HandleDrag(const base::ListValue* args) {
    387   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG);
    388   content::DownloadItem* file = GetDownloadByValue(args);
    389   if (!file)
    390     return;
    391 
    392   content::WebContents* web_contents = GetWebUIWebContents();
    393   // |web_contents| is only NULL in the test.
    394   if (!web_contents)
    395     return;
    396 
    397   if (file->GetState() != content::DownloadItem::COMPLETE)
    398     return;
    399 
    400   gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath(
    401       file->GetTargetFilePath(), IconLoader::NORMAL);
    402   gfx::NativeView view = web_contents->GetView()->GetNativeView();
    403   {
    404     // Enable nested tasks during DnD, while |DragDownload()| blocks.
    405     base::MessageLoop::ScopedNestableTaskAllower allow(
    406         base::MessageLoop::current());
    407     download_util::DragDownload(file, icon, view);
    408   }
    409 }
    410 
    411 void DownloadsDOMHandler::HandleSaveDangerous(const base::ListValue* args) {
    412   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS);
    413   content::DownloadItem* file = GetDownloadByValue(args);
    414   if (file)
    415     ShowDangerPrompt(file);
    416 }
    417 
    418 void DownloadsDOMHandler::HandleDiscardDangerous(const base::ListValue* args) {
    419   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS);
    420   content::DownloadItem* file = GetDownloadByValue(args);
    421   if (file)
    422     file->Remove();
    423 }
    424 
    425 void DownloadsDOMHandler::HandleShow(const base::ListValue* args) {
    426   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW);
    427   content::DownloadItem* file = GetDownloadByValue(args);
    428   if (file)
    429     file->ShowDownloadInShell();
    430 }
    431 
    432 void DownloadsDOMHandler::HandlePause(const base::ListValue* args) {
    433   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE);
    434   content::DownloadItem* file = GetDownloadByValue(args);
    435   if (file)
    436     file->Pause();
    437 }
    438 
    439 void DownloadsDOMHandler::HandleResume(const base::ListValue* args) {
    440   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RESUME);
    441   content::DownloadItem* file = GetDownloadByValue(args);
    442   if (file)
    443     file->Resume();
    444 }
    445 
    446 void DownloadsDOMHandler::HandleRemove(const base::ListValue* args) {
    447   if (!IsDeletingHistoryAllowed())
    448     return;
    449 
    450   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REMOVE);
    451   content::DownloadItem* file = GetDownloadByValue(args);
    452   if (file)
    453     file->Remove();
    454 }
    455 
    456 void DownloadsDOMHandler::HandleCancel(const base::ListValue* args) {
    457   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL);
    458   content::DownloadItem* file = GetDownloadByValue(args);
    459   if (file)
    460     file->Cancel(true);
    461 }
    462 
    463 void DownloadsDOMHandler::HandleClearAll(const base::ListValue* args) {
    464   if (IsDeletingHistoryAllowed()) {
    465     CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL);
    466     // IsDeletingHistoryAllowed already checked for the existence of the
    467     // manager.
    468     main_notifier_.GetManager()->RemoveAllDownloads();
    469 
    470     // If this is an incognito downloads page, clear All should clear main
    471     // download manager as well.
    472     if (original_notifier_.get() && original_notifier_->GetManager())
    473       original_notifier_->GetManager()->RemoveAllDownloads();
    474   }
    475 
    476   // downloads.js always clears the display and relies on HandleClearAll to
    477   // ScheduleSendCurrentDownloads(). If any downloads are removed, then
    478   // OnDownloadRemoved() will call it, but if no downloads are actually removed,
    479   // then HandleClearAll needs to call it manually.
    480   ScheduleSendCurrentDownloads();
    481 }
    482 
    483 void DownloadsDOMHandler::HandleOpenDownloadsFolder(
    484     const base::ListValue* args) {
    485   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FOLDER);
    486   if (main_notifier_.GetManager()) {
    487     platform_util::OpenItem(DownloadPrefs::FromDownloadManager(
    488         main_notifier_.GetManager())->DownloadPath());
    489   }
    490 }
    491 
    492 // DownloadsDOMHandler, private: ----------------------------------------------
    493 
    494 void DownloadsDOMHandler::ScheduleSendCurrentDownloads() {
    495   // Don't call SendCurrentDownloads() every time anything changes. Batch them
    496   // together instead. This may handle hundreds of OnDownloadDestroyed() calls
    497   // in a single UI message loop iteration when the user Clears All downloads.
    498   if (update_scheduled_)
    499     return;
    500   update_scheduled_ = true;
    501   BrowserThread::PostTask(
    502       BrowserThread::UI, FROM_HERE,
    503       base::Bind(&DownloadsDOMHandler::SendCurrentDownloads,
    504                  weak_ptr_factory_.GetWeakPtr()));
    505 }
    506 
    507 void DownloadsDOMHandler::SendCurrentDownloads() {
    508   update_scheduled_ = false;
    509   content::DownloadManager::DownloadVector all_items, filtered_items;
    510   if (main_notifier_.GetManager()) {
    511     main_notifier_.GetManager()->GetAllDownloads(&all_items);
    512     main_notifier_.GetManager()->CheckForHistoryFilesRemoval();
    513   }
    514   if (original_notifier_.get() && original_notifier_->GetManager()) {
    515     original_notifier_->GetManager()->GetAllDownloads(&all_items);
    516     original_notifier_->GetManager()->CheckForHistoryFilesRemoval();
    517   }
    518   DownloadQuery query;
    519   if (search_terms_ && !search_terms_->empty()) {
    520     query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_.get());
    521   }
    522   query.AddFilter(base::Bind(&IsDownloadDisplayable));
    523   query.AddSorter(DownloadQuery::SORT_START_TIME, DownloadQuery::DESCENDING);
    524   query.Limit(kMaxDownloads);
    525   query.Search(all_items.begin(), all_items.end(), &filtered_items);
    526   base::ListValue results_value;
    527   for (content::DownloadManager::DownloadVector::const_iterator
    528        iter = filtered_items.begin(); iter != filtered_items.end(); ++iter) {
    529     results_value.Append(CreateDownloadItemValue(
    530         *iter,
    531         (original_notifier_.get() &&
    532           main_notifier_.GetManager() &&
    533           (main_notifier_.GetManager()->GetDownload((*iter)->GetId()) ==
    534           *iter))));
    535   }
    536   CallDownloadsList(results_value);
    537 }
    538 
    539 void DownloadsDOMHandler::ShowDangerPrompt(
    540     content::DownloadItem* dangerous_item) {
    541   DownloadDangerPrompt* danger_prompt = DownloadDangerPrompt::Create(
    542       dangerous_item,
    543       GetWebUIWebContents(),
    544       false,
    545       base::Bind(&DownloadsDOMHandler::DangerPromptDone,
    546                  weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId()));
    547   // danger_prompt will delete itself.
    548   DCHECK(danger_prompt);
    549 }
    550 
    551 void DownloadsDOMHandler::DangerPromptDone(
    552     int download_id, DownloadDangerPrompt::Action action) {
    553   if (action != DownloadDangerPrompt::ACCEPT)
    554     return;
    555   content::DownloadItem* item = NULL;
    556   if (main_notifier_.GetManager())
    557     item = main_notifier_.GetManager()->GetDownload(download_id);
    558   if (!item && original_notifier_.get() && original_notifier_->GetManager())
    559     item = original_notifier_->GetManager()->GetDownload(download_id);
    560   if (!item || item->IsDone())
    561     return;
    562   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS);
    563   item->ValidateDangerousDownload();
    564 }
    565 
    566 bool DownloadsDOMHandler::IsDeletingHistoryAllowed() {
    567   content::DownloadManager* manager = main_notifier_.GetManager();
    568   return (manager &&
    569           Profile::FromBrowserContext(manager->GetBrowserContext())->
    570               GetPrefs()->GetBoolean(prefs::kAllowDeletingBrowserHistory));
    571 }
    572 
    573 content::DownloadItem* DownloadsDOMHandler::GetDownloadByValue(
    574     const base::ListValue* args) {
    575   int download_id = -1;
    576   if (!ExtractIntegerValue(args, &download_id))
    577     return NULL;
    578   content::DownloadItem* item = NULL;
    579   if (main_notifier_.GetManager())
    580     item = main_notifier_.GetManager()->GetDownload(download_id);
    581   if (!item && original_notifier_.get() && original_notifier_->GetManager())
    582     item = original_notifier_->GetManager()->GetDownload(download_id);
    583   return item;
    584 }
    585 
    586 content::WebContents* DownloadsDOMHandler::GetWebUIWebContents() {
    587   return web_ui()->GetWebContents();
    588 }
    589 
    590 void DownloadsDOMHandler::CallDownloadsList(const base::ListValue& downloads) {
    591   web_ui()->CallJavascriptFunction("downloadsList", downloads);
    592 }
    593 
    594 void DownloadsDOMHandler::CallDownloadUpdated(
    595     const base::ListValue& download_item) {
    596   web_ui()->CallJavascriptFunction("downloadUpdated", download_item);
    597 }
    598