// 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/memory/singleton.h" #include "base/metrics/histogram.h" #include "base/string_piece.h" #include "base/threading/thread.h" #include "base/utf_string_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_prefs.h" #include "chrome/browser/download/download_service.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/download/download_util.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/tab_contents/tab_contents.h" #include "chrome/browser/ui/webui/chrome_url_data_manager.h" #include "chrome/browser/ui/webui/fileicon_source.h" #include "chrome/common/url_constants.h" #include "content/public/browser/download_item.h" #include "content/public/browser/user_metrics.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "grit/generated_resources.h" #include "ui/gfx/image/image.h" #if !defined(OS_MACOSX) #include "content/public/browser/browser_thread.h" #endif #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/extensions/file_manager_util.h" #endif using content::BrowserContext; using content::BrowserThread; using content::UserMetricsAction; namespace { // Maximum number of downloads to show. TODO(glen): Remove this and instead // stuff the downloads down the pipe slowly. static const int kMaxDownloads = 150; // Sort DownloadItems into descending order by their start time. class DownloadItemSorter : public std::binary_function { public: bool operator()(const content::DownloadItem* lhs, const content::DownloadItem* rhs) { return lhs->GetStartTime() > rhs->GetStartTime(); } }; 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_MAX }; void CountDownloadsDOMEvents(DownloadsDOMEvent event) { UMA_HISTOGRAM_ENUMERATION("Download.DOMEvent", event, DOWNLOADS_DOM_EVENT_MAX); } } // namespace DownloadsDOMHandler::DownloadsDOMHandler(content::DownloadManager* dlm) : search_text_(), download_manager_(dlm), original_profile_download_manager_(NULL), initialized_(false), ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { // Create our fileicon data source. Profile* profile = Profile::FromBrowserContext(dlm->GetBrowserContext()); ChromeURLDataManager::AddDataSource(profile, new FileIconSource()); // Figure out our parent DownloadManager, if any. Profile* original_profile = profile->GetOriginalProfile(); if (original_profile != profile) { original_profile_download_manager_ = BrowserContext::GetDownloadManager(original_profile); } } DownloadsDOMHandler::~DownloadsDOMHandler() { ClearDownloadItems(); download_manager_->RemoveObserver(this); if (original_profile_download_manager_) original_profile_download_manager_->RemoveObserver(this); } // DownloadsDOMHandler, public: ----------------------------------------------- void DownloadsDOMHandler::OnPageLoaded(const base::ListValue* args) { if (initialized_) return; initialized_ = true; download_manager_->AddObserver(this); if (original_profile_download_manager_) original_profile_download_manager_->AddObserver(this); } void DownloadsDOMHandler::RegisterMessages() { web_ui()->RegisterMessageCallback("onPageLoaded", base::Bind(&DownloadsDOMHandler::OnPageLoaded, base::Unretained(this))); web_ui()->RegisterMessageCallback("getDownloads", base::Bind(&DownloadsDOMHandler::HandleGetDownloads, base::Unretained(this))); web_ui()->RegisterMessageCallback("openFile", base::Bind(&DownloadsDOMHandler::HandleOpenFile, base::Unretained(this))); web_ui()->RegisterMessageCallback("drag", base::Bind(&DownloadsDOMHandler::HandleDrag, base::Unretained(this))); web_ui()->RegisterMessageCallback("saveDangerous", base::Bind(&DownloadsDOMHandler::HandleSaveDangerous, base::Unretained(this))); web_ui()->RegisterMessageCallback("discardDangerous", base::Bind(&DownloadsDOMHandler::HandleDiscardDangerous, base::Unretained(this))); web_ui()->RegisterMessageCallback("show", base::Bind(&DownloadsDOMHandler::HandleShow, base::Unretained(this))); web_ui()->RegisterMessageCallback("togglepause", base::Bind(&DownloadsDOMHandler::HandlePause, base::Unretained(this))); web_ui()->RegisterMessageCallback("resume", base::Bind(&DownloadsDOMHandler::HandlePause, base::Unretained(this))); web_ui()->RegisterMessageCallback("remove", base::Bind(&DownloadsDOMHandler::HandleRemove, base::Unretained(this))); web_ui()->RegisterMessageCallback("cancel", base::Bind(&DownloadsDOMHandler::HandleCancel, base::Unretained(this))); web_ui()->RegisterMessageCallback("clearAll", base::Bind(&DownloadsDOMHandler::HandleClearAll, base::Unretained(this))); web_ui()->RegisterMessageCallback("openDownloadsFolder", base::Bind(&DownloadsDOMHandler::HandleOpenDownloadsFolder, base::Unretained(this))); } void DownloadsDOMHandler::OnDownloadUpdated(content::DownloadItem* download) { // Get the id for the download. Our downloads are sorted latest to first, // and the id is the index into that list. We should be careful of sync // errors between the UI and the download_items_ list (we may wish to use // something other than 'id'). OrderedDownloads::iterator it = std::find(download_items_.begin(), download_items_.end(), download); if (it == download_items_.end()) return; if (download->GetState() == content::DownloadItem::REMOVING) { (*it)->RemoveObserver(this); *it = NULL; // A later ModelChanged() notification will change the WebUI's // view of the downloads list. return; } const int id = static_cast(it - download_items_.begin()); ListValue results_value; results_value.Append(download_util::CreateDownloadItemValue(download, id)); web_ui()->CallJavascriptFunction("downloadUpdated", results_value); } // A download has started or been deleted. Query our DownloadManager for the // current set of downloads. void DownloadsDOMHandler::ModelChanged(content::DownloadManager* manager) { DCHECK(manager == download_manager_ || manager == original_profile_download_manager_); ClearDownloadItems(); download_manager_->SearchDownloads(WideToUTF16(search_text_), &download_items_); // If we have a parent DownloadManager, let it add its downloads to the // results. if (original_profile_download_manager_) { original_profile_download_manager_->SearchDownloads( WideToUTF16(search_text_), &download_items_); } sort(download_items_.begin(), download_items_.end(), DownloadItemSorter()); // Remove any extension downloads. for (size_t i = 0; i < download_items_.size();) { if (download_crx_util::IsExtensionDownload(*download_items_[i])) download_items_.erase(download_items_.begin() + i); else i++; } // Add ourself to all download items as an observer. for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (static_cast(it - download_items_.begin()) > kMaxDownloads) break; // We should never see anything that isn't already in the history. DCHECK(*it); DCHECK((*it)->IsPersisted()); (*it)->AddObserver(this); } SendCurrentDownloads(); } void DownloadsDOMHandler::ManagerGoingDown(content::DownloadManager* manager) { // This should never happen. The lifetime of the DownloadsDOMHandler // is tied to the tab in which downloads.html is displayed, which cannot // outlive the Browser that contains it, which cannot outlive the Profile // it is associated with. If that profile is an incognito profile, // it cannot outlive its original profile. Thus this class should be // destroyed before a ManagerGoingDown() notification occurs. NOTREACHED(); } void DownloadsDOMHandler::HandleGetDownloads(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS); std::wstring new_search = UTF16ToWideHack(ExtractStringValue(args)); if (search_text_.compare(new_search) != 0) { search_text_ = new_search; ModelChanged(download_manager_); } else { SendCurrentDownloads(); } download_manager_->CheckForHistoryFilesRemoval(); } void DownloadsDOMHandler::HandleOpenFile(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->OpenDownload(); } void DownloadsDOMHandler::HandleDrag(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG); content::DownloadItem* file = GetDownloadByValue(args); if (file) { IconManager* im = g_browser_process->icon_manager(); gfx::Image* icon = im->LookupIcon(file->GetUserVerifiedFilePath(), IconLoader::NORMAL); gfx::NativeView view = web_ui()->GetWebContents()->GetNativeView(); { // Enable nested tasks during DnD, while |DragDownload()| blocks. MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); download_util::DragDownload(file, icon, view); } } } void DownloadsDOMHandler::HandleSaveDangerous(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); content::DownloadItem* file = GetDownloadByValue(args); if (file) ShowDangerPrompt(file); // TODO(benjhayden): else ModelChanged()? Downloads might be able to disappear // out from under us, so update our idea of the downloads as soon as possible. } void DownloadsDOMHandler::HandleDiscardDangerous(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->Delete(content::DownloadItem::DELETE_DUE_TO_USER_DISCARD); } void DownloadsDOMHandler::HandleShow(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->ShowDownloadInShell(); } void DownloadsDOMHandler::HandlePause(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->TogglePause(); } void DownloadsDOMHandler::HandleRemove(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REMOVE); content::DownloadItem* file = GetDownloadByValue(args); if (file) { DCHECK(file->IsPersisted()); file->Remove(); } } void DownloadsDOMHandler::HandleCancel(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL); content::DownloadItem* file = GetDownloadByValue(args); if (file) file->Cancel(true); } void DownloadsDOMHandler::HandleClearAll(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL); download_manager_->RemoveAllDownloads(); // If this is an incognito downloader, clear All should clear main download // manager as well. if (original_profile_download_manager_) original_profile_download_manager_->RemoveAllDownloads(); } void DownloadsDOMHandler::HandleOpenDownloadsFolder(const ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FOLDER); platform_util::OpenItem( DownloadPrefs::FromDownloadManager(download_manager_)->download_path()); } // DownloadsDOMHandler, private: ---------------------------------------------- void DownloadsDOMHandler::SendCurrentDownloads() { ListValue results_value; for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (!*it) continue; int index = static_cast(it - download_items_.begin()); if (index <= kMaxDownloads) results_value.Append(download_util::CreateDownloadItemValue(*it, index)); } web_ui()->CallJavascriptFunction("downloadsList", results_value); } void DownloadsDOMHandler::ShowDangerPrompt( content::DownloadItem* dangerous_item) { DownloadDangerPrompt* danger_prompt = DownloadDangerPrompt::Create( dangerous_item, TabContents::FromWebContents(web_ui()->GetWebContents()), base::Bind(&DownloadsDOMHandler::DangerPromptAccepted, weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId()), base::Closure()); // danger_prompt will delete itself. DCHECK(danger_prompt); } void DownloadsDOMHandler::DangerPromptAccepted(int download_id) { content::DownloadItem* item = download_manager_->GetActiveDownloadItem( download_id); if (!item) return; CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); item->DangerousDownloadValidated(); } void DownloadsDOMHandler::ClearDownloadItems() { // Clear out old state and remove self as observer for each download. for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (!*it) continue; (*it)->RemoveObserver(this); } download_items_.clear(); } content::DownloadItem* DownloadsDOMHandler::GetDownloadById(int id) { for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (static_cast(it - download_items_.begin() == id)) { return (*it); } } return NULL; } content::DownloadItem* DownloadsDOMHandler::GetDownloadByValue( const ListValue* args) { int id; if (ExtractIntegerValue(args, &id)) { return GetDownloadById(id); } return NULL; }