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