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/download/download_shelf_context_menu.h"
6
7#include "base/command_line.h"
8#include "chrome/browser/browser_process.h"
9#include "chrome/browser/download/download_crx_util.h"
10#include "chrome/browser/download/download_item_model.h"
11#include "chrome/browser/download/download_prefs.h"
12#include "chrome/browser/download/download_target_determiner.h"
13#include "chrome/browser/safe_browsing/download_protection_service.h"
14#include "chrome/browser/safe_browsing/safe_browsing_service.h"
15#include "chrome/common/url_constants.h"
16#include "chrome/grit/generated_resources.h"
17#include "content/public/browser/download_item.h"
18#include "content/public/browser/download_manager.h"
19#include "content/public/browser/page_navigator.h"
20#include "content/public/common/content_switches.h"
21#include "extensions/common/extension.h"
22#include "ui/base/l10n/l10n_util.h"
23
24#if defined(OS_WIN)
25#include "chrome/browser/ui/pdf/adobe_reader_info_win.h"
26#endif
27
28using content::DownloadItem;
29
30namespace {
31
32// Returns true if downloads resumption is enabled.
33bool IsDownloadResumptionEnabled() {
34  return base::CommandLine::ForCurrentProcess()->HasSwitch(
35      switches::kEnableDownloadResumption);
36}
37
38}  // namespace
39
40DownloadShelfContextMenu::~DownloadShelfContextMenu() {
41  DetachFromDownloadItem();
42}
43
44DownloadShelfContextMenu::DownloadShelfContextMenu(
45    DownloadItem* download_item,
46    content::PageNavigator* navigator)
47    : download_item_(download_item),
48      navigator_(navigator) {
49  DCHECK(download_item_);
50  download_item_->AddObserver(this);
51
52#if defined(OS_WIN)
53  is_pdf_reader_up_to_date_ = false;
54  if (IsDownloadPdf() && IsAdobeReaderDefaultPDFViewer()) {
55    is_pdf_reader_up_to_date_ =
56        DownloadTargetDeterminer::IsAdobeReaderUpToDate();
57  }
58#endif  // defined(OS_WIN)
59}
60
61ui::SimpleMenuModel* DownloadShelfContextMenu::GetMenuModel() {
62  ui::SimpleMenuModel* model = NULL;
63
64  if (!download_item_)
65    return NULL;
66
67  DownloadItemModel download_model(download_item_);
68  // We shouldn't be opening a context menu for a dangerous download, unless it
69  // is a malicious download.
70  DCHECK(!download_model.IsDangerous() || download_model.MightBeMalicious());
71
72  if (download_model.IsMalicious())
73    model = GetMaliciousMenuModel();
74  else if (download_model.MightBeMalicious())
75    model = GetMaybeMaliciousMenuModel();
76  else if (download_item_->GetState() == DownloadItem::COMPLETE)
77    model = GetFinishedMenuModel();
78  else if (download_item_->GetState() == DownloadItem::INTERRUPTED)
79    model = GetInterruptedMenuModel();
80  else
81    model = GetInProgressMenuModel();
82  return model;
83}
84
85bool DownloadShelfContextMenu::IsCommandIdEnabled(int command_id) const {
86  if (!download_item_)
87    return false;
88
89  switch (static_cast<ContextMenuCommands>(command_id)) {
90    case SHOW_IN_FOLDER:
91      return download_item_->CanShowInFolder();
92    case OPEN_WHEN_COMPLETE:
93    case PLATFORM_OPEN:
94      return download_item_->CanOpenDownload() &&
95          !download_crx_util::IsExtensionDownload(*download_item_);
96    case ALWAYS_OPEN_TYPE:
97      // For temporary downloads, the target filename might be a temporary
98      // filename. Don't base an "Always open" decision based on it. Also
99      // exclude extensions.
100      return download_item_->CanOpenDownload() &&
101          !download_crx_util::IsExtensionDownload(*download_item_);
102    case CANCEL:
103      return !download_item_->IsDone();
104    case TOGGLE_PAUSE:
105      return !download_item_->IsDone();
106    case DISCARD:
107    case KEEP:
108    case LEARN_MORE_SCANNING:
109    case LEARN_MORE_INTERRUPTED:
110      return true;
111  }
112  NOTREACHED();
113  return false;
114}
115
116bool DownloadShelfContextMenu::IsCommandIdChecked(int command_id) const {
117  if (!download_item_)
118    return false;
119
120  switch (command_id) {
121    case OPEN_WHEN_COMPLETE:
122      return download_item_->GetOpenWhenComplete() ||
123          download_crx_util::IsExtensionDownload(*download_item_);
124    case ALWAYS_OPEN_TYPE:
125#if defined(OS_WIN)
126      if (CanOpenPdfInReader()) {
127        DownloadPrefs* prefs = DownloadPrefs::FromBrowserContext(
128            download_item_->GetBrowserContext());
129        return prefs->ShouldOpenPdfInAdobeReader();
130      }
131#endif
132      return download_item_->ShouldOpenFileBasedOnExtension();
133    case TOGGLE_PAUSE:
134      return download_item_->IsPaused();
135  }
136  return false;
137}
138
139bool DownloadShelfContextMenu::IsCommandIdVisible(int command_id) const {
140  if (!download_item_)
141    return false;
142
143  if (command_id == PLATFORM_OPEN)
144    return (DownloadItemModel(download_item_).ShouldPreferOpeningInBrowser());
145
146  return true;
147}
148
149void DownloadShelfContextMenu::ExecuteCommand(int command_id, int event_flags) {
150  if (!download_item_)
151    return;
152
153  switch (static_cast<ContextMenuCommands>(command_id)) {
154    case SHOW_IN_FOLDER:
155      download_item_->ShowDownloadInShell();
156      break;
157    case OPEN_WHEN_COMPLETE:
158      download_item_->OpenDownload();
159      break;
160    case ALWAYS_OPEN_TYPE: {
161      bool is_checked = IsCommandIdChecked(ALWAYS_OPEN_TYPE);
162      DownloadPrefs* prefs = DownloadPrefs::FromBrowserContext(
163          download_item_->GetBrowserContext());
164#if defined(OS_WIN)
165      if (CanOpenPdfInReader()) {
166        prefs->SetShouldOpenPdfInAdobeReader(!is_checked);
167        DownloadItemModel(download_item_).SetShouldPreferOpeningInBrowser(
168            is_checked);
169        break;
170      }
171#endif
172      base::FilePath path = download_item_->GetTargetFilePath();
173      if (is_checked)
174        prefs->DisableAutoOpenBasedOnExtension(path);
175      else
176        prefs->EnableAutoOpenBasedOnExtension(path);
177      break;
178    }
179    case PLATFORM_OPEN:
180      DownloadItemModel(download_item_).OpenUsingPlatformHandler();
181      break;
182    case CANCEL:
183      download_item_->Cancel(true /* Cancelled by user */);
184      break;
185    case TOGGLE_PAUSE:
186      if (download_item_->GetState() == DownloadItem::IN_PROGRESS &&
187          !download_item_->IsPaused()) {
188        download_item_->Pause();
189      } else {
190        download_item_->Resume();
191      }
192      break;
193    case DISCARD:
194      download_item_->Remove();
195      break;
196    case KEEP:
197      download_item_->ValidateDangerousDownload();
198      break;
199    case LEARN_MORE_SCANNING: {
200#if defined(FULL_SAFE_BROWSING)
201      using safe_browsing::DownloadProtectionService;
202      SafeBrowsingService* sb_service =
203          g_browser_process->safe_browsing_service();
204      DownloadProtectionService* protection_service =
205          (sb_service ? sb_service->download_protection_service() : NULL);
206      if (protection_service) {
207        protection_service->ShowDetailsForDownload(*download_item_, navigator_);
208      }
209#else
210      // Should only be getting invoked if we are using safe browsing.
211      NOTREACHED();
212#endif
213      break;
214    }
215    case LEARN_MORE_INTERRUPTED:
216      navigator_->OpenURL(
217          content::OpenURLParams(GURL(chrome::kDownloadInterruptedLearnMoreURL),
218                                 content::Referrer(),
219                                 NEW_FOREGROUND_TAB,
220                                 ui::PAGE_TRANSITION_LINK,
221                                 false));
222      break;
223  }
224}
225
226bool DownloadShelfContextMenu::GetAcceleratorForCommandId(
227    int command_id, ui::Accelerator* accelerator) {
228  return false;
229}
230
231bool DownloadShelfContextMenu::IsItemForCommandIdDynamic(int command_id) const {
232  return command_id == TOGGLE_PAUSE;
233}
234
235base::string16 DownloadShelfContextMenu::GetLabelForCommandId(
236    int command_id) const {
237  switch (static_cast<ContextMenuCommands>(command_id)) {
238    case SHOW_IN_FOLDER:
239      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_SHOW);
240    case OPEN_WHEN_COMPLETE:
241      if (download_item_ && !download_item_->IsDone())
242        return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_OPEN_WHEN_COMPLETE);
243      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_OPEN);
244    case ALWAYS_OPEN_TYPE:
245      return l10n_util::GetStringUTF16(GetAlwaysOpenStringId());
246    case PLATFORM_OPEN:
247      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_PLATFORM_OPEN);
248    case CANCEL:
249      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_CANCEL);
250    case TOGGLE_PAUSE:
251      if (download_item_ &&
252          download_item_->GetState() == DownloadItem::IN_PROGRESS &&
253          !download_item_->IsPaused())
254        return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_PAUSE_ITEM);
255      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_RESUME_ITEM);
256    case DISCARD:
257      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_DISCARD);
258    case KEEP:
259      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_KEEP);
260    case LEARN_MORE_SCANNING:
261      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_LEARN_MORE_SCANNING);
262    case LEARN_MORE_INTERRUPTED:
263      return l10n_util::GetStringUTF16(
264          IDS_DOWNLOAD_MENU_LEARN_MORE_INTERRUPTED);
265  }
266  NOTREACHED();
267  return base::string16();
268}
269
270void DownloadShelfContextMenu::DetachFromDownloadItem() {
271  if (!download_item_)
272    return;
273
274  download_item_->RemoveObserver(this);
275  download_item_ = NULL;
276}
277
278void DownloadShelfContextMenu::OnDownloadDestroyed(DownloadItem* download) {
279  DCHECK(download_item_ == download);
280  DetachFromDownloadItem();
281}
282
283ui::SimpleMenuModel* DownloadShelfContextMenu::GetInProgressMenuModel() {
284  if (in_progress_download_menu_model_)
285    return in_progress_download_menu_model_.get();
286
287  in_progress_download_menu_model_.reset(new ui::SimpleMenuModel(this));
288
289  in_progress_download_menu_model_->AddCheckItemWithStringId(
290      OPEN_WHEN_COMPLETE, IDS_DOWNLOAD_MENU_OPEN_WHEN_COMPLETE);
291  in_progress_download_menu_model_->AddCheckItemWithStringId(
292      ALWAYS_OPEN_TYPE, GetAlwaysOpenStringId());
293  in_progress_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
294  in_progress_download_menu_model_->AddItemWithStringId(
295      TOGGLE_PAUSE, IDS_DOWNLOAD_MENU_PAUSE_ITEM);
296  in_progress_download_menu_model_->AddItemWithStringId(
297      SHOW_IN_FOLDER, IDS_DOWNLOAD_MENU_SHOW);
298  in_progress_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
299  in_progress_download_menu_model_->AddItemWithStringId(
300      CANCEL, IDS_DOWNLOAD_MENU_CANCEL);
301
302  return in_progress_download_menu_model_.get();
303}
304
305ui::SimpleMenuModel* DownloadShelfContextMenu::GetFinishedMenuModel() {
306  if (finished_download_menu_model_)
307    return finished_download_menu_model_.get();
308
309  finished_download_menu_model_.reset(new ui::SimpleMenuModel(this));
310
311  finished_download_menu_model_->AddItemWithStringId(
312      OPEN_WHEN_COMPLETE, IDS_DOWNLOAD_MENU_OPEN);
313  finished_download_menu_model_->AddCheckItemWithStringId(
314      ALWAYS_OPEN_TYPE, GetAlwaysOpenStringId());
315  finished_download_menu_model_->AddItemWithStringId(
316      PLATFORM_OPEN, IDS_DOWNLOAD_MENU_PLATFORM_OPEN);
317  finished_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
318  finished_download_menu_model_->AddItemWithStringId(
319      SHOW_IN_FOLDER, IDS_DOWNLOAD_MENU_SHOW);
320  finished_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
321  finished_download_menu_model_->AddItemWithStringId(
322      CANCEL, IDS_DOWNLOAD_MENU_CANCEL);
323
324  return finished_download_menu_model_.get();
325}
326
327ui::SimpleMenuModel* DownloadShelfContextMenu::GetInterruptedMenuModel() {
328#if !defined(OS_WIN)
329  // If resumption isn't enabled and we aren't on Windows, then none of the
330  // options here are applicable.
331  if (!IsDownloadResumptionEnabled())
332    return GetInProgressMenuModel();
333#endif
334
335  if (interrupted_download_menu_model_)
336    return interrupted_download_menu_model_.get();
337
338  interrupted_download_menu_model_.reset(new ui::SimpleMenuModel(this));
339
340  if (IsDownloadResumptionEnabled()) {
341    interrupted_download_menu_model_->AddItemWithStringId(
342        TOGGLE_PAUSE, IDS_DOWNLOAD_MENU_RESUME_ITEM);
343  }
344#if defined(OS_WIN)
345  // The Help Center article is currently Windows specific.
346  // TODO(asanka): Enable this for other platforms when the article is expanded
347  // for other platforms.
348  interrupted_download_menu_model_->AddItemWithStringId(
349      LEARN_MORE_INTERRUPTED, IDS_DOWNLOAD_MENU_LEARN_MORE_INTERRUPTED);
350#endif
351  if (IsDownloadResumptionEnabled()) {
352    interrupted_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
353    interrupted_download_menu_model_->AddItemWithStringId(
354        CANCEL, IDS_DOWNLOAD_MENU_CANCEL);
355  }
356
357  return interrupted_download_menu_model_.get();
358}
359
360ui::SimpleMenuModel* DownloadShelfContextMenu::GetMaybeMaliciousMenuModel() {
361  if (maybe_malicious_download_menu_model_)
362    return maybe_malicious_download_menu_model_.get();
363
364  maybe_malicious_download_menu_model_.reset(new ui::SimpleMenuModel(this));
365
366  maybe_malicious_download_menu_model_->AddItemWithStringId(
367      KEEP, IDS_DOWNLOAD_MENU_KEEP);
368  maybe_malicious_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
369  maybe_malicious_download_menu_model_->AddItemWithStringId(
370      LEARN_MORE_SCANNING, IDS_DOWNLOAD_MENU_LEARN_MORE_SCANNING);
371  return maybe_malicious_download_menu_model_.get();
372}
373
374ui::SimpleMenuModel* DownloadShelfContextMenu::GetMaliciousMenuModel() {
375  if (malicious_download_menu_model_)
376    return malicious_download_menu_model_.get();
377
378  malicious_download_menu_model_.reset(new ui::SimpleMenuModel(this));
379
380  DownloadItemModel download_model(download_item_);
381  malicious_download_menu_model_->AddItemWithStringId(
382      LEARN_MORE_SCANNING, IDS_DOWNLOAD_MENU_LEARN_MORE_SCANNING);
383
384  return malicious_download_menu_model_.get();
385}
386
387int DownloadShelfContextMenu::GetAlwaysOpenStringId() const {
388#if defined(OS_WIN)
389  if (CanOpenPdfInReader())
390    return IDS_DOWNLOAD_MENU_ALWAYS_OPEN_PDF_IN_READER;
391#endif
392  return IDS_DOWNLOAD_MENU_ALWAYS_OPEN_TYPE;
393}
394
395#if defined(OS_WIN)
396bool DownloadShelfContextMenu::IsDownloadPdf() const {
397  base::FilePath path = download_item_->GetTargetFilePath();
398  return path.MatchesExtension(FILE_PATH_LITERAL(".pdf"));
399}
400
401bool DownloadShelfContextMenu::CanOpenPdfInReader() const {
402  return (is_pdf_reader_up_to_date_ && IsDownloadPdf());
403}
404#endif
405