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