1// Copyright 2013 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 "base/compiler_specific.h"
6#include "chrome/browser/download/download_danger_prompt.h"
7#include "chrome/browser/download/download_stats.h"
8#include "chrome/browser/ui/views/constrained_window_views.h"
9#include "components/web_modal/web_contents_modal_dialog_host.h"
10#include "components/web_modal/web_contents_modal_dialog_manager.h"
11#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/download_danger_type.h"
14#include "content/public/browser/download_item.h"
15#include "content/public/browser/web_contents.h"
16#include "grit/chromium_strings.h"
17#include "grit/generated_resources.h"
18#include "ui/base/l10n/l10n_util.h"
19#include "ui/views/controls/button/label_button.h"
20#include "ui/views/controls/label.h"
21#include "ui/views/layout/grid_layout.h"
22#include "ui/views/view.h"
23#include "ui/views/widget/widget.h"
24#include "ui/views/window/dialog_client_view.h"
25#include "ui/views/window/dialog_delegate.h"
26
27using content::BrowserThread;
28using web_modal::WebContentsModalDialogManager;
29using web_modal::WebContentsModalDialogManagerDelegate;
30
31namespace {
32
33const int kMessageWidth = 320;
34const int kParagraphPadding = 15;
35
36// Views-specific implementation of download danger prompt dialog. We use this
37// class rather than a TabModalConfirmDialog so that we can use custom
38// formatting on the text in the body of the dialog.
39class DownloadDangerPromptViews : public DownloadDangerPrompt,
40                                  public content::DownloadItem::Observer,
41                                  public views::DialogDelegate {
42 public:
43  DownloadDangerPromptViews(content::DownloadItem* item,
44                            content::WebContents* web_contents,
45                            bool show_context,
46                            const OnDone& done);
47
48  // DownloadDangerPrompt methods:
49  virtual void InvokeActionForTesting(Action action) OVERRIDE;
50
51  // views::DialogDelegate methods:
52  virtual base::string16 GetDialogButtonLabel(
53      ui::DialogButton button) const OVERRIDE;
54  virtual base::string16 GetWindowTitle() const OVERRIDE;
55  virtual void DeleteDelegate() OVERRIDE;
56  virtual ui::ModalType GetModalType() const OVERRIDE;
57  virtual bool Cancel() OVERRIDE;
58  virtual bool Accept() OVERRIDE;
59  virtual bool Close() OVERRIDE;
60  // TODO(wittman): Remove this override once we move to the new style frame
61  // view on all dialogs.
62  virtual views::NonClientFrameView* CreateNonClientFrameView(
63      views::Widget* widget) OVERRIDE;
64  virtual views::View* GetInitiallyFocusedView() OVERRIDE;
65  virtual views::View* GetContentsView() OVERRIDE;
66  virtual views::Widget* GetWidget() OVERRIDE;
67  virtual const views::Widget* GetWidget() const OVERRIDE;
68
69  // content::DownloadItem::Observer:
70  virtual void OnDownloadUpdated(content::DownloadItem* download) OVERRIDE;
71
72 private:
73  base::string16 GetAcceptButtonTitle() const;
74  base::string16 GetCancelButtonTitle() const;
75  // The message lead is separated from the main text and is bolded.
76  base::string16 GetMessageLead() const;
77  base::string16 GetMessageBody() const;
78  void RunDone(Action action);
79
80  content::DownloadItem* download_;
81  content::WebContents* web_contents_;
82  bool show_context_;
83  OnDone done_;
84
85  views::View* contents_view_;
86};
87
88DownloadDangerPromptViews::DownloadDangerPromptViews(
89    content::DownloadItem* item,
90    content::WebContents* web_contents,
91    bool show_context,
92    const OnDone& done)
93    : download_(item),
94      web_contents_(web_contents),
95      show_context_(show_context),
96      done_(done),
97      contents_view_(NULL) {
98  DCHECK(!done_.is_null());
99  download_->AddObserver(this);
100
101  contents_view_ = new views::View;
102
103  views::GridLayout* layout = views::GridLayout::CreatePanel(contents_view_);
104  contents_view_->SetLayoutManager(layout);
105
106  views::ColumnSet* column_set = layout->AddColumnSet(0);
107  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
108                        views::GridLayout::FIXED, kMessageWidth, 0);
109
110  const base::string16 message_lead = GetMessageLead();
111
112  if (!message_lead.empty()) {
113    views::Label* message_lead_label = new views::Label(message_lead);
114    message_lead_label->SetMultiLine(true);
115    message_lead_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
116    message_lead_label->SetAllowCharacterBreak(true);
117
118    gfx::FontList font_list(gfx::Font().DeriveFont(0, gfx::Font::BOLD));
119    message_lead_label->SetFontList(font_list);
120
121    layout->StartRow(0, 0);
122    layout->AddView(message_lead_label);
123
124    layout->AddPaddingRow(0, kParagraphPadding);
125  }
126
127  views::Label* message_body_label = new views::Label(GetMessageBody());
128  message_body_label->SetMultiLine(true);
129  message_body_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
130  message_body_label->SetAllowCharacterBreak(true);
131
132  layout->StartRow(0, 0);
133  layout->AddView(message_body_label);
134
135  RecordOpenedDangerousConfirmDialog(download_->GetDangerType());
136}
137
138// DownloadDangerPrompt methods:
139void DownloadDangerPromptViews::InvokeActionForTesting(Action action) {
140  switch (action) {
141    case ACCEPT:
142      Accept();
143      break;
144
145    case CANCEL:
146    case DISMISS:
147      Cancel();
148      break;
149
150    default:
151      NOTREACHED();
152      break;
153  }
154}
155
156// views::DialogDelegate methods:
157base::string16 DownloadDangerPromptViews::GetDialogButtonLabel(
158    ui::DialogButton button) const {
159  switch (button) {
160    case ui::DIALOG_BUTTON_OK:
161      return GetAcceptButtonTitle();
162
163    case ui::DIALOG_BUTTON_CANCEL:
164      return GetCancelButtonTitle();
165
166    default:
167      return DialogDelegate::GetDialogButtonLabel(button);
168  };
169}
170
171base::string16 DownloadDangerPromptViews::GetWindowTitle() const {
172  if (show_context_)
173    return l10n_util::GetStringUTF16(IDS_CONFIRM_KEEP_DANGEROUS_DOWNLOAD_TITLE);
174  else
175    return l10n_util::GetStringUTF16(IDS_RESTORE_KEEP_DANGEROUS_DOWNLOAD_TITLE);
176}
177
178void DownloadDangerPromptViews::DeleteDelegate() {
179  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
180
181  delete this;
182}
183
184ui::ModalType DownloadDangerPromptViews::GetModalType() const {
185#if defined(USE_ASH)
186  return ui::MODAL_TYPE_CHILD;
187#else
188  return views::WidgetDelegate::GetModalType();
189#endif
190}
191
192bool DownloadDangerPromptViews::Cancel() {
193  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194
195  RunDone(CANCEL);
196  return true;
197}
198
199bool DownloadDangerPromptViews::Accept() {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201
202  RunDone(ACCEPT);
203  return true;
204}
205
206bool DownloadDangerPromptViews::Close() {
207  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208
209  RunDone(DISMISS);
210  return true;
211}
212
213// TODO(wittman): Remove this override once we move to the new style frame
214// view on all dialogs.
215views::NonClientFrameView* DownloadDangerPromptViews::CreateNonClientFrameView(
216    views::Widget* widget) {
217  return CreateConstrainedStyleNonClientFrameView(
218      widget, web_contents_->GetBrowserContext());
219}
220
221views::View* DownloadDangerPromptViews::GetInitiallyFocusedView() {
222  return GetDialogClientView()->cancel_button();
223}
224
225views::View* DownloadDangerPromptViews::GetContentsView() {
226  return contents_view_;
227}
228
229views::Widget* DownloadDangerPromptViews::GetWidget() {
230  return contents_view_->GetWidget();
231}
232
233const views::Widget* DownloadDangerPromptViews::GetWidget() const {
234  return contents_view_->GetWidget();
235}
236
237// content::DownloadItem::Observer:
238void DownloadDangerPromptViews::OnDownloadUpdated(
239    content::DownloadItem* download) {
240  // If the download is nolonger dangerous (accepted externally) or the download
241  // is in a terminal state, then the download danger prompt is no longer
242  // necessary.
243  if (!download_->IsDangerous() || download_->IsDone()) {
244    RunDone(DISMISS);
245    Cancel();
246  }
247}
248
249base::string16 DownloadDangerPromptViews::GetAcceptButtonTitle() const {
250  if (show_context_)
251    return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD);
252  switch (download_->GetDangerType()) {
253    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
254    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
255    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
256      return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD_AGAIN_MALICIOUS);
257    }
258    default:
259      return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD_AGAIN);
260  }
261}
262
263base::string16 DownloadDangerPromptViews::GetCancelButtonTitle() const {
264  if (show_context_)
265    return l10n_util::GetStringUTF16(IDS_CANCEL);
266  switch (download_->GetDangerType()) {
267    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
268    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
269    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
270      return l10n_util::GetStringUTF16(IDS_CONFIRM_CANCEL_AGAIN_MALICIOUS);
271    }
272    default:
273      return l10n_util::GetStringUTF16(IDS_CANCEL);
274  }
275}
276
277base::string16 DownloadDangerPromptViews::GetMessageLead() const {
278  if (!show_context_) {
279    switch (download_->GetDangerType()) {
280      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
281      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
282      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
283        return l10n_util::GetStringUTF16(
284            IDS_PROMPT_CONFIRM_KEEP_MALICIOUS_DOWNLOAD_LEAD);
285
286      default:
287        break;
288    }
289  }
290
291  return base::string16();
292}
293
294base::string16 DownloadDangerPromptViews::GetMessageBody() const {
295  if (show_context_) {
296    switch (download_->GetDangerType()) {
297      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: {
298        return l10n_util::GetStringFUTF16(
299            IDS_PROMPT_DANGEROUS_DOWNLOAD,
300            download_->GetFileNameToReportUser().LossyDisplayName());
301      }
302      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: // Fall through
303      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
304      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
305        return l10n_util::GetStringFUTF16(
306            IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT,
307            download_->GetFileNameToReportUser().LossyDisplayName());
308      }
309      case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
310        return l10n_util::GetStringFUTF16(
311            IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
312            download_->GetFileNameToReportUser().LossyDisplayName());
313      }
314      case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
315        return l10n_util::GetStringFUTF16(
316            IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
317            download_->GetFileNameToReportUser().LossyDisplayName());
318      }
319      case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
320      case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
321      case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
322      case content::DOWNLOAD_DANGER_TYPE_MAX: {
323        break;
324      }
325    }
326  } else {
327    switch (download_->GetDangerType()) {
328      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
329      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
330      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
331        return l10n_util::GetStringUTF16(
332            IDS_PROMPT_CONFIRM_KEEP_MALICIOUS_DOWNLOAD_BODY);
333      }
334      default: {
335        return l10n_util::GetStringUTF16(
336            IDS_PROMPT_CONFIRM_KEEP_DANGEROUS_DOWNLOAD);
337      }
338    }
339  }
340  NOTREACHED();
341  return base::string16();
342}
343
344void DownloadDangerPromptViews::RunDone(Action action) {
345  // Invoking the callback can cause the download item state to change or cause
346  // the window to close, and |callback| refers to a member variable.
347  OnDone done = done_;
348  done_.Reset();
349  if (download_ != NULL) {
350    download_->RemoveObserver(this);
351    download_ = NULL;
352  }
353  if (!done.is_null())
354    done.Run(action);
355}
356
357}  // namespace
358
359DownloadDangerPrompt* DownloadDangerPrompt::Create(
360    content::DownloadItem* item,
361    content::WebContents* web_contents,
362    bool show_context,
363    const OnDone& done) {
364  DownloadDangerPromptViews* download_danger_prompt =
365      new DownloadDangerPromptViews(item, web_contents, show_context, done);
366
367  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
368      WebContentsModalDialogManager::FromWebContents(web_contents);
369  WebContentsModalDialogManagerDelegate* modal_delegate =
370      web_contents_modal_dialog_manager->delegate();
371  CHECK(modal_delegate);
372  views::Widget* dialog = views::Widget::CreateWindowAsFramelessChild(
373      download_danger_prompt,
374      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
375  web_contents_modal_dialog_manager->ShowDialog(dialog->GetNativeView());
376
377  return download_danger_prompt;
378}
379