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