1// Copyright (c) 2011 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/active_downloads_ui.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/callback.h"
12#include "base/command_line.h"
13#include "base/file_util.h"
14#include "base/logging.h"
15#include "base/memory/singleton.h"
16#include "base/message_loop.h"
17#include "base/path_service.h"
18#include "base/string_piece.h"
19#include "base/string_util.h"
20#include "base/threading/thread.h"
21#include "base/time.h"
22#include "base/utf_string_conversions.h"
23#include "base/values.h"
24#include "chrome/browser/chromeos/cros/cros_library.h"
25#include "chrome/browser/chromeos/login/user_manager.h"
26#include "chrome/browser/download/download_item.h"
27#include "chrome/browser/download/download_manager.h"
28#include "chrome/browser/download/download_util.h"
29#include "chrome/browser/profiles/profile.h"
30#include "chrome/browser/tabs/tab_strip_model.h"
31#include "chrome/browser/ui/browser.h"
32#include "chrome/browser/ui/browser_list.h"
33#include "chrome/browser/ui/browser_window.h"
34#include "chrome/browser/ui/webui/favicon_source.h"
35#include "chrome/browser/ui/webui/mediaplayer_ui.h"
36#include "chrome/common/chrome_paths.h"
37#include "chrome/common/chrome_switches.h"
38#include "chrome/common/jstemplate_builder.h"
39#include "chrome/common/net/url_fetcher.h"
40#include "chrome/common/url_constants.h"
41#include "content/browser/browser_thread.h"
42#include "content/browser/tab_contents/tab_contents.h"
43#include "grit/browser_resources.h"
44#include "grit/chromium_strings.h"
45#include "grit/generated_resources.h"
46#include "grit/locale_settings.h"
47#include "net/base/escape.h"
48#include "net/url_request/url_request_file_job.h"
49#include "ui/base/l10n/l10n_util.h"
50#include "ui/base/resource/resource_bundle.h"
51
52namespace {
53
54static const int kPopupLeft = 0;
55static const int kPopupTop = 0;
56static const int kPopupWidth = 250;
57// Minimum height of window must be 100, so kPopupHeight has space for
58// 2 download rows of 36 px and 'Show All Downloads' which is 29px.
59static const int kPopupHeight = 36 * 2 + 29;
60
61static const char kPropertyPath[] = "path";
62static const char kPropertyTitle[] = "title";
63static const char kPropertyDirectory[] = "isDirectory";
64
65class ActiveDownloadsUIHTMLSource : public ChromeURLDataManager::DataSource {
66 public:
67  ActiveDownloadsUIHTMLSource();
68
69  // Called when the network layer has requested a resource underneath
70  // the path we registered.
71  virtual void StartDataRequest(const std::string& path,
72                                bool is_incognito,
73                                int request_id);
74  virtual std::string GetMimeType(const std::string&) const {
75    return "text/html";
76  }
77
78 private:
79  ~ActiveDownloadsUIHTMLSource() {}
80
81  DISALLOW_COPY_AND_ASSIGN(ActiveDownloadsUIHTMLSource);
82};
83
84// The handler for Javascript messages related to the "active_downloads" view.
85class ActiveDownloadsHandler
86    : public WebUIMessageHandler,
87      public DownloadManager::Observer,
88      public DownloadItem::Observer {
89 public:
90  ActiveDownloadsHandler();
91  virtual ~ActiveDownloadsHandler();
92
93  // Initialization after Attach.
94  void Init();
95
96  // WebUIMessageHandler implementation.
97  virtual WebUIMessageHandler* Attach(WebUI* web_ui);
98  virtual void RegisterMessages();
99
100  // DownloadItem::Observer interface.
101  virtual void OnDownloadUpdated(DownloadItem* item);
102  virtual void OnDownloadOpened(DownloadItem* item) { }
103
104  // DownloadManager::Observer interface.
105  virtual void ModelChanged();
106
107  // WebUI Callbacks.
108  void HandleGetDownloads(const ListValue* args);
109  void HandlePauseToggleDownload(const ListValue* args);
110  void HandleCancelDownload(const ListValue* args);
111  void HandleAllowDownload(const ListValue* args);
112  void OpenNewPopupWindow(const ListValue* args);
113  void OpenNewFullWindow(const ListValue* args);
114  void PlayMediaFile(const ListValue* args);
115
116 private:
117  // Downloads helpers.
118  DownloadItem* GetDownloadById(const ListValue* args);
119  void UpdateDownloadList();
120  void SendDownloads();
121  void AddDownload(DownloadItem* item);
122
123  void OpenNewWindow(const ListValue* args, bool popup);
124
125  Profile* profile_;
126  TabContents* tab_contents_;
127  DownloadManager* download_manager_;
128
129  typedef std::vector<DownloadItem*> DownloadList;
130  DownloadList active_downloads_;
131  DownloadList downloads_;
132
133  DISALLOW_COPY_AND_ASSIGN(ActiveDownloadsHandler);
134};
135
136////////////////////////////////////////////////////////////////////////////////
137//
138// ActiveDownloadsUIHTMLSource
139//
140////////////////////////////////////////////////////////////////////////////////
141
142ActiveDownloadsUIHTMLSource::ActiveDownloadsUIHTMLSource()
143    : DataSource(chrome::kChromeUIActiveDownloadsHost, MessageLoop::current()) {
144}
145
146void ActiveDownloadsUIHTMLSource::StartDataRequest(const std::string& path,
147                                              bool is_incognito,
148                                              int request_id) {
149  DictionaryValue localized_strings;
150  localized_strings.SetString("allowdownload",
151      l10n_util::GetStringUTF16(IDS_FILEBROWSER_CONFIRM_DOWNLOAD));
152  localized_strings.SetString("cancel",
153      l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_CANCEL));
154  localized_strings.SetString("confirmcancel",
155      l10n_util::GetStringUTF16(IDS_FILEBROWSER_CONFIRM_CANCEL));
156  localized_strings.SetString("confirmyes",
157      l10n_util::GetStringUTF16(IDS_FILEBROWSER_CONFIRM_YES));
158  localized_strings.SetString("open",
159      l10n_util::GetStringUTF16(IDS_FILEBROWSER_OPEN));
160  localized_strings.SetString("pause",
161      l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_PAUSE));
162  localized_strings.SetString("resume",
163      l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_RESUME));
164  localized_strings.SetString("showalldownloads",
165      l10n_util::GetStringUTF16(IDS_FILEBROWSER_SHOW_ALL_DOWNLOADS));
166  FilePath default_download_path;
167  if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS,
168                        &default_download_path)) {
169    NOTREACHED();
170  }
171  // TODO(viettrungluu): this is wrong -- FilePath's need not be Unicode.
172  localized_strings.SetString("downloadpath", default_download_path.value());
173  localized_strings.SetString("error_unknown_file_type",
174      l10n_util::GetStringUTF16(IDS_FILEBROWSER_ERROR_UNKNOWN_FILE_TYPE));
175  SetFontAndTextDirection(&localized_strings);
176
177  static const base::StringPiece active_downloads_html(
178      ResourceBundle::GetSharedInstance().GetRawDataResource(
179          IDR_ACTIVE_DOWNLOADS_HTML));
180  const std::string full_html = jstemplate_builder::GetI18nTemplateHtml(
181      active_downloads_html, &localized_strings);
182
183  scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes);
184  html_bytes->data.resize(full_html.size());
185  std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin());
186
187  SendResponse(request_id, html_bytes);
188}
189
190////////////////////////////////////////////////////////////////////////////////
191//
192// ActiveDownloadsHandler
193//
194////////////////////////////////////////////////////////////////////////////////
195
196ActiveDownloadsHandler::ActiveDownloadsHandler()
197    : profile_(NULL),
198      tab_contents_(NULL),
199      download_manager_(NULL) {
200}
201
202ActiveDownloadsHandler::~ActiveDownloadsHandler() {
203  for (size_t i = 0; i < downloads_.size(); ++i) {
204    downloads_[i]->RemoveObserver(this);
205  }
206  download_manager_->RemoveObserver(this);
207}
208
209WebUIMessageHandler* ActiveDownloadsHandler::Attach(WebUI* web_ui) {
210  // Create our favicon data source.
211  profile_ = web_ui->GetProfile();
212  profile_->GetChromeURLDataManager()->AddDataSource(
213      new FaviconSource(profile_));
214  tab_contents_ = web_ui->tab_contents();
215  return WebUIMessageHandler::Attach(web_ui);
216}
217
218void ActiveDownloadsHandler::Init() {
219  download_manager_ = profile_->GetDownloadManager();
220  download_manager_->AddObserver(this);
221}
222
223void ActiveDownloadsHandler::RegisterMessages() {
224  web_ui_->RegisterMessageCallback("getDownloads",
225      NewCallback(this, &ActiveDownloadsHandler::HandleGetDownloads));
226  web_ui_->RegisterMessageCallback("pauseToggleDownload",
227      NewCallback(this, &ActiveDownloadsHandler::HandlePauseToggleDownload));
228  web_ui_->RegisterMessageCallback("cancelDownload",
229      NewCallback(this, &ActiveDownloadsHandler::HandleCancelDownload));
230  web_ui_->RegisterMessageCallback("allowDownload",
231      NewCallback(this, &ActiveDownloadsHandler::HandleAllowDownload));
232  web_ui_->RegisterMessageCallback("openNewPopupWindow",
233      NewCallback(this, &ActiveDownloadsHandler::OpenNewPopupWindow));
234  web_ui_->RegisterMessageCallback("openNewFullWindow",
235      NewCallback(this, &ActiveDownloadsHandler::OpenNewFullWindow));
236  web_ui_->RegisterMessageCallback("playMediaFile",
237      NewCallback(this, &ActiveDownloadsHandler::PlayMediaFile));
238}
239
240void ActiveDownloadsHandler::PlayMediaFile(const ListValue* args) {
241  FilePath file_path(UTF16ToUTF8(ExtractStringValue(args)));
242
243  Browser* browser = Browser::GetBrowserForController(
244      &tab_contents_->controller(), NULL);
245  MediaPlayer* mediaplayer = MediaPlayer::GetInstance();
246  mediaplayer->ForcePlayMediaFile(profile_, file_path, browser);
247}
248
249DownloadItem* ActiveDownloadsHandler::GetDownloadById(
250    const ListValue* args) {
251  int i;
252  if (!ExtractIntegerValue(args, &i))
253    return NULL;
254  size_t id(i);
255  return id < downloads_.size() ? downloads_[id] : NULL;
256}
257
258void ActiveDownloadsHandler::HandlePauseToggleDownload(const ListValue* args) {
259  DownloadItem* item = GetDownloadById(args);
260  if (item && item->IsPartialDownload())
261    item->TogglePause();
262}
263
264void ActiveDownloadsHandler::HandleAllowDownload(const ListValue* args) {
265  DownloadItem* item = GetDownloadById(args);
266  if (item)
267    item->DangerousDownloadValidated();
268}
269
270void ActiveDownloadsHandler::HandleCancelDownload(const ListValue* args) {
271  DownloadItem* item = GetDownloadById(args);
272  if (item) {
273    if (item->IsPartialDownload())
274      item->Cancel(true);
275    item->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD);
276  }
277}
278
279void ActiveDownloadsHandler::OpenNewFullWindow(const ListValue* args) {
280  OpenNewWindow(args, false);
281}
282
283void ActiveDownloadsHandler::OpenNewPopupWindow(const ListValue* args) {
284  OpenNewWindow(args, true);
285}
286
287void ActiveDownloadsHandler::OpenNewWindow(const ListValue* args, bool popup) {
288  std::string url = UTF16ToUTF8(ExtractStringValue(args));
289  Browser* browser = popup ?
290      Browser::CreateForType(Browser::TYPE_APP_PANEL, profile_) :
291      BrowserList::GetLastActive();
292  browser::NavigateParams params(browser, GURL(url), PageTransition::LINK);
293  params.disposition = NEW_FOREGROUND_TAB;
294  browser::Navigate(&params);
295  // TODO(beng): The following two calls should be automatic by Navigate().
296  if (popup)
297    params.browser->window()->SetBounds(gfx::Rect(0, 0, 400, 300));
298  params.browser->window()->Show();
299}
300
301void ActiveDownloadsHandler::ModelChanged() {
302  UpdateDownloadList();
303}
304
305void ActiveDownloadsHandler::HandleGetDownloads(const ListValue* args) {
306  UpdateDownloadList();
307}
308
309void ActiveDownloadsHandler::UpdateDownloadList() {
310  DownloadList downloads;
311  download_manager_->GetAllDownloads(FilePath(), &downloads);
312  active_downloads_.clear();
313  for (size_t i = 0; i < downloads.size(); ++i) {
314    AddDownload(downloads[i]);
315  }
316  SendDownloads();
317}
318
319void ActiveDownloadsHandler::AddDownload(DownloadItem* item) {
320  // Observe in progress and dangerous downloads.
321  if (item->state() == DownloadItem::IN_PROGRESS ||
322      item->safety_state() == DownloadItem::DANGEROUS) {
323    active_downloads_.push_back(item);
324
325    DownloadList::const_iterator it =
326      std::find(downloads_.begin(), downloads_.end(), item);
327    if (it == downloads_.end()) {
328      downloads_.push_back(item);
329      item->AddObserver(this);
330    }
331  }
332}
333
334void ActiveDownloadsHandler::SendDownloads() {
335  ListValue results;
336  for (size_t i = 0; i < downloads_.size(); ++i) {
337    results.Append(download_util::CreateDownloadItemValue(downloads_[i], i));
338  }
339
340  web_ui_->CallJavascriptFunction("downloadsList", results);
341}
342
343void ActiveDownloadsHandler::OnDownloadUpdated(DownloadItem* item) {
344  DownloadList::iterator it =
345      find(downloads_.begin(), downloads_.end(), item);
346
347  if (it == downloads_.end()) {
348    NOTREACHED() << "Updated item " << item->full_path().value()
349      << " not found";
350  }
351
352  if (item->state() == DownloadItem::REMOVING) {
353    item->RemoveObserver(this);
354    downloads_.erase(it);
355    DownloadList::iterator ita =
356        find(active_downloads_.begin(), active_downloads_.end(), item);
357    if (ita != active_downloads_.end())
358      active_downloads_.erase(ita);
359    SendDownloads();
360  } else {
361    const size_t id = it - downloads_.begin();
362    scoped_ptr<DictionaryValue> result(
363        download_util::CreateDownloadItemValue(item, id));
364    web_ui_->CallJavascriptFunction("downloadUpdated", *result);
365  }
366}
367
368}  // namespace
369
370////////////////////////////////////////////////////////////////////////////////
371//
372// ActiveDownloadsUI
373//
374////////////////////////////////////////////////////////////////////////////////
375
376
377ActiveDownloadsUI::ActiveDownloadsUI(TabContents* contents)
378    : HtmlDialogUI(contents) {
379  ActiveDownloadsHandler* handler = new ActiveDownloadsHandler();
380  AddMessageHandler(handler->Attach(this));
381  handler->Init();
382  ActiveDownloadsUIHTMLSource* html_source = new ActiveDownloadsUIHTMLSource();
383
384  // Set up the chrome://active-downloads/ source.
385  contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
386}
387
388// static
389Browser* ActiveDownloadsUI::OpenPopup(Profile* profile) {
390  Browser* browser = GetPopup(profile);
391
392  // Create new browser if no matching pop up is found.
393  if (browser == NULL) {
394    browser = Browser::CreateForType(Browser::TYPE_APP_PANEL, profile);
395
396    browser::NavigateParams params(
397        browser,
398        GURL(chrome::kChromeUIActiveDownloadsURL),
399        PageTransition::LINK);
400    params.disposition = NEW_FOREGROUND_TAB;
401    browser::Navigate(&params);
402
403    // TODO(beng): The following two calls should be automatic by Navigate().
404    params.browser->window()->SetBounds(gfx::Rect(kPopupLeft,
405                                                  kPopupTop,
406                                                  kPopupWidth,
407                                                  kPopupHeight));
408    params.browser->window()->Show();
409  } else {
410    browser->window()->Show();
411  }
412
413  return browser;
414}
415
416Browser* ActiveDownloadsUI::GetPopup(Profile* profile) {
417  for (BrowserList::const_iterator it = BrowserList::begin();
418       it != BrowserList::end();
419       ++it) {
420    if (((*it)->type() == Browser::TYPE_APP_PANEL)) {
421      TabContents* tab_contents = (*it)->GetSelectedTabContents();
422      DCHECK(tab_contents);
423      if (!tab_contents)
424        continue;
425      const GURL& url = tab_contents->GetURL();
426
427      if (url.SchemeIs(chrome::kChromeUIScheme) &&
428          url.host() == chrome::kChromeUIActiveDownloadsHost &&
429          (*it)->profile() == profile) {
430        return (*it);
431      }
432    }
433  }
434  return NULL;
435}
436
437