Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / download / download_item_view.cc
blob94d017d9d5c542bbca6d29293044d516c380cba7
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.
5 #include "chrome/browser/ui/views/download/download_item_view.h"
7 #include <algorithm>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/file_path.h"
13 #include "base/i18n/break_iterator.h"
14 #include "base/i18n/rtl.h"
15 #include "base/location.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/sys_string_conversions.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/download/chrome_download_manager_delegate.h"
24 #include "chrome/browser/download/download_item_model.h"
25 #include "chrome/browser/download/download_stats.h"
26 #include "chrome/browser/download/drag_download_item.h"
27 #include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/safe_browsing/download_feedback_service.h"
30 #include "chrome/browser/safe_browsing/download_protection_service.h"
31 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
32 #include "chrome/browser/themes/theme_properties.h"
33 #include "chrome/browser/ui/views/download/download_feedback_dialog_view.h"
34 #include "chrome/browser/ui/views/download/download_shelf_context_menu_view.h"
35 #include "chrome/browser/ui/views/download/download_shelf_view.h"
36 #include "chrome/browser/ui/views/frame/browser_view.h"
37 #include "chrome/common/pref_names.h"
38 #include "chrome/grit/generated_resources.h"
39 #include "content/public/browser/download_danger_type.h"
40 #include "grit/theme_resources.h"
41 #include "third_party/icu/source/common/unicode/uchar.h"
42 #include "ui/accessibility/ax_view_state.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "ui/base/resource/resource_bundle.h"
45 #include "ui/base/theme_provider.h"
46 #include "ui/events/event.h"
47 #include "ui/gfx/animation/slide_animation.h"
48 #include "ui/gfx/canvas.h"
49 #include "ui/gfx/color_utils.h"
50 #include "ui/gfx/image/image.h"
51 #include "ui/gfx/text_elider.h"
52 #include "ui/gfx/text_utils.h"
53 #include "ui/views/controls/button/label_button.h"
54 #include "ui/views/controls/label.h"
55 #include "ui/views/mouse_constants.h"
56 #include "ui/views/widget/root_view.h"
57 #include "ui/views/widget/widget.h"
59 using content::DownloadItem;
60 using extensions::ExperienceSamplingEvent;
62 // TODO(paulg): These may need to be adjusted when download progress
63 // animation is added, and also possibly to take into account
64 // different screen resolutions.
65 static const int kTextWidth = 140; // Pixels
66 static const int kDangerousTextWidth = 200; // Pixels
67 static const int kVerticalPadding = 3; // Pixels
68 static const int kVerticalTextPadding = 2; // Pixels
69 static const int kTooltipMaxWidth = 800; // Pixels
71 // We add some padding before the left image so that the progress animation icon
72 // hides the corners of the left image.
73 static const int kLeftPadding = 0; // Pixels.
75 // The space between the Save and Discard buttons when prompting for a dangerous
76 // download.
77 static const int kButtonPadding = 5; // Pixels.
79 // The space on the left and right side of the dangerous download label.
80 static const int kLabelPadding = 4; // Pixels.
82 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
84 // How long the 'download complete' animation should last for.
85 static const int kCompleteAnimationDurationMs = 2500;
87 // How long the 'download interrupted' animation should last for.
88 static const int kInterruptedAnimationDurationMs = 2500;
90 // How long we keep the item disabled after the user clicked it to open the
91 // downloaded item.
92 static const int kDisabledOnOpenDuration = 3000;
94 // Darken light-on-dark download status text by 20% before drawing, thus
95 // creating a "muted" version of title text for both dark-on-light and
96 // light-on-dark themes.
97 static const double kDownloadItemLuminanceMod = 0.8;
99 DownloadItemView::DownloadItemView(DownloadItem* download_item,
100 DownloadShelfView* parent)
101 : warning_icon_(NULL),
102 shelf_(parent),
103 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
104 body_state_(NORMAL),
105 drop_down_state_(NORMAL),
106 mode_(NORMAL_MODE),
107 drop_down_pressed_(false),
108 dragging_(false),
109 starting_drag_(false),
110 model_(download_item),
111 save_button_(NULL),
112 discard_button_(NULL),
113 dangerous_download_label_(NULL),
114 dangerous_download_label_sized_(false),
115 disabled_while_opening_(false),
116 creation_time_(base::Time::Now()),
117 time_download_warning_shown_(base::Time()),
118 weak_ptr_factory_(this) {
119 DCHECK(download());
120 download()->AddObserver(this);
121 set_context_menu_controller(this);
123 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
125 BodyImageSet normal_body_image_set = {
126 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
127 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
128 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
129 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
130 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
131 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
132 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
133 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
134 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
136 normal_body_image_set_ = normal_body_image_set;
138 DropDownImageSet normal_drop_down_image_set = {
139 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
140 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
141 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
143 normal_drop_down_image_set_ = normal_drop_down_image_set;
145 BodyImageSet hot_body_image_set = {
146 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
147 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
148 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
149 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
150 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
151 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
152 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
153 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
154 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
156 hot_body_image_set_ = hot_body_image_set;
158 DropDownImageSet hot_drop_down_image_set = {
159 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
160 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
161 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
163 hot_drop_down_image_set_ = hot_drop_down_image_set;
165 BodyImageSet pushed_body_image_set = {
166 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
167 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
168 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
169 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
170 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
171 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
172 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
173 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
174 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
176 pushed_body_image_set_ = pushed_body_image_set;
178 DropDownImageSet pushed_drop_down_image_set = {
179 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
180 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
181 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
183 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
185 BodyImageSet dangerous_mode_body_image_set = {
186 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
187 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
188 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
189 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
190 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
191 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
192 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
193 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
194 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
196 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
198 malicious_mode_body_image_set_ = normal_body_image_set;
200 LoadIcon();
202 font_list_ = rb.GetFontList(ui::ResourceBundle::BaseFont);
203 box_height_ = std::max<int>(2 * kVerticalPadding + font_list_.GetHeight() +
204 kVerticalTextPadding + font_list_.GetHeight(),
205 2 * kVerticalPadding +
206 normal_body_image_set_.top_left->height() +
207 normal_body_image_set_.bottom_left->height());
209 if (DownloadShelf::kProgressIndicatorSize > box_height_)
210 box_y_ = (DownloadShelf::kProgressIndicatorSize - box_height_) / 2;
211 else
212 box_y_ = 0;
214 body_hover_animation_.reset(new gfx::SlideAnimation(this));
215 drop_hover_animation_.reset(new gfx::SlideAnimation(this));
217 SetAccessibilityFocusable(true);
219 OnDownloadUpdated(download());
220 UpdateDropDownButtonPosition();
223 DownloadItemView::~DownloadItemView() {
224 StopDownloadProgress();
225 download()->RemoveObserver(this);
227 // ExperienceSampling: If the user took no action to remove the warning
228 // before it disappeared, then the user effectively dismissed the download
229 // without keeping it.
230 if (sampling_event_.get())
231 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kIgnore);
234 // Progress animation handlers.
236 void DownloadItemView::StartDownloadProgress() {
237 if (progress_timer_.IsRunning())
238 return;
239 progress_start_time_ = base::TimeTicks::Now();
240 progress_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(
241 DownloadShelf::kProgressRateMs),
242 this, &DownloadItemView::SchedulePaint);
245 void DownloadItemView::StopDownloadProgress() {
246 if (!progress_timer_.IsRunning())
247 return;
248 previous_progress_elapsed_ += base::TimeTicks::Now() - progress_start_time_;
249 progress_start_time_ = base::TimeTicks();
250 progress_timer_.Stop();
253 void DownloadItemView::OnExtractIconComplete(gfx::Image* icon_bitmap) {
254 if (icon_bitmap)
255 shelf_->SchedulePaint();
258 // DownloadObserver interface.
260 // Update the progress graphic on the icon and our text status label
261 // to reflect our current bytes downloaded, time remaining.
262 void DownloadItemView::OnDownloadUpdated(DownloadItem* download_item) {
263 DCHECK_EQ(download(), download_item);
265 if (!model_.ShouldShowInShelf()) {
266 shelf_->RemoveDownloadView(this); // This will delete us!
267 return;
270 if (IsShowingWarningDialog() && !model_.IsDangerous()) {
271 // We have been approved.
272 ClearWarningDialog();
273 } else if (!IsShowingWarningDialog() && model_.IsDangerous()) {
274 ShowWarningDialog();
275 // Force the shelf to layout again as our size has changed.
276 shelf_->Layout();
277 SchedulePaint();
278 } else {
279 base::string16 status_text = model_.GetStatusText();
280 switch (download()->GetState()) {
281 case DownloadItem::IN_PROGRESS:
282 download()->IsPaused() ?
283 StopDownloadProgress() : StartDownloadProgress();
284 LoadIconIfItemPathChanged();
285 break;
286 case DownloadItem::INTERRUPTED:
287 StopDownloadProgress();
288 complete_animation_.reset(new gfx::SlideAnimation(this));
289 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
290 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
291 complete_animation_->Show();
292 SchedulePaint();
293 LoadIcon();
294 break;
295 case DownloadItem::COMPLETE:
296 if (model_.ShouldRemoveFromShelfWhenComplete()) {
297 shelf_->RemoveDownloadView(this); // This will delete us!
298 return;
300 StopDownloadProgress();
301 complete_animation_.reset(new gfx::SlideAnimation(this));
302 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
303 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
304 complete_animation_->Show();
305 SchedulePaint();
306 LoadIcon();
307 break;
308 case DownloadItem::CANCELLED:
309 StopDownloadProgress();
310 if (complete_animation_)
311 complete_animation_->Stop();
312 LoadIcon();
313 break;
314 default:
315 NOTREACHED();
317 status_text_ = status_text;
320 base::string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth);
321 if (new_tip != tooltip_text_) {
322 tooltip_text_ = new_tip;
323 TooltipTextChanged();
326 UpdateAccessibleName();
328 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
329 // are spaces between each DownloadItemView that the parent is responsible
330 // for painting.
331 shelf_->SchedulePaint();
334 void DownloadItemView::OnDownloadDestroyed(DownloadItem* download) {
335 shelf_->RemoveDownloadView(this); // This will delete us!
338 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
339 disabled_while_opening_ = true;
340 SetEnabled(false);
341 base::MessageLoop::current()->task_runner()->PostDelayedTask(
342 FROM_HERE,
343 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
344 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
346 // Notify our parent.
347 shelf_->OpenedDownload(this);
350 // View overrides
352 // In dangerous mode we have to layout our buttons.
353 void DownloadItemView::Layout() {
354 if (IsShowingWarningDialog()) {
355 BodyImageSet* body_image_set =
356 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
357 &malicious_mode_body_image_set_;
358 int x = kLeftPadding + body_image_set->top_left->width() +
359 warning_icon_->width() + kLabelPadding;
360 int y = (height() - dangerous_download_label_->height()) / 2;
361 dangerous_download_label_->SetBounds(x, y,
362 dangerous_download_label_->width(),
363 dangerous_download_label_->height());
364 gfx::Size button_size = GetButtonSize();
365 x += dangerous_download_label_->width() + kLabelPadding;
366 y = (height() - button_size.height()) / 2;
367 if (save_button_) {
368 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
369 x += button_size.width() + kButtonPadding;
371 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
372 UpdateColorsFromTheme();
376 gfx::Size DownloadItemView::GetPreferredSize() const {
377 int width, height;
379 // First, we set the height to the height of two rows or text plus margins.
380 height = 2 * kVerticalPadding + 2 * font_list_.GetHeight() +
381 kVerticalTextPadding;
382 // Then we increase the size if the progress icon doesn't fit.
383 height = std::max<int>(height, DownloadShelf::kProgressIndicatorSize);
385 if (IsShowingWarningDialog()) {
386 const BodyImageSet* body_image_set =
387 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
388 &malicious_mode_body_image_set_;
389 width = kLeftPadding + body_image_set->top_left->width();
390 width += warning_icon_->width() + kLabelPadding;
391 width += dangerous_download_label_->width() + kLabelPadding;
392 gfx::Size button_size = GetButtonSize();
393 // Make sure the button fits.
394 height = std::max<int>(height, 2 * kVerticalPadding + button_size.height());
395 // Then we make sure the warning icon fits.
396 height = std::max<int>(height, 2 * kVerticalPadding +
397 warning_icon_->height());
398 if (save_button_)
399 width += button_size.width() + kButtonPadding;
400 width += button_size.width();
401 width += body_image_set->top_right->width();
402 if (mode_ == MALICIOUS_MODE)
403 width += normal_drop_down_image_set_.top->width();
404 } else {
405 width = kLeftPadding + normal_body_image_set_.top_left->width();
406 width += DownloadShelf::kProgressIndicatorSize;
407 width += kTextWidth;
408 width += normal_body_image_set_.top_right->width();
409 width += normal_drop_down_image_set_.top->width();
411 return gfx::Size(width, height);
414 // Handle a mouse click and open the context menu if the mouse is
415 // over the drop-down region.
416 bool DownloadItemView::OnMousePressed(const ui::MouseEvent& event) {
417 HandlePressEvent(event, event.IsOnlyLeftMouseButton());
418 return true;
421 // Handle drag (file copy) operations.
422 bool DownloadItemView::OnMouseDragged(const ui::MouseEvent& event) {
423 // Mouse should not activate us in dangerous mode.
424 if (IsShowingWarningDialog())
425 return true;
427 if (!starting_drag_) {
428 starting_drag_ = true;
429 drag_start_point_ = event.location();
431 if (dragging_) {
432 if (download()->GetState() == DownloadItem::COMPLETE) {
433 IconManager* im = g_browser_process->icon_manager();
434 gfx::Image* icon = im->LookupIconFromFilepath(
435 download()->GetTargetFilePath(), IconLoader::SMALL);
436 views::Widget* widget = GetWidget();
437 DragDownloadItem(
438 download(), icon, widget ? widget->GetNativeView() : NULL);
440 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
441 dragging_ = true;
443 return true;
446 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
447 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
450 void DownloadItemView::OnMouseCaptureLost() {
451 // Mouse should not activate us in dangerous mode.
452 if (mode_ == DANGEROUS_MODE)
453 return;
455 if (dragging_) {
456 // Starting a drag results in a MouseCaptureLost.
457 dragging_ = false;
458 starting_drag_ = false;
460 SetState(NORMAL, NORMAL);
463 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
464 // Mouse should not activate us in dangerous mode.
465 if (mode_ == DANGEROUS_MODE)
466 return;
468 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
469 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
472 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
473 // Mouse should not activate us in dangerous mode.
474 if (mode_ == DANGEROUS_MODE)
475 return;
477 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
480 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
481 // Key press should not activate us in dangerous mode.
482 if (IsShowingWarningDialog())
483 return true;
485 if (event.key_code() == ui::VKEY_SPACE ||
486 event.key_code() == ui::VKEY_RETURN) {
487 // OpenDownload may delete this, so don't add any code after this line.
488 OpenDownload();
489 return true;
491 return false;
494 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
495 base::string16* tooltip) const {
496 if (IsShowingWarningDialog()) {
497 tooltip->clear();
498 return false;
501 tooltip->assign(tooltip_text_);
503 return true;
506 void DownloadItemView::GetAccessibleState(ui::AXViewState* state) {
507 state->name = accessible_name_;
508 state->role = ui::AX_ROLE_BUTTON;
509 if (model_.IsDangerous())
510 state->AddStateFlag(ui::AX_STATE_DISABLED);
511 else
512 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
515 void DownloadItemView::OnThemeChanged() {
516 UpdateColorsFromTheme();
519 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
520 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
521 HandlePressEvent(*event, true);
522 event->SetHandled();
523 return;
526 if (event->type() == ui::ET_GESTURE_TAP) {
527 HandleClickEvent(*event, true);
528 event->SetHandled();
529 return;
532 SetState(NORMAL, NORMAL);
533 views::View::OnGestureEvent(event);
536 void DownloadItemView::ShowContextMenuForView(View* source,
537 const gfx::Point& point,
538 ui::MenuSourceType source_type) {
539 // |point| is in screen coordinates. So convert it to local coordinates first.
540 gfx::Point local_point = point;
541 ConvertPointFromScreen(this, &local_point);
542 ShowContextMenuImpl(local_point, source_type);
545 void DownloadItemView::ButtonPressed(views::Button* sender,
546 const ui::Event& event) {
547 base::TimeDelta warning_duration;
548 if (!time_download_warning_shown_.is_null())
549 warning_duration = base::Time::Now() - time_download_warning_shown_;
551 if (save_button_ && sender == save_button_) {
552 // The user has confirmed a dangerous download. We'd record how quickly the
553 // user did this to detect whether we're being clickjacked.
554 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
555 // ExperienceSampling: User chose to proceed with a dangerous download.
556 if (sampling_event_.get()) {
557 sampling_event_->CreateUserDecisionEvent(
558 ExperienceSamplingEvent::kProceed);
559 sampling_event_.reset(NULL);
561 // This will change the state and notify us.
562 download()->ValidateDangerousDownload();
563 return;
566 // WARNING: all end states after this point delete |this|.
567 DCHECK_EQ(discard_button_, sender);
568 if (model_.IsMalicious()) {
569 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
570 // ExperienceSampling: User chose to dismiss the dangerous download.
571 if (sampling_event_.get()) {
572 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
573 sampling_event_.reset(NULL);
575 shelf_->RemoveDownloadView(this);
576 return;
578 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration);
579 if (model_.ShouldAllowDownloadFeedback() &&
580 !shelf_->browser()->profile()->IsOffTheRecord()) {
581 if (!shelf_->browser()->profile()->GetPrefs()->HasPrefPath(
582 prefs::kSafeBrowsingExtendedReportingEnabled)) {
583 // Show dialog, because the dialog hasn't been shown before.
584 DownloadFeedbackDialogView::Show(
585 shelf_->get_parent()->GetNativeWindow(),
586 shelf_->browser()->profile(),
587 shelf_->GetNavigator(),
588 base::Bind(
589 &DownloadItemView::PossiblySubmitDownloadToFeedbackService,
590 weak_ptr_factory_.GetWeakPtr()));
591 } else {
592 PossiblySubmitDownloadToFeedbackService(
593 shelf_->browser()->profile()->GetPrefs()->GetBoolean(
594 prefs::kSafeBrowsingExtendedReportingEnabled));
596 return;
598 download()->Remove();
601 void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) {
602 // We don't care if what animation (body button/drop button/complete),
603 // is calling back, as they all have to go through the same paint call.
604 SchedulePaint();
607 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
608 OnPaintBackground(canvas);
609 if (HasFocus())
610 canvas->DrawFocusRect(GetLocalBounds());
613 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
614 // and MALICIOUS_MODE).
616 // NORMAL_MODE: We are displaying an in-progress or completed download.
617 // .-------------------------------+-.
618 // | [icon] Filename |v|
619 // | [ ] Status | |
620 // `-------------------------------+-'
621 // | | \_ Drop down button. Invokes menu. Responds
622 // | | to mouse. (NORMAL, HOT or PUSHED).
623 // | \_ Icon is overlaid on top of in-progress animation.
624 // \_ Both the body and the drop down button respond to mouse hover and can be
625 // pushed (NORMAL, HOT or PUSHED).
627 // DANGEROUS_MODE: The file could be potentially dangerous.
628 // .-------------------------------------------------------.
629 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
630 // | [ ] [destroy your computer..] [ ] [ ] |
631 // `-------------------------------------------------------'
632 // | | | | \_ No drop down button.
633 // | | | \_ Buttons are views::LabelButtons.
634 // | | \_ Text is in a label (dangerous_download_label_)
635 // | \_ Warning icon. No progress animation.
636 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
638 // MALICIOUS_MODE: The file is known malware.
639 // .---------------------------------------------+-.
640 // | [ - ] [This file is malicious.] [ Discard ] |v|
641 // | [ ] [ ] [ ] | |-.
642 // `---------------------------------------------+-' |
643 // | | | | Drop down button. Responds to
644 // | | | | mouse.(NORMAL, HOT or PUSHED)
645 // | | | \_ Button is a views::LabelButton.
646 // | | \_ Text is in a label (dangerous_download_label_)
647 // | \_ Warning icon. No progress animation.
648 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
650 void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) {
651 BodyImageSet* body_image_set = NULL;
652 switch (mode_) {
653 case NORMAL_MODE:
654 if (body_state_ == PUSHED)
655 body_image_set = &pushed_body_image_set_;
656 else // NORMAL or HOT
657 body_image_set = &normal_body_image_set_;
658 break;
659 case DANGEROUS_MODE:
660 body_image_set = &dangerous_mode_body_image_set_;
661 break;
662 case MALICIOUS_MODE:
663 body_image_set = &malicious_mode_body_image_set_;
664 break;
665 default:
666 NOTREACHED();
669 DropDownImageSet* drop_down_image_set = NULL;
670 switch (mode_) {
671 case NORMAL_MODE:
672 case MALICIOUS_MODE:
673 if (drop_down_state_ == PUSHED)
674 drop_down_image_set = &pushed_drop_down_image_set_;
675 else // NORMAL or HOT
676 drop_down_image_set = &normal_drop_down_image_set_;
677 break;
678 case DANGEROUS_MODE:
679 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
680 // drop_down_image_set == NULL.
681 break;
682 default:
683 NOTREACHED();
686 int center_width = width() - kLeftPadding -
687 body_image_set->left->width() -
688 body_image_set->right->width() -
689 (drop_down_image_set ?
690 normal_drop_down_image_set_.center->width() :
693 // May be caused by animation.
694 if (center_width <= 0)
695 return;
697 // Draw status before button image to effectively lighten text. No status for
698 // warning dialogs.
699 if (!IsShowingWarningDialog()) {
700 if (!status_text_.empty()) {
701 int mirrored_x = GetMirroredXWithWidthInView(
702 DownloadShelf::kProgressIndicatorSize, kTextWidth);
703 // Add font_list_.height() to compensate for title, which is drawn later.
704 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() +
705 kVerticalTextPadding;
706 SkColor file_name_color = GetThemeProvider()->GetColor(
707 ThemeProperties::COLOR_BOOKMARK_TEXT);
708 // If text is light-on-dark, lightening it alone will do nothing.
709 // Therefore we mute luminance a wee bit before drawing in this case.
710 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
711 file_name_color = SkColorSetRGB(
712 static_cast<int>(kDownloadItemLuminanceMod *
713 SkColorGetR(file_name_color)),
714 static_cast<int>(kDownloadItemLuminanceMod *
715 SkColorGetG(file_name_color)),
716 static_cast<int>(kDownloadItemLuminanceMod *
717 SkColorGetB(file_name_color)));
718 canvas->DrawStringRect(status_text_, font_list_, file_name_color,
719 gfx::Rect(mirrored_x, y, kTextWidth,
720 font_list_.GetHeight()));
724 // Paint the background images.
725 int x = kLeftPadding;
726 canvas->Save();
727 if (base::i18n::IsRTL()) {
728 // Since we do not have the mirrored images for
729 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
730 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
731 // for RTL UI, we flip the canvas to draw those images mirrored.
732 // Consequently, we do not need to mirror the x-axis of those images.
733 canvas->Translate(gfx::Vector2d(width(), 0));
734 canvas->Scale(-1, 1);
736 PaintImages(canvas,
737 body_image_set->top_left, body_image_set->left,
738 body_image_set->bottom_left,
739 x, box_y_, box_height_, body_image_set->top_left->width());
740 x += body_image_set->top_left->width();
741 PaintImages(canvas,
742 body_image_set->top, body_image_set->center,
743 body_image_set->bottom,
744 x, box_y_, box_height_, center_width);
745 x += center_width;
746 PaintImages(canvas,
747 body_image_set->top_right, body_image_set->right,
748 body_image_set->bottom_right,
749 x, box_y_, box_height_, body_image_set->top_right->width());
751 // Overlay our body hot state. Warning dialogs don't display body a hot state.
752 if (!IsShowingWarningDialog() &&
753 body_hover_animation_->GetCurrentValue() > 0) {
754 canvas->SaveLayerAlpha(
755 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
757 int x = kLeftPadding;
758 PaintImages(canvas,
759 hot_body_image_set_.top_left, hot_body_image_set_.left,
760 hot_body_image_set_.bottom_left,
761 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
762 x += body_image_set->top_left->width();
763 PaintImages(canvas,
764 hot_body_image_set_.top, hot_body_image_set_.center,
765 hot_body_image_set_.bottom,
766 x, box_y_, box_height_, center_width);
767 x += center_width;
768 PaintImages(canvas,
769 hot_body_image_set_.top_right, hot_body_image_set_.right,
770 hot_body_image_set_.bottom_right,
771 x, box_y_, box_height_,
772 hot_body_image_set_.top_right->width());
773 canvas->Restore();
776 x += body_image_set->top_right->width();
778 // Paint the drop-down.
779 if (drop_down_image_set) {
780 PaintImages(canvas,
781 drop_down_image_set->top, drop_down_image_set->center,
782 drop_down_image_set->bottom,
783 x, box_y_, box_height_, drop_down_image_set->top->width());
785 // Overlay our drop-down hot state.
786 if (drop_hover_animation_->GetCurrentValue() > 0) {
787 canvas->SaveLayerAlpha(
788 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
790 PaintImages(canvas,
791 drop_down_image_set->top, drop_down_image_set->center,
792 drop_down_image_set->bottom,
793 x, box_y_, box_height_, drop_down_image_set->top->width());
795 canvas->Restore();
799 // Restore the canvas to avoid file name etc. text are drawn flipped.
800 // Consequently, the x-axis of following canvas->DrawXXX() method should be
801 // mirrored so the text and images are down in the right positions.
802 canvas->Restore();
804 // Print the text, left aligned and always print the file extension.
805 // Last value of x was the end of the right image, just before the button.
806 // Note that in dangerous mode we use a label (as the text is multi-line).
807 if (!IsShowingWarningDialog()) {
808 base::string16 filename;
809 if (!disabled_while_opening_) {
810 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(),
811 font_list_, kTextWidth);
812 } else {
813 // First, Calculate the download status opening string width.
814 base::string16 status_string =
815 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
816 base::string16());
817 int status_string_width = gfx::GetStringWidth(status_string, font_list_);
818 // Then, elide the file name.
819 base::string16 filename_string =
820 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_,
821 kTextWidth - status_string_width);
822 // Last, concat the whole string.
823 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
824 filename_string);
827 int mirrored_x = GetMirroredXWithWidthInView(
828 DownloadShelf::kProgressIndicatorSize, kTextWidth);
829 SkColor file_name_color = GetThemeProvider()->GetColor(
830 ThemeProperties::COLOR_BOOKMARK_TEXT);
831 int y =
832 box_y_ + (status_text_.empty() ?
833 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
835 // Draw the file's name.
836 canvas->DrawStringRect(
837 filename, font_list_,
838 enabled() ? file_name_color : kFileNameDisabledColor,
839 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight()));
842 // Load the icon.
843 IconManager* im = g_browser_process->icon_manager();
844 gfx::Image* image = im->LookupIconFromFilepath(
845 download()->GetTargetFilePath(), IconLoader::SMALL);
846 const gfx::ImageSkia* icon = NULL;
847 if (IsShowingWarningDialog())
848 icon = warning_icon_;
849 else if (image)
850 icon = image->ToImageSkia();
852 // We count on the fact that the icon manager will cache the icons and if one
853 // is available, it will be cached here. We *don't* want to request the icon
854 // to be loaded here, since this will also get called if the icon can't be
855 // loaded, in which case LookupIcon will always be NULL. The loading will be
856 // triggered only when we think the status might change.
857 if (icon) {
858 if (!IsShowingWarningDialog()) {
859 DownloadItem::DownloadState state = download()->GetState();
860 canvas->Save();
861 if (base::i18n::IsRTL())
862 canvas->Translate(
863 gfx::Vector2d(width() - DownloadShelf::kProgressIndicatorSize, 0));
865 if (state == DownloadItem::IN_PROGRESS) {
866 base::TimeDelta progress_time = previous_progress_elapsed_;
867 if (!download()->IsPaused())
868 progress_time += base::TimeTicks::Now() - progress_start_time_;
869 DownloadShelf::PaintDownloadProgress(canvas, *GetThemeProvider(),
870 progress_time,
871 model_.PercentComplete());
872 } else if (complete_animation_.get() &&
873 complete_animation_->is_animating()) {
874 if (state == DownloadItem::INTERRUPTED) {
875 DownloadShelf::PaintDownloadInterrupted(
876 canvas, *GetThemeProvider(),
877 complete_animation_->GetCurrentValue());
878 } else {
879 DCHECK_EQ(DownloadItem::COMPLETE, state);
880 DownloadShelf::PaintDownloadComplete(
881 canvas, *GetThemeProvider(),
882 complete_animation_->GetCurrentValue());
885 canvas->Restore();
888 // Draw the icon image.
889 int icon_x, icon_y;
891 if (IsShowingWarningDialog()) {
892 icon_x = kLeftPadding + body_image_set->top_left->width();
893 icon_y = (height() - icon->height()) / 2;
894 } else {
895 icon_x = DownloadShelf::kFiletypeIconOffset;
896 icon_y = DownloadShelf::kFiletypeIconOffset;
898 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
899 if (enabled()) {
900 canvas->DrawImageInt(*icon, icon_x, icon_y);
901 } else {
902 // Use an alpha to make the image look disabled.
903 SkPaint paint;
904 paint.setAlpha(120);
905 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
910 void DownloadItemView::OnFocus() {
911 View::OnFocus();
912 // We render differently when focused.
913 SchedulePaint();
916 void DownloadItemView::OnBlur() {
917 View::OnBlur();
918 // We render differently when focused.
919 SchedulePaint();
922 void DownloadItemView::OpenDownload() {
923 DCHECK(!IsShowingWarningDialog());
924 // We're interested in how long it takes users to open downloads. If they
925 // open downloads super quickly, we should be concerned about clickjacking.
926 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
927 base::Time::Now() - creation_time_);
929 UpdateAccessibleName();
931 // Calling download()->OpenDownload may delete this, so this must be
932 // the last thing we do.
933 download()->OpenDownload();
936 bool DownloadItemView::SubmitDownloadToFeedbackService() {
937 #if defined(FULL_SAFE_BROWSING)
938 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
939 if (!sb_service)
940 return false;
941 safe_browsing::DownloadProtectionService* download_protection_service =
942 sb_service->download_protection_service();
943 if (!download_protection_service)
944 return false;
945 download_protection_service->feedback_service()->BeginFeedbackForDownload(
946 download());
947 // WARNING: we are deleted at this point. Don't access 'this'.
948 return true;
949 #else
950 NOTREACHED();
951 return false;
952 #endif
955 void DownloadItemView::PossiblySubmitDownloadToFeedbackService(bool enabled) {
956 if (!enabled || !SubmitDownloadToFeedbackService())
957 download()->Remove();
958 // WARNING: 'this' is deleted at this point. Don't access 'this'.
961 void DownloadItemView::LoadIcon() {
962 IconManager* im = g_browser_process->icon_manager();
963 last_download_item_path_ = download()->GetTargetFilePath();
964 im->LoadIcon(last_download_item_path_,
965 IconLoader::SMALL,
966 base::Bind(&DownloadItemView::OnExtractIconComplete,
967 base::Unretained(this)),
968 &cancelable_task_tracker_);
971 void DownloadItemView::LoadIconIfItemPathChanged() {
972 base::FilePath current_download_path = download()->GetTargetFilePath();
973 if (last_download_item_path_ == current_download_path)
974 return;
976 LoadIcon();
979 void DownloadItemView::UpdateColorsFromTheme() {
980 if (dangerous_download_label_ && GetThemeProvider()) {
981 dangerous_download_label_->SetEnabledColor(
982 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
986 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
987 ui::MenuSourceType source_type) {
988 gfx::Point point = p;
989 gfx::Size size;
991 // Similar hack as in MenuButton.
992 // We're about to show the menu from a mouse press. By showing from the
993 // mouse press event we block RootView in mouse dispatching. This also
994 // appears to cause RootView to get a mouse pressed BEFORE the mouse
995 // release is seen, which means RootView sends us another mouse press no
996 // matter where the user pressed. To force RootView to recalculate the
997 // mouse target during the mouse press we explicitly set the mouse handler
998 // to NULL.
999 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
1000 SetMouseHandler(NULL);
1002 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
1003 // to drop down arrow button.
1004 if (source_type != ui::MENU_SOURCE_MOUSE &&
1005 source_type != ui::MENU_SOURCE_TOUCH) {
1006 drop_down_pressed_ = true;
1007 SetState(NORMAL, PUSHED);
1008 point.SetPoint(drop_down_x_left_, box_y_);
1009 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
1011 // Post a task to release the button. When we call the Run method on the menu
1012 // below, it runs an inner message loop that might cause us to be deleted.
1013 // Posting a task with a WeakPtr lets us safely handle the button release.
1014 base::MessageLoop::current()->task_runner()->PostNonNestableTask(
1015 FROM_HERE, base::Bind(&DownloadItemView::ReleaseDropDown,
1016 weak_ptr_factory_.GetWeakPtr()));
1017 views::View::ConvertPointToScreen(this, &point);
1019 if (!context_menu_.get())
1020 context_menu_.reset(new DownloadShelfContextMenuView(download()));
1022 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
1023 gfx::Rect(point, size), source_type);
1024 // We could be deleted now.
1027 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event,
1028 bool active_event) {
1029 // The event should not activate us in dangerous mode.
1030 if (mode_ == DANGEROUS_MODE)
1031 return;
1033 // Stop any completion animation.
1034 if (complete_animation_.get() && complete_animation_->is_animating())
1035 complete_animation_->End();
1037 if (active_event) {
1038 if (InDropDownButtonXCoordinateRange(event.x())) {
1039 if (context_menu_.get()) {
1040 // Ignore two close clicks. This typically happens when the user clicks
1041 // the button to close the menu.
1042 base::TimeDelta delta =
1043 base::TimeTicks::Now() - context_menu_->close_time();
1044 if (delta.InMilliseconds() < views::kMinimumMsBetweenButtonClicks)
1045 return;
1047 drop_down_pressed_ = true;
1048 SetState(NORMAL, PUSHED);
1049 // We are setting is_mouse_gesture to false when calling ShowContextMenu
1050 // so that the positioning of the context menu will be similar to a
1051 // keyboard invocation. I.e. we want the menu to always be positioned
1052 // next to the drop down button instead of the next to the pointer.
1053 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD);
1054 // Once called, it is possible that *this was deleted (e.g.: due to
1055 // invoking the 'Discard' action.)
1056 } else if (!IsShowingWarningDialog()) {
1057 SetState(PUSHED, NORMAL);
1062 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event,
1063 bool active_event) {
1064 // Mouse should not activate us in dangerous mode.
1065 if (mode_ == DANGEROUS_MODE)
1066 return;
1068 SetState(NORMAL, NORMAL);
1070 if (!active_event ||
1071 InDropDownButtonXCoordinateRange(event.x()) ||
1072 IsShowingWarningDialog()) {
1073 return;
1076 // OpenDownload may delete this, so don't add any code after this line.
1077 OpenDownload();
1080 // Load an icon for the file type we're downloading, and animate any in progress
1081 // download state.
1082 void DownloadItemView::PaintImages(gfx::Canvas* canvas,
1083 const gfx::ImageSkia* top_image,
1084 const gfx::ImageSkia* center_image,
1085 const gfx::ImageSkia* bottom_image,
1086 int x, int y, int height, int width) {
1087 int middle_height = height - top_image->height() - bottom_image->height();
1088 // Draw the top.
1089 canvas->DrawImageInt(*top_image,
1090 0, 0, top_image->width(), top_image->height(),
1091 x, y, width, top_image->height(), false);
1092 y += top_image->height();
1093 // Draw the center.
1094 canvas->DrawImageInt(*center_image,
1095 0, 0, center_image->width(), center_image->height(),
1096 x, y, width, middle_height, false);
1097 y += middle_height;
1098 // Draw the bottom.
1099 canvas->DrawImageInt(*bottom_image,
1100 0, 0, bottom_image->width(), bottom_image->height(),
1101 x, y, width, bottom_image->height(), false);
1104 void DownloadItemView::SetState(State new_body_state, State new_drop_state) {
1105 // If we are showing a warning dialog, we don't change body state.
1106 if (IsShowingWarningDialog()) {
1107 new_body_state = NORMAL;
1109 // Current body_state_ should always be NORMAL for warning dialogs.
1110 DCHECK_EQ(NORMAL, body_state_);
1111 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1112 DCHECK_NE(DANGEROUS_MODE, mode_);
1114 // Avoid extra SchedulePaint()s if the state is going to be the same.
1115 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state)
1116 return;
1118 AnimateStateTransition(body_state_, new_body_state,
1119 body_hover_animation_.get());
1120 AnimateStateTransition(drop_down_state_, new_drop_state,
1121 drop_hover_animation_.get());
1122 body_state_ = new_body_state;
1123 drop_down_state_ = new_drop_state;
1124 SchedulePaint();
1127 void DownloadItemView::ClearWarningDialog() {
1128 DCHECK(download()->GetDangerType() ==
1129 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1130 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1132 mode_ = NORMAL_MODE;
1133 body_state_ = NORMAL;
1134 drop_down_state_ = NORMAL;
1136 // ExperienceSampling: User proceeded through the warning.
1137 if (sampling_event_.get()) {
1138 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
1139 sampling_event_.reset(NULL);
1141 // Remove the views used by the warning dialog.
1142 if (save_button_) {
1143 RemoveChildView(save_button_);
1144 delete save_button_;
1145 save_button_ = NULL;
1147 RemoveChildView(discard_button_);
1148 delete discard_button_;
1149 discard_button_ = NULL;
1150 RemoveChildView(dangerous_download_label_);
1151 delete dangerous_download_label_;
1152 dangerous_download_label_ = NULL;
1153 dangerous_download_label_sized_ = false;
1154 cached_button_size_.SetSize(0,0);
1156 // Set the accessible name back to the status and filename instead of the
1157 // download warning.
1158 UpdateAccessibleName();
1159 UpdateDropDownButtonPosition();
1161 // We need to load the icon now that the download has the real path.
1162 LoadIcon();
1164 // Force the shelf to layout again as our size has changed.
1165 shelf_->Layout();
1166 shelf_->SchedulePaint();
1168 TooltipTextChanged();
1171 void DownloadItemView::ShowWarningDialog() {
1172 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
1173 time_download_warning_shown_ = base::Time::Now();
1174 content::DownloadDangerType danger_type = download()->GetDangerType();
1175 RecordDangerousDownloadWarningShown(danger_type);
1176 #if defined(FULL_SAFE_BROWSING)
1177 if (model_.ShouldAllowDownloadFeedback()) {
1178 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1179 danger_type);
1181 #endif
1182 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
1184 // ExperienceSampling: Dangerous or malicious download warning is being shown
1185 // to the user, so we start a new SamplingEvent and track it.
1186 std::string event_name = model_.MightBeMalicious()
1187 ? ExperienceSamplingEvent::kMaliciousDownload
1188 : ExperienceSamplingEvent::kDangerousDownload;
1189 sampling_event_.reset(
1190 new ExperienceSamplingEvent(event_name,
1191 download()->GetURL(),
1192 download()->GetReferrerUrl(),
1193 download()->GetBrowserContext()));
1195 body_state_ = NORMAL;
1196 drop_down_state_ = NORMAL;
1197 if (mode_ == DANGEROUS_MODE) {
1198 save_button_ = new views::LabelButton(
1199 this, model_.GetWarningConfirmButtonText());
1200 save_button_->SetStyle(views::Button::STYLE_BUTTON);
1201 AddChildView(save_button_);
1203 int discard_button_message = model_.IsMalicious() ?
1204 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD;
1205 discard_button_ = new views::LabelButton(
1206 this, l10n_util::GetStringUTF16(discard_button_message));
1207 discard_button_->SetStyle(views::Button::STYLE_BUTTON);
1208 AddChildView(discard_button_);
1210 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1211 switch (danger_type) {
1212 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
1213 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
1214 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
1215 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
1216 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
1217 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING);
1218 break;
1220 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
1221 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
1222 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
1223 case content::DOWNLOAD_DANGER_TYPE_MAX:
1224 NOTREACHED();
1225 // fallthrough
1227 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1228 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
1230 base::string16 dangerous_label =
1231 model_.GetWarningText(font_list_, kTextWidth);
1232 dangerous_download_label_ = new views::Label(dangerous_label);
1233 dangerous_download_label_->SetMultiLine(true);
1234 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1235 dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
1236 AddChildView(dangerous_download_label_);
1237 SizeLabelToMinWidth();
1238 UpdateDropDownButtonPosition();
1239 TooltipTextChanged();
1242 gfx::Size DownloadItemView::GetButtonSize() const {
1243 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
1244 gfx::Size size;
1246 // We cache the size when successfully retrieved, not for performance reasons
1247 // but because if this DownloadItemView is being animated while the tab is
1248 // not showing, the native buttons are not parented and their preferred size
1249 // is 0, messing-up the layout.
1250 if (cached_button_size_.width() != 0)
1251 return cached_button_size_;
1253 if (save_button_)
1254 size = save_button_->GetMinimumSize();
1255 gfx::Size discard_size = discard_button_->GetMinimumSize();
1257 size.SetSize(std::max(size.width(), discard_size.width()),
1258 std::max(size.height(), discard_size.height()));
1260 if (size.width() != 0)
1261 cached_button_size_ = size;
1263 return size;
1266 // This method computes the minimum width of the label for displaying its text
1267 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1268 // configuration with minimum width.
1269 void DownloadItemView::SizeLabelToMinWidth() {
1270 if (dangerous_download_label_sized_)
1271 return;
1273 base::string16 label_text = dangerous_download_label_->text();
1274 base::TrimWhitespace(label_text, base::TRIM_ALL, &label_text);
1275 DCHECK_EQ(base::string16::npos, label_text.find('\n'));
1277 // Make the label big so that GetPreferredSize() is not constrained by the
1278 // current width.
1279 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1281 // Use a const string from here. BreakIterator requies that text.data() not
1282 // change during its lifetime.
1283 const base::string16 original_text(label_text);
1284 // Using BREAK_WORD can work in most cases, but it can also break
1285 // lines where it should not. Using BREAK_LINE is safer although
1286 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1287 base::i18n::BreakIterator iter(original_text,
1288 base::i18n::BreakIterator::BREAK_LINE);
1289 bool status = iter.Init();
1290 DCHECK(status);
1292 base::string16 prev_text = original_text;
1293 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1294 int min_width = size.width();
1296 // Go through the string and try each line break (starting with no line break)
1297 // searching for the optimal line break position. Stop if we find one that
1298 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1299 // a short string (e.g.: "This file is malicious") from being broken up
1300 // unnecessarily.
1301 while (iter.Advance() && min_width > kDangerousTextWidth) {
1302 size_t pos = iter.pos();
1303 if (pos >= original_text.length())
1304 break;
1305 base::string16 current_text = original_text;
1306 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1307 // return false and inserting a new line after a surrogate pair
1308 // is perfectly ok.
1309 base::char16 line_end_char = current_text[pos - 1];
1310 if (u_isUWhiteSpace(line_end_char))
1311 current_text.replace(pos - 1, 1, 1, base::char16('\n'));
1312 else
1313 current_text.insert(pos, 1, base::char16('\n'));
1314 dangerous_download_label_->SetText(current_text);
1315 size = dangerous_download_label_->GetPreferredSize();
1317 // If the width is growing again, it means we passed the optimal width spot.
1318 if (size.width() > min_width) {
1319 dangerous_download_label_->SetText(prev_text);
1320 break;
1321 } else {
1322 min_width = size.width();
1324 prev_text = current_text;
1327 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1328 dangerous_download_label_sized_ = true;
1331 void DownloadItemView::Reenable() {
1332 disabled_while_opening_ = false;
1333 SetEnabled(true); // Triggers a repaint.
1336 void DownloadItemView::ReleaseDropDown() {
1337 drop_down_pressed_ = false;
1338 SetState(NORMAL, NORMAL);
1341 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1342 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1343 return true;
1344 return false;
1347 void DownloadItemView::UpdateAccessibleName() {
1348 base::string16 new_name;
1349 if (IsShowingWarningDialog()) {
1350 new_name = dangerous_download_label_->text();
1351 } else {
1352 new_name = status_text_ + base::char16(' ') +
1353 download()->GetFileNameToReportUser().LossyDisplayName();
1356 // If the name has changed, notify assistive technology that the name
1357 // has changed so they can announce it immediately.
1358 if (new_name != accessible_name_) {
1359 accessible_name_ = new_name;
1360 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true);
1364 void DownloadItemView::UpdateDropDownButtonPosition() {
1365 gfx::Size size = GetPreferredSize();
1366 if (base::i18n::IsRTL()) {
1367 // Drop down button is glued to the left of the download shelf.
1368 drop_down_x_left_ = 0;
1369 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
1370 } else {
1371 // Drop down button is glued to the right of the download shelf.
1372 drop_down_x_left_ =
1373 size.width() - normal_drop_down_image_set_.top->width();
1374 drop_down_x_right_ = size.width();
1378 void DownloadItemView::AnimateStateTransition(State from, State to,
1379 gfx::SlideAnimation* animation) {
1380 if (from == NORMAL && to == HOT) {
1381 animation->Show();
1382 } else if (from == HOT && to == NORMAL) {
1383 animation->Hide();
1384 } else if (from != to) {
1385 animation->Reset((to == HOT) ? 1.0 : 0.0);