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