// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/webui/downloads_dom_handler.h" #include #include #include "base/basictypes.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/i18n/rtl.h" #include "base/i18n/time_formatting.h" #include "base/logging.h" #include "base/memory/singleton.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "base/supports_user_data.h" #include "base/threading/thread.h" #include "base/value_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/download/download_crx_util.h" #include "chrome/browser/download/download_danger_prompt.h" #include "chrome/browser/download/download_history.h" #include "chrome/browser/download/download_item_model.h" #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/download_query.h" #include "chrome/browser/download/download_service.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/download/drag_download_item.h" #include "chrome/browser/extensions/api/downloads/downloads_api.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/fileicon_source.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/user_metrics.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "extensions/browser/extension_system.h" #include "net/base/filename_util.h" #include "ui/base/l10n/time_format.h" #include "ui/gfx/image/image.h" using base::UserMetricsAction; using content::BrowserContext; using content::BrowserThread; namespace { // Maximum number of downloads to show. TODO(glen): Remove this and instead // stuff the downloads down the pipe slowly. static const size_t kMaxDownloads = 150; enum DownloadsDOMEvent { DOWNLOADS_DOM_EVENT_GET_DOWNLOADS = 0, DOWNLOADS_DOM_EVENT_OPEN_FILE = 1, DOWNLOADS_DOM_EVENT_DRAG = 2, DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS = 3, DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS = 4, DOWNLOADS_DOM_EVENT_SHOW = 5, DOWNLOADS_DOM_EVENT_PAUSE = 6, DOWNLOADS_DOM_EVENT_REMOVE = 7, DOWNLOADS_DOM_EVENT_CANCEL = 8, DOWNLOADS_DOM_EVENT_CLEAR_ALL = 9, DOWNLOADS_DOM_EVENT_OPEN_FOLDER = 10, DOWNLOADS_DOM_EVENT_RESUME = 11, DOWNLOADS_DOM_EVENT_MAX }; void CountDownloadsDOMEvents(DownloadsDOMEvent event) { UMA_HISTOGRAM_ENUMERATION("Download.DOMEvent", event, DOWNLOADS_DOM_EVENT_MAX); } // Returns a string constant to be used as the |danger_type| value in // CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE, // DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the // |danger_type| value is only defined if the value of |state| is |DANGEROUS|. const char* GetDangerTypeString(content::DownloadDangerType danger_type) { switch (danger_type) { case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: return "DANGEROUS_FILE"; case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: return "DANGEROUS_URL"; case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: return "DANGEROUS_CONTENT"; case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: return "UNCOMMON_CONTENT"; case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: return "DANGEROUS_HOST"; case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: return "POTENTIALLY_UNWANTED"; default: // Don't return a danger type string if it is NOT_DANGEROUS or // MAYBE_DANGEROUS_CONTENT. NOTREACHED(); return ""; } } // Returns a JSON dictionary containing some of the attributes of |download|. // The JSON dictionary will also have a field "id" set to |id|, and a field // "otr" set to |incognito|. base::DictionaryValue* CreateDownloadItemValue( content::DownloadItem* download_item, bool incognito) { // TODO(asanka): Move towards using download_model here for getting status and // progress. The difference currently only matters to Drive downloads and // those don't show up on the downloads page, but should. DownloadItemModel download_model(download_item); // The items which are to be written into file_value are also described in // chrome/browser/resources/downloads/downloads.js in @typedef for // BackendDownloadObject. Please update it whenever you add or remove // any keys in file_value. base::DictionaryValue* file_value = new base::DictionaryValue(); file_value->SetInteger( "started", static_cast(download_item->GetStartTime().ToTimeT())); file_value->SetString( "since_string", ui::TimeFormat::RelativeDate( download_item->GetStartTime(), NULL)); file_value->SetString( "date_string", base::TimeFormatShortDate(download_item->GetStartTime())); file_value->SetString("id", base::Uint64ToString(download_item->GetId())); base::FilePath download_path(download_item->GetTargetFilePath()); file_value->Set("file_path", base::CreateFilePathValue(download_path)); file_value->SetString("file_url", net::FilePathToFileURL(download_path).spec()); extensions::DownloadedByExtension* by_ext = extensions::DownloadedByExtension::Get(download_item); if (by_ext) { file_value->SetString("by_ext_id", by_ext->id()); file_value->SetString("by_ext_name", by_ext->name()); // Lookup the extension's current name() in case the user changed their // language. This won't work if the extension was uninstalled, so the name // might be the wrong language. bool include_disabled = true; const extensions::Extension* extension = extensions::ExtensionSystem::Get( Profile::FromBrowserContext(download_item->GetBrowserContext()))-> extension_service()->GetExtensionById(by_ext->id(), include_disabled); if (extension) file_value->SetString("by_ext_name", extension->name()); } // Keep file names as LTR. base::string16 file_name = download_item->GetFileNameToReportUser().LossyDisplayName(); file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name); file_value->SetString("file_name", file_name); file_value->SetString("url", download_item->GetURL().spec()); file_value->SetBoolean("otr", incognito); file_value->SetInteger("total", static_cast( download_item->GetTotalBytes())); file_value->SetBoolean("file_externally_removed", download_item->GetFileExternallyRemoved()); file_value->SetBoolean("retry", false); // Overridden below if needed. file_value->SetBoolean("resume", download_item->CanResume()); switch (download_item->GetState()) { case content::DownloadItem::IN_PROGRESS: if (download_item->IsDangerous()) { file_value->SetString("state", "DANGEROUS"); // These are the only danger states that the UI is equipped to handle. DCHECK(download_item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || download_item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || download_item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || download_item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || download_item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || download_item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED); const char* danger_type_value = GetDangerTypeString(download_item->GetDangerType()); file_value->SetString("danger_type", danger_type_value); } else if (download_item->IsPaused()) { file_value->SetString("state", "PAUSED"); } else { file_value->SetString("state", "IN_PROGRESS"); } file_value->SetString("progress_status_text", download_model.GetTabProgressStatusText()); file_value->SetInteger("percent", std::max(0, static_cast(download_item->PercentComplete()))); file_value->SetInteger("received", static_cast(download_item->GetReceivedBytes())); break; case content::DownloadItem::INTERRUPTED: file_value->SetString("state", "INTERRUPTED"); file_value->SetString("progress_status_text", download_model.GetTabProgressStatusText()); file_value->SetInteger("percent", static_cast(download_item->PercentComplete())); file_value->SetInteger("received", static_cast(download_item->GetReceivedBytes())); file_value->SetString("last_reason_text", download_model.GetInterruptReasonText()); if (content::DOWNLOAD_INTERRUPT_REASON_CRASH == download_item->GetLastReason() && !download_item->CanResume()) file_value->SetBoolean("retry", true); break; case content::DownloadItem::CANCELLED: file_value->SetString("state", "CANCELLED"); file_value->SetBoolean("retry", true); break; case content::DownloadItem::COMPLETE: DCHECK(!download_item->IsDangerous()); file_value->SetString("state", "COMPLETE"); break; case content::DownloadItem::MAX_DOWNLOAD_STATE: NOTREACHED(); } return file_value; } // Filters out extension downloads and downloads that don't have a filename yet. bool IsDownloadDisplayable(const content::DownloadItem& item) { return !download_crx_util::IsExtensionDownload(item) && !item.IsTemporary() && !item.GetFileNameToReportUser().empty() && !item.GetTargetFilePath().empty() && DownloadItemModel( const_cast(&item)).ShouldShowInShelf(); } } // namespace DownloadsDOMHandler::DownloadsDOMHandler(content::DownloadManager* dlm) : main_notifier_(dlm, this), update_scheduled_(false), weak_ptr_factory_(this) { // Create our fileicon data source. Profile* profile = Profile::FromBrowserContext(dlm->GetBrowserContext()); content::URLDataSource::Add(profile, new FileIconSource()); if (profile->IsOffTheRecord()) { original_notifier_.reset(new AllDownloadItemNotifier( BrowserContext::GetDownloadManager(profile->GetOriginalProfile()), this)); } } DownloadsDOMHandler::~DownloadsDOMHandler() { FinalizeRemovals(); } // DownloadsDOMHandler, public: ----------------------------------------------- void DownloadsDOMHandler::RegisterMessages() { web_ui()->RegisterMessageCallback("getDownloads", base::Bind(&DownloadsDOMHandler::HandleGetDownloads, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("openFile", base::Bind(&DownloadsDOMHandler::HandleOpenFile, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("drag", base::Bind(&DownloadsDOMHandler::HandleDrag, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("saveDangerous", base::Bind(&DownloadsDOMHandler::HandleSaveDangerous, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("discardDangerous", base::Bind(&DownloadsDOMHandler::HandleDiscardDangerous, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("show", base::Bind(&DownloadsDOMHandler::HandleShow, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("pause", base::Bind(&DownloadsDOMHandler::HandlePause, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("resume", base::Bind(&DownloadsDOMHandler::HandleResume, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("remove", base::Bind(&DownloadsDOMHandler::HandleRemove, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("undo", base::Bind(&DownloadsDOMHandler::HandleUndo, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("cancel", base::Bind(&DownloadsDOMHandler::HandleCancel, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("clearAll", base::Bind(&DownloadsDOMHandler::HandleClearAll, weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback("openDownloadsFolder", base::Bind(&DownloadsDOMHandler::HandleOpenDownloadsFolder, weak_ptr_factory_.GetWeakPtr())); } void DownloadsDOMHandler::OnDownloadCreated( content::DownloadManager* manager, content::DownloadItem* download_item) { if (IsDownloadDisplayable(*download_item)) ScheduleSendCurrentDownloads(); else new_downloads_.insert(download_item->GetId()); } void DownloadsDOMHandler::OnDownloadUpdated( content::DownloadManager* manager, content::DownloadItem* download_item) { bool showing_new_item = false; if (new_downloads_.count(download_item->GetId())) { // A new download (that the page doesn't know about yet) has been updated. if (!IsDownloadDisplayable(*download_item)) { // Item isn't ready to be displayed yet. Wait until it is. return; } new_downloads_.erase(download_item->GetId()); showing_new_item = true; } if (showing_new_item || DownloadItemModel(download_item).IsBeingRevived() || !IsDownloadDisplayable(*download_item)) { // A download will be shown or hidden by this update. Resend the list. ScheduleSendCurrentDownloads(); return; } if (search_terms_ && !search_terms_->empty()) { // Don't CallUpdateItem() if download_item doesn't match // search_terms_. // TODO(benjhayden): Consider splitting MatchesQuery() out to a function. content::DownloadManager::DownloadVector all_items, filtered_items; all_items.push_back(download_item); DownloadQuery query; query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_); query.Search(all_items.begin(), all_items.end(), &filtered_items); if (filtered_items.empty()) return; } scoped_ptr item(CreateDownloadItemValue( download_item, original_notifier_ && manager == main_notifier_.GetManager())); CallUpdateItem(*item); } void DownloadsDOMHandler::OnDownloadRemoved( content::DownloadManager* manager, content::DownloadItem* download_item) { if (!DownloadItemModel(download_item).ShouldShowInShelf()) return; // This relies on |download_item| being removed from DownloadManager in this // MessageLoop iteration. |download_item| may not have been removed from // DownloadManager when OnDownloadRemoved() is fired, so bounce off the // MessageLoop to give it a chance to be removed. SendCurrentDownloads() looks // at all downloads, and we do not tell it that |download_item| is being // removed. If DownloadManager is ever changed to not immediately remove // |download_item| from its map when OnDownloadRemoved is sent, then // DownloadsDOMHandler::OnDownloadRemoved() will need to explicitly tell // SendCurrentDownloads() that |download_item| was removed. A // SupportsUserData::Data would be the correct way to do this. ScheduleSendCurrentDownloads(); } void DownloadsDOMHandler::HandleGetDownloads(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS); search_terms_.reset(args && !args->empty() ? args->DeepCopy() : NULL); ScheduleSendCurrentDownloads(); } void DownloadsDOMHandler::HandleOpenFile(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->OpenDownload(); } void DownloadsDOMHandler::HandleDrag(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG); content::DownloadItem* file = GetDownloadByValue(args); if (!file) return; content::WebContents* web_contents = GetWebUIWebContents(); // |web_contents| is only NULL in the test. if (!web_contents) return; if (file->GetState() != content::DownloadItem::COMPLETE) return; gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath( file->GetTargetFilePath(), IconLoader::NORMAL); gfx::NativeView view = web_contents->GetNativeView(); { // Enable nested tasks during DnD, while |DragDownload()| blocks. base::MessageLoop::ScopedNestableTaskAllower allow( base::MessageLoop::current()); DragDownloadItem(file, icon, view); } } void DownloadsDOMHandler::HandleSaveDangerous(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); content::DownloadItem* file = GetDownloadByValue(args); if (file) ShowDangerPrompt(file); } void DownloadsDOMHandler::HandleDiscardDangerous(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->Remove(); } void DownloadsDOMHandler::HandleShow(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->ShowDownloadInShell(); } void DownloadsDOMHandler::HandlePause(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->Pause(); } void DownloadsDOMHandler::HandleResume(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RESUME); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->Resume(); } void DownloadsDOMHandler::HandleRemove(const base::ListValue* args) { if (!IsDeletingHistoryAllowed()) return; CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REMOVE); content::DownloadItem* file = GetDownloadByValue(args); if (!file) return; std::vector downloads; downloads.push_back(file); RemoveDownloads(downloads); } void DownloadsDOMHandler::HandleUndo(const base::ListValue* args) { // TODO(dbeam): handle more than removed downloads someday? if (removals_.empty()) return; const std::set last_removed_ids = removals_.back(); removals_.pop_back(); for (auto id : last_removed_ids) { content::DownloadItem* download = GetDownloadById(id); if (!download) continue; DownloadItemModel model(download); model.SetShouldShowInShelf(true); model.SetIsBeingRevived(true); download->UpdateObservers(); model.SetIsBeingRevived(false); } } void DownloadsDOMHandler::HandleCancel(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->Cancel(true); } void DownloadsDOMHandler::HandleClearAll(const base::ListValue* args) { if (!IsDeletingHistoryAllowed()) { // This should only be reached during tests. return; } CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL); std::vector downloads; if (GetMainNotifierManager()) GetMainNotifierManager()->GetAllDownloads(&downloads); if (original_notifier_ && original_notifier_->GetManager()) original_notifier_->GetManager()->GetAllDownloads(&downloads); RemoveDownloads(downloads); } void DownloadsDOMHandler::RemoveDownloads( const std::vector& to_remove) { std::set ids; for (auto* download : to_remove) { DownloadItemModel item_model(download); if (!item_model.ShouldShowInShelf() || download->GetState() == content::DownloadItem::IN_PROGRESS) { continue; } item_model.SetShouldShowInShelf(false); ids.insert(download->GetId()); download->UpdateObservers(); } if (!ids.empty()) removals_.push_back(ids); } void DownloadsDOMHandler::HandleOpenDownloadsFolder( const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FOLDER); content::DownloadManager* manager = main_notifier_.GetManager(); if (manager) { platform_util::OpenItem( Profile::FromBrowserContext(manager->GetBrowserContext()), DownloadPrefs::FromDownloadManager(manager)->DownloadPath(), platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback()); } } // DownloadsDOMHandler, private: ---------------------------------------------- void DownloadsDOMHandler::ScheduleSendCurrentDownloads() { // Don't call SendCurrentDownloads() every time anything changes. Batch them // together instead. This may handle hundreds of OnDownloadDestroyed() calls // in a single UI message loop iteration when the user Clears All downloads. if (update_scheduled_) return; update_scheduled_ = true; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DownloadsDOMHandler::SendCurrentDownloads, weak_ptr_factory_.GetWeakPtr())); } content::DownloadManager* DownloadsDOMHandler::GetMainNotifierManager() { return main_notifier_.GetManager(); } void DownloadsDOMHandler::FinalizeRemovals() { while (!removals_.empty()) { const std::set remove = removals_.back(); removals_.pop_back(); for (const auto id : remove) { content::DownloadItem* download = GetDownloadById(id); if (download) download->Remove(); } } } void DownloadsDOMHandler::SendCurrentDownloads() { update_scheduled_ = false; content::DownloadManager::DownloadVector all_items, filtered_items; if (main_notifier_.GetManager()) { main_notifier_.GetManager()->GetAllDownloads(&all_items); main_notifier_.GetManager()->CheckForHistoryFilesRemoval(); } if (original_notifier_ && original_notifier_->GetManager()) { original_notifier_->GetManager()->GetAllDownloads(&all_items); original_notifier_->GetManager()->CheckForHistoryFilesRemoval(); } DownloadQuery query; if (search_terms_ && !search_terms_->empty()) query.AddFilter(DownloadQuery::FILTER_QUERY, *search_terms_); query.AddFilter(base::Bind(&IsDownloadDisplayable)); query.AddSorter(DownloadQuery::SORT_START_TIME, DownloadQuery::DESCENDING); query.Limit(kMaxDownloads); query.Search(all_items.begin(), all_items.end(), &filtered_items); base::ListValue results_value; for (auto* item : filtered_items) { results_value.Append(CreateDownloadItemValue( item, original_notifier_ && main_notifier_.GetManager() && main_notifier_.GetManager()->GetDownload(item->GetId()) == item)); } CallUpdateAll(results_value); } void DownloadsDOMHandler::ShowDangerPrompt( content::DownloadItem* dangerous_item) { DownloadDangerPrompt* danger_prompt = DownloadDangerPrompt::Create( dangerous_item, GetWebUIWebContents(), false, base::Bind(&DownloadsDOMHandler::DangerPromptDone, weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId())); // danger_prompt will delete itself. DCHECK(danger_prompt); } void DownloadsDOMHandler::DangerPromptDone( int download_id, DownloadDangerPrompt::Action action) { if (action != DownloadDangerPrompt::ACCEPT) return; content::DownloadItem* item = NULL; if (main_notifier_.GetManager()) item = main_notifier_.GetManager()->GetDownload(download_id); if (!item && original_notifier_.get() && original_notifier_->GetManager()) item = original_notifier_->GetManager()->GetDownload(download_id); if (!item || item->IsDone()) return; CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); item->ValidateDangerousDownload(); } bool DownloadsDOMHandler::IsDeletingHistoryAllowed() { content::DownloadManager* manager = main_notifier_.GetManager(); return manager && Profile::FromBrowserContext(manager->GetBrowserContext())-> GetPrefs()->GetBoolean(prefs::kAllowDeletingBrowserHistory); } content::DownloadItem* DownloadsDOMHandler::GetDownloadByValue( const base::ListValue* args) { std::string download_id; if (!args->GetString(0, &download_id)) { NOTREACHED(); return nullptr; } uint64 id; if (!base::StringToUint64(download_id, &id)) { NOTREACHED(); return nullptr; } return GetDownloadById(static_cast(id)); } content::DownloadItem* DownloadsDOMHandler::GetDownloadById(uint32 id) { content::DownloadItem* item = NULL; if (GetMainNotifierManager()) item = GetMainNotifierManager()->GetDownload(id); if (!item && original_notifier_ && original_notifier_->GetManager()) item = original_notifier_->GetManager()->GetDownload(id); return item; } content::WebContents* DownloadsDOMHandler::GetWebUIWebContents() { return web_ui()->GetWebContents(); } void DownloadsDOMHandler::CallUpdateAll(const base::ListValue& list) { web_ui()->CallJavascriptFunction("downloads.Manager.updateAll", list); } void DownloadsDOMHandler::CallUpdateItem(const base::DictionaryValue& item) { web_ui()->CallJavascriptFunction("downloads.Manager.updateItem", item); }