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