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