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