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