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