// Copyright (c) 2011 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/active_downloads_ui.h" #include <algorithm> #include <string> #include <vector> #include "base/callback.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/logging.h" #include "base/memory/singleton.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/string_piece.h" #include "base/string_util.h" #include "base/threading/thread.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/download/download_item.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/webui/favicon_source.h" #include "chrome/browser/ui/webui/mediaplayer_ui.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/net/url_fetcher.h" #include "chrome/common/url_constants.h" #include "content/browser/browser_thread.h" #include "content/browser/tab_contents/tab_contents.h" #include "grit/browser_resources.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "net/base/escape.h" #include "net/url_request/url_request_file_job.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" namespace { static const int kPopupLeft = 0; static const int kPopupTop = 0; static const int kPopupWidth = 250; // Minimum height of window must be 100, so kPopupHeight has space for // 2 download rows of 36 px and 'Show All Downloads' which is 29px. static const int kPopupHeight = 36 * 2 + 29; static const char kPropertyPath[] = "path"; static const char kPropertyTitle[] = "title"; static const char kPropertyDirectory[] = "isDirectory"; class ActiveDownloadsUIHTMLSource : public ChromeURLDataManager::DataSource { public: ActiveDownloadsUIHTMLSource(); // Called when the network layer has requested a resource underneath // the path we registered. virtual void StartDataRequest(const std::string& path, bool is_incognito, int request_id); virtual std::string GetMimeType(const std::string&) const { return "text/html"; } private: ~ActiveDownloadsUIHTMLSource() {} DISALLOW_COPY_AND_ASSIGN(ActiveDownloadsUIHTMLSource); }; // The handler for Javascript messages related to the "active_downloads" view. class ActiveDownloadsHandler : public WebUIMessageHandler, public DownloadManager::Observer, public DownloadItem::Observer { public: ActiveDownloadsHandler(); virtual ~ActiveDownloadsHandler(); // Initialization after Attach. void Init(); // WebUIMessageHandler implementation. virtual WebUIMessageHandler* Attach(WebUI* web_ui); virtual void RegisterMessages(); // DownloadItem::Observer interface. virtual void OnDownloadUpdated(DownloadItem* item); virtual void OnDownloadOpened(DownloadItem* item) { } // DownloadManager::Observer interface. virtual void ModelChanged(); // WebUI Callbacks. void HandleGetDownloads(const ListValue* args); void HandlePauseToggleDownload(const ListValue* args); void HandleCancelDownload(const ListValue* args); void HandleAllowDownload(const ListValue* args); void OpenNewPopupWindow(const ListValue* args); void OpenNewFullWindow(const ListValue* args); void PlayMediaFile(const ListValue* args); private: // Downloads helpers. DownloadItem* GetDownloadById(const ListValue* args); void UpdateDownloadList(); void SendDownloads(); void AddDownload(DownloadItem* item); void OpenNewWindow(const ListValue* args, bool popup); Profile* profile_; TabContents* tab_contents_; DownloadManager* download_manager_; typedef std::vector<DownloadItem*> DownloadList; DownloadList active_downloads_; DownloadList downloads_; DISALLOW_COPY_AND_ASSIGN(ActiveDownloadsHandler); }; //////////////////////////////////////////////////////////////////////////////// // // ActiveDownloadsUIHTMLSource // //////////////////////////////////////////////////////////////////////////////// ActiveDownloadsUIHTMLSource::ActiveDownloadsUIHTMLSource() : DataSource(chrome::kChromeUIActiveDownloadsHost, MessageLoop::current()) { } void ActiveDownloadsUIHTMLSource::StartDataRequest(const std::string& path, bool is_incognito, int request_id) { DictionaryValue localized_strings; localized_strings.SetString("allowdownload", l10n_util::GetStringUTF16(IDS_FILEBROWSER_CONFIRM_DOWNLOAD)); localized_strings.SetString("cancel", l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_CANCEL)); localized_strings.SetString("confirmcancel", l10n_util::GetStringUTF16(IDS_FILEBROWSER_CONFIRM_CANCEL)); localized_strings.SetString("confirmyes", l10n_util::GetStringUTF16(IDS_FILEBROWSER_CONFIRM_YES)); localized_strings.SetString("open", l10n_util::GetStringUTF16(IDS_FILEBROWSER_OPEN)); localized_strings.SetString("pause", l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_PAUSE)); localized_strings.SetString("resume", l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_RESUME)); localized_strings.SetString("showalldownloads", l10n_util::GetStringUTF16(IDS_FILEBROWSER_SHOW_ALL_DOWNLOADS)); FilePath default_download_path; if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &default_download_path)) { NOTREACHED(); } // TODO(viettrungluu): this is wrong -- FilePath's need not be Unicode. localized_strings.SetString("downloadpath", default_download_path.value()); localized_strings.SetString("error_unknown_file_type", l10n_util::GetStringUTF16(IDS_FILEBROWSER_ERROR_UNKNOWN_FILE_TYPE)); SetFontAndTextDirection(&localized_strings); static const base::StringPiece active_downloads_html( ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_ACTIVE_DOWNLOADS_HTML)); const std::string full_html = jstemplate_builder::GetI18nTemplateHtml( active_downloads_html, &localized_strings); scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); html_bytes->data.resize(full_html.size()); std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); SendResponse(request_id, html_bytes); } //////////////////////////////////////////////////////////////////////////////// // // ActiveDownloadsHandler // //////////////////////////////////////////////////////////////////////////////// ActiveDownloadsHandler::ActiveDownloadsHandler() : profile_(NULL), tab_contents_(NULL), download_manager_(NULL) { } ActiveDownloadsHandler::~ActiveDownloadsHandler() { for (size_t i = 0; i < downloads_.size(); ++i) { downloads_[i]->RemoveObserver(this); } download_manager_->RemoveObserver(this); } WebUIMessageHandler* ActiveDownloadsHandler::Attach(WebUI* web_ui) { // Create our favicon data source. profile_ = web_ui->GetProfile(); profile_->GetChromeURLDataManager()->AddDataSource( new FaviconSource(profile_)); tab_contents_ = web_ui->tab_contents(); return WebUIMessageHandler::Attach(web_ui); } void ActiveDownloadsHandler::Init() { download_manager_ = profile_->GetDownloadManager(); download_manager_->AddObserver(this); } void ActiveDownloadsHandler::RegisterMessages() { web_ui_->RegisterMessageCallback("getDownloads", NewCallback(this, &ActiveDownloadsHandler::HandleGetDownloads)); web_ui_->RegisterMessageCallback("pauseToggleDownload", NewCallback(this, &ActiveDownloadsHandler::HandlePauseToggleDownload)); web_ui_->RegisterMessageCallback("cancelDownload", NewCallback(this, &ActiveDownloadsHandler::HandleCancelDownload)); web_ui_->RegisterMessageCallback("allowDownload", NewCallback(this, &ActiveDownloadsHandler::HandleAllowDownload)); web_ui_->RegisterMessageCallback("openNewPopupWindow", NewCallback(this, &ActiveDownloadsHandler::OpenNewPopupWindow)); web_ui_->RegisterMessageCallback("openNewFullWindow", NewCallback(this, &ActiveDownloadsHandler::OpenNewFullWindow)); web_ui_->RegisterMessageCallback("playMediaFile", NewCallback(this, &ActiveDownloadsHandler::PlayMediaFile)); } void ActiveDownloadsHandler::PlayMediaFile(const ListValue* args) { FilePath file_path(UTF16ToUTF8(ExtractStringValue(args))); Browser* browser = Browser::GetBrowserForController( &tab_contents_->controller(), NULL); MediaPlayer* mediaplayer = MediaPlayer::GetInstance(); mediaplayer->ForcePlayMediaFile(profile_, file_path, browser); } DownloadItem* ActiveDownloadsHandler::GetDownloadById( const ListValue* args) { int i; if (!ExtractIntegerValue(args, &i)) return NULL; size_t id(i); return id < downloads_.size() ? downloads_[id] : NULL; } void ActiveDownloadsHandler::HandlePauseToggleDownload(const ListValue* args) { DownloadItem* item = GetDownloadById(args); if (item && item->IsPartialDownload()) item->TogglePause(); } void ActiveDownloadsHandler::HandleAllowDownload(const ListValue* args) { DownloadItem* item = GetDownloadById(args); if (item) item->DangerousDownloadValidated(); } void ActiveDownloadsHandler::HandleCancelDownload(const ListValue* args) { DownloadItem* item = GetDownloadById(args); if (item) { if (item->IsPartialDownload()) item->Cancel(true); item->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD); } } void ActiveDownloadsHandler::OpenNewFullWindow(const ListValue* args) { OpenNewWindow(args, false); } void ActiveDownloadsHandler::OpenNewPopupWindow(const ListValue* args) { OpenNewWindow(args, true); } void ActiveDownloadsHandler::OpenNewWindow(const ListValue* args, bool popup) { std::string url = UTF16ToUTF8(ExtractStringValue(args)); Browser* browser = popup ? Browser::CreateForType(Browser::TYPE_APP_PANEL, profile_) : BrowserList::GetLastActive(); browser::NavigateParams params(browser, GURL(url), PageTransition::LINK); params.disposition = NEW_FOREGROUND_TAB; browser::Navigate(¶ms); // TODO(beng): The following two calls should be automatic by Navigate(). if (popup) params.browser->window()->SetBounds(gfx::Rect(0, 0, 400, 300)); params.browser->window()->Show(); } void ActiveDownloadsHandler::ModelChanged() { UpdateDownloadList(); } void ActiveDownloadsHandler::HandleGetDownloads(const ListValue* args) { UpdateDownloadList(); } void ActiveDownloadsHandler::UpdateDownloadList() { DownloadList downloads; download_manager_->GetAllDownloads(FilePath(), &downloads); active_downloads_.clear(); for (size_t i = 0; i < downloads.size(); ++i) { AddDownload(downloads[i]); } SendDownloads(); } void ActiveDownloadsHandler::AddDownload(DownloadItem* item) { // Observe in progress and dangerous downloads. if (item->state() == DownloadItem::IN_PROGRESS || item->safety_state() == DownloadItem::DANGEROUS) { active_downloads_.push_back(item); DownloadList::const_iterator it = std::find(downloads_.begin(), downloads_.end(), item); if (it == downloads_.end()) { downloads_.push_back(item); item->AddObserver(this); } } } void ActiveDownloadsHandler::SendDownloads() { ListValue results; for (size_t i = 0; i < downloads_.size(); ++i) { results.Append(download_util::CreateDownloadItemValue(downloads_[i], i)); } web_ui_->CallJavascriptFunction("downloadsList", results); } void ActiveDownloadsHandler::OnDownloadUpdated(DownloadItem* item) { DownloadList::iterator it = find(downloads_.begin(), downloads_.end(), item); if (it == downloads_.end()) { NOTREACHED() << "Updated item " << item->full_path().value() << " not found"; } if (item->state() == DownloadItem::REMOVING) { item->RemoveObserver(this); downloads_.erase(it); DownloadList::iterator ita = find(active_downloads_.begin(), active_downloads_.end(), item); if (ita != active_downloads_.end()) active_downloads_.erase(ita); SendDownloads(); } else { const size_t id = it - downloads_.begin(); scoped_ptr<DictionaryValue> result( download_util::CreateDownloadItemValue(item, id)); web_ui_->CallJavascriptFunction("downloadUpdated", *result); } } } // namespace //////////////////////////////////////////////////////////////////////////////// // // ActiveDownloadsUI // //////////////////////////////////////////////////////////////////////////////// ActiveDownloadsUI::ActiveDownloadsUI(TabContents* contents) : HtmlDialogUI(contents) { ActiveDownloadsHandler* handler = new ActiveDownloadsHandler(); AddMessageHandler(handler->Attach(this)); handler->Init(); ActiveDownloadsUIHTMLSource* html_source = new ActiveDownloadsUIHTMLSource(); // Set up the chrome://active-downloads/ source. contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source); } // static Browser* ActiveDownloadsUI::OpenPopup(Profile* profile) { Browser* browser = GetPopup(profile); // Create new browser if no matching pop up is found. if (browser == NULL) { browser = Browser::CreateForType(Browser::TYPE_APP_PANEL, profile); browser::NavigateParams params( browser, GURL(chrome::kChromeUIActiveDownloadsURL), PageTransition::LINK); params.disposition = NEW_FOREGROUND_TAB; browser::Navigate(¶ms); // TODO(beng): The following two calls should be automatic by Navigate(). params.browser->window()->SetBounds(gfx::Rect(kPopupLeft, kPopupTop, kPopupWidth, kPopupHeight)); params.browser->window()->Show(); } else { browser->window()->Show(); } return browser; } Browser* ActiveDownloadsUI::GetPopup(Profile* profile) { for (BrowserList::const_iterator it = BrowserList::begin(); it != BrowserList::end(); ++it) { if (((*it)->type() == Browser::TYPE_APP_PANEL)) { TabContents* tab_contents = (*it)->GetSelectedTabContents(); DCHECK(tab_contents); if (!tab_contents) continue; const GURL& url = tab_contents->GetURL(); if (url.SchemeIs(chrome::kChromeUIScheme) && url.host() == chrome::kChromeUIActiveDownloadsHost && (*it)->profile() == profile) { return (*it); } } } return NULL; }