• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 <vector>
8 
9 #include "base/callback.h"
10 #include "base/file_path.h"
11 #include "base/i18n/break_iterator.h"
12 #include "base/i18n/rtl.h"
13 #include "base/metrics/histogram.h"
14 #include "base/string_util.h"
15 #include "base/sys_string_conversions.h"
16 #include "base/utf_string_conversions.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/download/download_item_model.h"
19 #include "chrome/browser/download/download_util.h"
20 #include "chrome/browser/themes/theme_service.h"
21 #include "chrome/browser/ui/views/download/download_shelf_view.h"
22 #include "grit/generated_resources.h"
23 #include "grit/theme_resources.h"
24 #include "ui/base/accessibility/accessible_view_state.h"
25 #include "ui/base/animation/slide_animation.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/base/text/text_elider.h"
29 #include "ui/gfx/canvas_skia.h"
30 #include "ui/gfx/color_utils.h"
31 #include "ui/gfx/image.h"
32 #include "unicode/uchar.h"
33 #include "views/controls/button/native_button.h"
34 #include "views/controls/menu/menu_2.h"
35 #include "views/widget/root_view.h"
36 #include "views/widget/widget.h"
37 
38 using base::TimeDelta;
39 
40 // TODO(paulg): These may need to be adjusted when download progress
41 //              animation is added, and also possibly to take into account
42 //              different screen resolutions.
43 static const int kTextWidth = 140;            // Pixels
44 static const int kDangerousTextWidth = 200;   // Pixels
45 static const int kHorizontalTextPadding = 2;  // Pixels
46 static const int kVerticalPadding = 3;        // Pixels
47 static const int kVerticalTextSpacer = 2;     // Pixels
48 static const int kVerticalTextPadding = 2;    // Pixels
49 
50 // The maximum number of characters we show in a file name when displaying the
51 // dangerous download message.
52 static const int kFileNameMaxLength = 20;
53 
54 // We add some padding before the left image so that the progress animation icon
55 // hides the corners of the left image.
56 static const int kLeftPadding = 0;  // Pixels.
57 
58 // The space between the Save and Discard buttons when prompting for a dangerous
59 // download.
60 static const int kButtonPadding = 5;  // Pixels.
61 
62 // The space on the left and right side of the dangerous download label.
63 static const int kLabelPadding = 4;  // Pixels.
64 
65 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
66 
67 // How long the 'download complete' animation should last for.
68 static const int kCompleteAnimationDurationMs = 2500;
69 
70 // How long the 'download interrupted' animation should last for.
71 static const int kInterruptedAnimationDurationMs = 2500;
72 
73 // How long we keep the item disabled after the user clicked it to open the
74 // downloaded item.
75 static const int kDisabledOnOpenDuration = 3000;
76 
77 // Darken light-on-dark download status text by 20% before drawing, thus
78 // creating a "muted" version of title text for both dark-on-light and
79 // light-on-dark themes.
80 static const double kDownloadItemLuminanceMod = 0.8;
81 
82 // DownloadShelfContextMenuWin -------------------------------------------------
83 
84 class DownloadShelfContextMenuWin : public DownloadShelfContextMenu {
85  public:
DownloadShelfContextMenuWin(BaseDownloadItemModel * model)86   explicit DownloadShelfContextMenuWin(BaseDownloadItemModel* model)
87       : DownloadShelfContextMenu(model) {
88     DCHECK(model);
89   }
90 
Run(const gfx::Point & point)91   void Run(const gfx::Point& point) {
92     if (download_->IsComplete())
93       menu_.reset(new views::Menu2(GetFinishedMenuModel()));
94     else
95       menu_.reset(new views::Menu2(GetInProgressMenuModel()));
96 
97     // The menu's alignment is determined based on the UI layout.
98     views::Menu2::Alignment alignment;
99     if (base::i18n::IsRTL())
100       alignment = views::Menu2::ALIGN_TOPRIGHT;
101     else
102       alignment = views::Menu2::ALIGN_TOPLEFT;
103     menu_->RunMenuAt(point, alignment);
104   }
105 
106   // This method runs when the caller has been deleted and we should not attempt
107   // to access |download_|.
Stop()108   void Stop() {
109     download_ = NULL;
110   }
111 
112  private:
113   scoped_ptr<views::Menu2> menu_;
114 };
115 
116 // DownloadItemView ------------------------------------------------------------
117 
DownloadItemView(DownloadItem * download,DownloadShelfView * parent,BaseDownloadItemModel * model)118 DownloadItemView::DownloadItemView(DownloadItem* download,
119     DownloadShelfView* parent,
120     BaseDownloadItemModel* model)
121   : warning_icon_(NULL),
122     download_(download),
123     parent_(parent),
124     status_text_(UTF16ToWide(
125         l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING))),
126     show_status_text_(true),
127     body_state_(NORMAL),
128     drop_down_state_(NORMAL),
129     progress_angle_(download_util::kStartAngleDegrees),
130     drop_down_pressed_(false),
131     dragging_(false),
132     starting_drag_(false),
133     model_(model),
134     save_button_(NULL),
135     discard_button_(NULL),
136     dangerous_download_label_(NULL),
137     dangerous_download_label_sized_(false),
138     disabled_while_opening_(false),
139     creation_time_(base::Time::Now()),
140     ALLOW_THIS_IN_INITIALIZER_LIST(reenable_method_factory_(this)),
141     deleted_(NULL) {
142   DCHECK(download_);
143   download_->AddObserver(this);
144 
145   ResourceBundle &rb = ResourceBundle::GetSharedInstance();
146 
147   BodyImageSet normal_body_image_set = {
148     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
149     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
150     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
151     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
152     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
153     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
154     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
155     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
156     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
157   };
158   normal_body_image_set_ = normal_body_image_set;
159 
160   DropDownImageSet normal_drop_down_image_set = {
161     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
162     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
163     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
164   };
165   normal_drop_down_image_set_ = normal_drop_down_image_set;
166 
167   BodyImageSet hot_body_image_set = {
168     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
169     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
170     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
171     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
172     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
173     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
174     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
175     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
176     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
177   };
178   hot_body_image_set_ = hot_body_image_set;
179 
180   DropDownImageSet hot_drop_down_image_set = {
181     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
182     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
183     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
184   };
185   hot_drop_down_image_set_ = hot_drop_down_image_set;
186 
187   BodyImageSet pushed_body_image_set = {
188     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
189     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
190     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
191     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
192     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
193     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
194     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
195     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
196     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
197   };
198   pushed_body_image_set_ = pushed_body_image_set;
199 
200   DropDownImageSet pushed_drop_down_image_set = {
201     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
202     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
203     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
204   };
205   pushed_drop_down_image_set_ = pushed_drop_down_image_set;
206 
207   BodyImageSet dangerous_mode_body_image_set = {
208     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
209     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
210     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
211     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
212     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
213     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
214     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
215     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
216     rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
217   };
218   dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
219 
220   LoadIcon();
221   tooltip_text_ =
222       UTF16ToWide(download_->GetFileNameToReportUser().LossyDisplayName());
223 
224   font_ = ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
225   box_height_ = std::max<int>(2 * kVerticalPadding + font_.GetHeight() +
226                                   kVerticalTextPadding + font_.GetHeight(),
227                               2 * kVerticalPadding +
228                                   normal_body_image_set_.top_left->height() +
229                                   normal_body_image_set_.bottom_left->height());
230 
231   if (download_util::kSmallProgressIconSize > box_height_)
232     box_y_ = (download_util::kSmallProgressIconSize - box_height_) / 2;
233   else
234     box_y_ = kVerticalPadding;
235 
236   gfx::Size size = GetPreferredSize();
237   if (base::i18n::IsRTL()) {
238     // Drop down button is glued to the left of the download shelf.
239     drop_down_x_left_ = 0;
240     drop_down_x_right_ = normal_drop_down_image_set_.top->width();
241   } else {
242     // Drop down button is glued to the right of the download shelf.
243     drop_down_x_left_ =
244         size.width() - normal_drop_down_image_set_.top->width();
245     drop_down_x_right_ = size.width();
246   }
247 
248   body_hover_animation_.reset(new ui::SlideAnimation(this));
249   drop_hover_animation_.reset(new ui::SlideAnimation(this));
250 
251   if (download->safety_state() == DownloadItem::DANGEROUS) {
252     tooltip_text_.clear();
253     body_state_ = DANGEROUS;
254     drop_down_state_ = DANGEROUS;
255     save_button_ = new views::NativeButton(this,
256         UTF16ToWide(l10n_util::GetStringUTF16(
257             download->is_extension_install() ?
258                 IDS_CONTINUE_EXTENSION_DOWNLOAD : IDS_SAVE_DOWNLOAD)));
259     save_button_->set_ignore_minimum_size(true);
260     discard_button_ = new views::NativeButton(
261         this, UTF16ToWide(l10n_util::GetStringUTF16(IDS_DISCARD_DOWNLOAD)));
262     discard_button_->set_ignore_minimum_size(true);
263     AddChildView(save_button_);
264     AddChildView(discard_button_);
265 
266     // Ensure the file name is not too long.
267 
268     // Extract the file extension (if any).
269     FilePath filename(download->target_name());
270 #if defined(OS_LINUX)
271     string16 extension = WideToUTF16(base::SysNativeMBToWide(
272         filename.Extension()));
273 #else
274     string16 extension = filename.Extension();
275 #endif
276 
277     // Remove leading '.'
278     if (extension.length() > 0)
279       extension = extension.substr(1);
280 #if defined(OS_LINUX)
281     string16 rootname = WideToUTF16(base::SysNativeMBToWide(
282         filename.RemoveExtension().value()));
283 #else
284     string16 rootname = filename.RemoveExtension().value();
285 #endif
286 
287     // Elide giant extensions (this shouldn't currently be hit, but might
288     // in future, should we ever notice unsafe giant extensions).
289     if (extension.length() > kFileNameMaxLength / 2)
290       ui::ElideString(extension, kFileNameMaxLength / 2, &extension);
291 
292     // The dangerous download label text and icon are different
293     // under different cases.
294     string16 dangerous_label;
295     if (download->danger_type() == DownloadItem::DANGEROUS_URL) {
296       // Safebrowsing shows the download URL leads to malicious file.
297       warning_icon_ = rb.GetBitmapNamed(IDR_SAFEBROWSING_WARNING);
298       dangerous_label =
299           l10n_util::GetStringUTF16(IDS_PROMPT_UNSAFE_DOWNLOAD_URL);
300     } else {
301       // The download file has dangerous file type (e.g.: an executable).
302       DCHECK(download->danger_type() == DownloadItem::DANGEROUS_FILE);
303       warning_icon_ = rb.GetBitmapNamed(IDR_WARNING);
304       if (download->is_extension_install()) {
305         dangerous_label =
306             l10n_util::GetStringUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
307       } else {
308         ui::ElideString(rootname,
309                         kFileNameMaxLength - extension.length(),
310                         &rootname);
311         string16 filename = rootname + ASCIIToUTF16(".") + extension;
312         filename = base::i18n::GetDisplayStringInLTRDirectionality(filename);
313         dangerous_label =
314             l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD, filename);
315       }
316     }
317 
318     dangerous_download_label_ = new views::Label(UTF16ToWide(dangerous_label));
319     dangerous_download_label_->SetMultiLine(true);
320     dangerous_download_label_->SetHorizontalAlignment(
321         views::Label::ALIGN_LEFT);
322     AddChildView(dangerous_download_label_);
323     SizeLabelToMinWidth();
324   }
325 
326   UpdateAccessibleName();
327   set_accessibility_focusable(true);
328 
329   // Set up our animation.
330   StartDownloadProgress();
331 }
332 
~DownloadItemView()333 DownloadItemView::~DownloadItemView() {
334   if (context_menu_.get()) {
335     context_menu_->Stop();
336   }
337   icon_consumer_.CancelAllRequests();
338   StopDownloadProgress();
339   download_->RemoveObserver(this);
340   if (deleted_)
341     *deleted_ = true;
342 }
343 
344 // Progress animation handlers.
345 
UpdateDownloadProgress()346 void DownloadItemView::UpdateDownloadProgress() {
347   progress_angle_ = (progress_angle_ +
348                      download_util::kUnknownIncrementDegrees) %
349                     download_util::kMaxDegrees;
350   SchedulePaint();
351 }
352 
StartDownloadProgress()353 void DownloadItemView::StartDownloadProgress() {
354   if (progress_timer_.IsRunning())
355     return;
356   progress_timer_.Start(
357       TimeDelta::FromMilliseconds(download_util::kProgressRateMs), this,
358       &DownloadItemView::UpdateDownloadProgress);
359 }
360 
StopDownloadProgress()361 void DownloadItemView::StopDownloadProgress() {
362   progress_timer_.Stop();
363 }
364 
OnExtractIconComplete(IconManager::Handle handle,gfx::Image * icon_bitmap)365 void DownloadItemView::OnExtractIconComplete(IconManager::Handle handle,
366                                              gfx::Image* icon_bitmap) {
367   if (icon_bitmap)
368     parent()->SchedulePaint();
369 }
370 
371 // DownloadObserver interface.
372 
373 // Update the progress graphic on the icon and our text status label
374 // to reflect our current bytes downloaded, time remaining.
OnDownloadUpdated(DownloadItem * download)375 void DownloadItemView::OnDownloadUpdated(DownloadItem* download) {
376   DCHECK(download == download_);
377 
378   if (body_state_ == DANGEROUS &&
379       download->safety_state() == DownloadItem::DANGEROUS_BUT_VALIDATED) {
380     // We have been approved.
381     ClearDangerousMode();
382   }
383 
384   string16 status_text = model_->GetStatusText();
385   switch (download_->state()) {
386     case DownloadItem::IN_PROGRESS:
387       download_->is_paused() ? StopDownloadProgress() : StartDownloadProgress();
388       break;
389     case DownloadItem::INTERRUPTED:
390       StopDownloadProgress();
391       complete_animation_.reset(new ui::SlideAnimation(this));
392       complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
393       complete_animation_->SetTweenType(ui::Tween::LINEAR);
394       complete_animation_->Show();
395       if (status_text.empty())
396         show_status_text_ = false;
397       SchedulePaint();
398       LoadIcon();
399       break;
400     case DownloadItem::COMPLETE:
401       if (download_->auto_opened()) {
402         parent_->RemoveDownloadView(this);  // This will delete us!
403         return;
404       }
405       StopDownloadProgress();
406       complete_animation_.reset(new ui::SlideAnimation(this));
407       complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
408       complete_animation_->SetTweenType(ui::Tween::LINEAR);
409       complete_animation_->Show();
410       if (status_text.empty())
411         show_status_text_ = false;
412       SchedulePaint();
413       LoadIcon();
414       break;
415     case DownloadItem::CANCELLED:
416       StopDownloadProgress();
417       LoadIcon();
418       break;
419     case DownloadItem::REMOVING:
420       parent_->RemoveDownloadView(this);  // This will delete us!
421       return;
422     default:
423       NOTREACHED();
424   }
425 
426   status_text_ = UTF16ToWideHack(status_text);
427   UpdateAccessibleName();
428 
429   // We use the parent's (DownloadShelfView's) SchedulePaint, since there
430   // are spaces between each DownloadItemView that the parent is responsible
431   // for painting.
432   parent()->SchedulePaint();
433 }
434 
OnDownloadOpened(DownloadItem * download)435 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
436   disabled_while_opening_ = true;
437   SetEnabled(false);
438   MessageLoop::current()->PostDelayedTask(
439       FROM_HERE,
440       reenable_method_factory_.NewRunnableMethod(&DownloadItemView::Reenable),
441       kDisabledOnOpenDuration);
442 
443   // Notify our parent.
444   parent_->OpenedDownload(this);
445 }
446 
447 // View overrides
448 
449 // In dangerous mode we have to layout our buttons.
Layout()450 void DownloadItemView::Layout() {
451   if (IsDangerousMode()) {
452     dangerous_download_label_->SetColor(
453       GetThemeProvider()->GetColor(ThemeService::COLOR_BOOKMARK_TEXT));
454 
455     int x = kLeftPadding + dangerous_mode_body_image_set_.top_left->width() +
456       warning_icon_->width() + kLabelPadding;
457     int y = (height() - dangerous_download_label_->height()) / 2;
458     dangerous_download_label_->SetBounds(x, y,
459                                          dangerous_download_label_->width(),
460                                          dangerous_download_label_->height());
461     gfx::Size button_size = GetButtonSize();
462     x += dangerous_download_label_->width() + kLabelPadding;
463     y = (height() - button_size.height()) / 2;
464     save_button_->SetBounds(x, y, button_size.width(), button_size.height());
465     x += button_size.width() + kButtonPadding;
466     discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
467   }
468 }
469 
GetPreferredSize()470 gfx::Size DownloadItemView::GetPreferredSize() {
471   int width, height;
472 
473   // First, we set the height to the height of two rows or text plus margins.
474   height = 2 * kVerticalPadding + 2 * font_.GetHeight() + kVerticalTextPadding;
475   // Then we increase the size if the progress icon doesn't fit.
476   height = std::max<int>(height, download_util::kSmallProgressIconSize);
477 
478   if (IsDangerousMode()) {
479     width = kLeftPadding + dangerous_mode_body_image_set_.top_left->width();
480     width += warning_icon_->width() + kLabelPadding;
481     width += dangerous_download_label_->width() + kLabelPadding;
482     gfx::Size button_size = GetButtonSize();
483     // Make sure the button fits.
484     height = std::max<int>(height, 2 * kVerticalPadding + button_size.height());
485     // Then we make sure the warning icon fits.
486     height = std::max<int>(height, 2 * kVerticalPadding +
487                                    warning_icon_->height());
488     width += button_size.width() * 2 + kButtonPadding;
489     width += dangerous_mode_body_image_set_.top_right->width();
490   } else {
491     width = kLeftPadding + normal_body_image_set_.top_left->width();
492     width += download_util::kSmallProgressIconSize;
493     width += kTextWidth;
494     width += normal_body_image_set_.top_right->width();
495     width += normal_drop_down_image_set_.top->width();
496   }
497   return gfx::Size(width, height);
498 }
499 
500 // Handle a mouse click and open the context menu if the mouse is
501 // over the drop-down region.
OnMousePressed(const views::MouseEvent & event)502 bool DownloadItemView::OnMousePressed(const views::MouseEvent& event) {
503   // Mouse should not activate us in dangerous mode.
504   if (IsDangerousMode())
505     return true;
506 
507   // Stop any completion animation.
508   if (complete_animation_.get() && complete_animation_->is_animating())
509     complete_animation_->End();
510 
511   gfx::Point menu_location(event.location());
512   if (event.IsOnlyLeftMouseButton()) {
513     if (!InDropDownButtonXCoordinateRange(event.x())) {
514       SetState(PUSHED, NORMAL);
515       return true;
516     }
517 
518     // Anchor the menu below the dropmarker.
519     menu_location.SetPoint(base::i18n::IsRTL() ?
520                                drop_down_x_right_ : drop_down_x_left_,
521                            height());
522     drop_down_pressed_ = true;
523     SetState(NORMAL, PUSHED);
524   }
525   ShowContextMenu(menu_location, true);
526   return true;
527 }
528 
529 // Handle drag (file copy) operations.
OnMouseDragged(const views::MouseEvent & event)530 bool DownloadItemView::OnMouseDragged(const views::MouseEvent& event) {
531   // Mouse should not activate us in dangerous mode.
532   if (IsDangerousMode())
533     return true;
534 
535   if (!starting_drag_) {
536     starting_drag_ = true;
537     drag_start_point_ = event.location();
538   }
539   if (dragging_) {
540     if (download_->IsComplete()) {
541       IconManager* im = g_browser_process->icon_manager();
542       gfx::Image* icon = im->LookupIcon(download_->GetUserVerifiedFilePath(),
543                                         IconLoader::SMALL);
544       if (icon) {
545         views::Widget* widget = GetWidget();
546         download_util::DragDownload(download_, icon,
547                                     widget ? widget->GetNativeView() : NULL);
548       }
549     }
550   } else if (ExceededDragThreshold(
551                  event.location().x() - drag_start_point_.x(),
552                  event.location().y() - drag_start_point_.y())) {
553     dragging_ = true;
554   }
555   return true;
556 }
557 
OnMouseReleased(const views::MouseEvent & event)558 void DownloadItemView::OnMouseReleased(const views::MouseEvent& event) {
559   // Mouse should not activate us in dangerous mode.
560   if (IsDangerousMode())
561     return;
562 
563   if (event.IsOnlyLeftMouseButton() &&
564       !InDropDownButtonXCoordinateRange(event.x())) {
565     OpenDownload();
566   }
567 
568   SetState(NORMAL, NORMAL);
569 }
570 
OnMouseCaptureLost()571 void DownloadItemView::OnMouseCaptureLost() {
572   // Mouse should not activate us in dangerous mode.
573   if (IsDangerousMode())
574     return;
575 
576   if (dragging_) {
577     // Starting a drag results in a MouseCaptureLost.
578     dragging_ = false;
579     starting_drag_ = false;
580   } else {
581     SetState(NORMAL, NORMAL);
582   }
583 }
584 
OnMouseMoved(const views::MouseEvent & event)585 void DownloadItemView::OnMouseMoved(const views::MouseEvent& event) {
586   // Mouse should not activate us in dangerous mode.
587   if (IsDangerousMode())
588     return;
589 
590   bool on_body = !InDropDownButtonXCoordinateRange(event.x());
591   SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
592   if (on_body) {
593     body_hover_animation_->Show();
594     drop_hover_animation_->Hide();
595   } else {
596     body_hover_animation_->Hide();
597     drop_hover_animation_->Show();
598   }
599 }
600 
OnMouseExited(const views::MouseEvent & event)601 void DownloadItemView::OnMouseExited(const views::MouseEvent& event) {
602   // Mouse should not activate us in dangerous mode.
603   if (IsDangerousMode())
604     return;
605 
606   SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
607   body_hover_animation_->Hide();
608   drop_hover_animation_->Hide();
609 }
610 
OnKeyPressed(const views::KeyEvent & event)611 bool DownloadItemView::OnKeyPressed(const views::KeyEvent& event) {
612   // Key press should not activate us in dangerous mode.
613   if (IsDangerousMode())
614     return true;
615 
616   if (event.key_code() == ui::VKEY_SPACE ||
617       event.key_code() == ui::VKEY_RETURN) {
618     OpenDownload();
619     return true;
620   }
621   return false;
622 }
623 
GetTooltipText(const gfx::Point & p,std::wstring * tooltip)624 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
625                                       std::wstring* tooltip) {
626   if (tooltip_text_.empty())
627     return false;
628 
629   tooltip->assign(tooltip_text_);
630   return true;
631 }
632 
ShowContextMenu(const gfx::Point & p,bool is_mouse_gesture)633 void DownloadItemView::ShowContextMenu(const gfx::Point& p,
634                                        bool is_mouse_gesture) {
635   gfx::Point point = p;
636 
637   // Similar hack as in MenuButton.
638   // We're about to show the menu from a mouse press. By showing from the
639   // mouse press event we block RootView in mouse dispatching. This also
640   // appears to cause RootView to get a mouse pressed BEFORE the mouse
641   // release is seen, which means RootView sends us another mouse press no
642   // matter where the user pressed. To force RootView to recalculate the
643   // mouse target during the mouse press we explicitly set the mouse handler
644   // to NULL.
645   GetRootView()->SetMouseHandler(NULL);
646 
647   // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
648   // to drop down arrow button.
649   if (!is_mouse_gesture) {
650     drop_down_pressed_ = true;
651     SetState(NORMAL, PUSHED);
652 
653     point.set_y(height());
654     if (base::i18n::IsRTL())
655       point.set_x(drop_down_x_right_);
656     else
657       point.set_x(drop_down_x_left_);
658   }
659 
660   views::View::ConvertPointToScreen(this, &point);
661 
662   if (!context_menu_.get())
663     context_menu_.reset(new DownloadShelfContextMenuWin(model_.get()));
664   // When we call the Run method on the menu, it runs an inner message loop
665   // that might causes us to be deleted.
666   bool deleted = false;
667   deleted_ = &deleted;
668   context_menu_->Run(point);
669   if (deleted)
670     return;  // We have been deleted! Don't access 'this'.
671   deleted_ = NULL;
672 
673   // If the menu action was to remove the download, this view will also be
674   // invalid so we must not access 'this' in this case.
675   if (context_menu_->download()) {
676     drop_down_pressed_ = false;
677     // Showing the menu blocks. Here we revert the state.
678     SetState(NORMAL, NORMAL);
679   }
680 }
681 
GetAccessibleState(ui::AccessibleViewState * state)682 void DownloadItemView::GetAccessibleState(ui::AccessibleViewState* state) {
683   state->name = accessible_name_;
684   state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
685   if (download_->safety_state() == DownloadItem::DANGEROUS) {
686     state->state = ui::AccessibilityTypes::STATE_UNAVAILABLE;
687   } else {
688     state->state = ui::AccessibilityTypes::STATE_HASPOPUP;
689   }
690 }
691 
ButtonPressed(views::Button * sender,const views::Event & event)692 void DownloadItemView::ButtonPressed(
693     views::Button* sender, const views::Event& event) {
694   if (sender == discard_button_) {
695     UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download",
696                              base::Time::Now() - creation_time_);
697     if (download_->IsPartialDownload())
698       download_->Cancel(true);
699     download_->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD);
700     // WARNING: we are deleted at this point.  Don't access 'this'.
701   } else if (sender == save_button_) {
702     // The user has confirmed a dangerous download.  We'd record how quickly the
703     // user did this to detect whether we're being clickjacked.
704     UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
705                              base::Time::Now() - creation_time_);
706     // This will change the state and notify us.
707     download_->DangerousDownloadValidated();
708   }
709 }
710 
AnimationProgressed(const ui::Animation * animation)711 void DownloadItemView::AnimationProgressed(const ui::Animation* animation) {
712   // We don't care if what animation (body button/drop button/complete),
713   // is calling back, as they all have to go through the same paint call.
714   SchedulePaint();
715 }
716 
OnPaint(gfx::Canvas * canvas)717 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
718   BodyImageSet* body_image_set = NULL;
719   switch (body_state_) {
720     case NORMAL:
721     case HOT:
722       body_image_set = &normal_body_image_set_;
723       break;
724     case PUSHED:
725       body_image_set = &pushed_body_image_set_;
726       break;
727     case DANGEROUS:
728       body_image_set = &dangerous_mode_body_image_set_;
729       break;
730     default:
731       NOTREACHED();
732   }
733   DropDownImageSet* drop_down_image_set = NULL;
734   switch (drop_down_state_) {
735     case NORMAL:
736     case HOT:
737       drop_down_image_set = &normal_drop_down_image_set_;
738       break;
739     case PUSHED:
740       drop_down_image_set = &pushed_drop_down_image_set_;
741       break;
742     case DANGEROUS:
743       drop_down_image_set = NULL;  // No drop-down in dangerous mode.
744       break;
745     default:
746       NOTREACHED();
747   }
748 
749   int center_width = width() - kLeftPadding -
750                      body_image_set->left->width() -
751                      body_image_set->right->width() -
752                      (drop_down_image_set ?
753                         normal_drop_down_image_set_.center->width() :
754                         0);
755 
756   // May be caused by animation.
757   if (center_width <= 0)
758     return;
759 
760   // Draw status before button image to effectively lighten text.
761   if (!IsDangerousMode()) {
762     if (show_status_text_) {
763       int mirrored_x = GetMirroredXWithWidthInView(
764           download_util::kSmallProgressIconSize, kTextWidth);
765       // Add font_.height() to compensate for title, which is drawn later.
766       int y = box_y_ + kVerticalPadding + font_.GetHeight() +
767               kVerticalTextPadding;
768       SkColor file_name_color = GetThemeProvider()->GetColor(
769           ThemeService::COLOR_BOOKMARK_TEXT);
770       // If text is light-on-dark, lightening it alone will do nothing.
771       // Therefore we mute luminance a wee bit before drawing in this case.
772       if (color_utils::RelativeLuminance(file_name_color) > 0.5)
773           file_name_color = SkColorSetRGB(
774               static_cast<int>(kDownloadItemLuminanceMod *
775                                SkColorGetR(file_name_color)),
776               static_cast<int>(kDownloadItemLuminanceMod *
777                                SkColorGetG(file_name_color)),
778               static_cast<int>(kDownloadItemLuminanceMod *
779                                SkColorGetB(file_name_color)));
780       canvas->DrawStringInt(WideToUTF16Hack(status_text_), font_,
781                             file_name_color, mirrored_x, y, kTextWidth,
782                             font_.GetHeight());
783     }
784   }
785 
786   // Paint the background images.
787   int x = kLeftPadding;
788   canvas->Save();
789   if (base::i18n::IsRTL()) {
790     // Since we do not have the mirrored images for
791     // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
792     // (hot_)body_image_set->bottom_left, and drop_down_image_set,
793     // for RTL UI, we flip the canvas to draw those images mirrored.
794     // Consequently, we do not need to mirror the x-axis of those images.
795     canvas->TranslateInt(width(), 0);
796     canvas->ScaleInt(-1, 1);
797   }
798   PaintBitmaps(canvas,
799                body_image_set->top_left, body_image_set->left,
800                body_image_set->bottom_left,
801                x, box_y_, box_height_, body_image_set->top_left->width());
802   x += body_image_set->top_left->width();
803   PaintBitmaps(canvas,
804                body_image_set->top, body_image_set->center,
805                body_image_set->bottom,
806                x, box_y_, box_height_, center_width);
807   x += center_width;
808   PaintBitmaps(canvas,
809                body_image_set->top_right, body_image_set->right,
810                body_image_set->bottom_right,
811                x, box_y_, box_height_, body_image_set->top_right->width());
812 
813   // Overlay our body hot state.
814   if (body_hover_animation_->GetCurrentValue() > 0) {
815     canvas->SaveLayerAlpha(
816         static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
817     canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode);
818 
819     int x = kLeftPadding;
820     PaintBitmaps(canvas,
821                  hot_body_image_set_.top_left, hot_body_image_set_.left,
822                  hot_body_image_set_.bottom_left,
823                  x, box_y_, box_height_, hot_body_image_set_.top_left->width());
824     x += body_image_set->top_left->width();
825     PaintBitmaps(canvas,
826                  hot_body_image_set_.top, hot_body_image_set_.center,
827                  hot_body_image_set_.bottom,
828                  x, box_y_, box_height_, center_width);
829     x += center_width;
830     PaintBitmaps(canvas,
831                  hot_body_image_set_.top_right, hot_body_image_set_.right,
832                  hot_body_image_set_.bottom_right,
833                  x, box_y_, box_height_,
834                  hot_body_image_set_.top_right->width());
835     canvas->Restore();
836   }
837 
838   x += body_image_set->top_right->width();
839 
840   // Paint the drop-down.
841   if (drop_down_image_set) {
842     PaintBitmaps(canvas,
843                  drop_down_image_set->top, drop_down_image_set->center,
844                  drop_down_image_set->bottom,
845                  x, box_y_, box_height_, drop_down_image_set->top->width());
846 
847     // Overlay our drop-down hot state.
848     if (drop_hover_animation_->GetCurrentValue() > 0) {
849       canvas->SaveLayerAlpha(
850           static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
851       canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255,
852                                        SkXfermode::kClear_Mode);
853 
854       PaintBitmaps(canvas,
855                    drop_down_image_set->top, drop_down_image_set->center,
856                    drop_down_image_set->bottom,
857                    x, box_y_, box_height_, drop_down_image_set->top->width());
858 
859       canvas->Restore();
860     }
861   }
862 
863   // Restore the canvas to avoid file name etc. text are drawn flipped.
864   // Consequently, the x-axis of following canvas->DrawXXX() method should be
865   // mirrored so the text and images are down in the right positions.
866   canvas->Restore();
867 
868   // Print the text, left aligned and always print the file extension.
869   // Last value of x was the end of the right image, just before the button.
870   // Note that in dangerous mode we use a label (as the text is multi-line).
871   if (!IsDangerousMode()) {
872     string16 filename;
873     if (!disabled_while_opening_) {
874       filename = ui::ElideFilename(download_->GetFileNameToReportUser(),
875                                    font_, kTextWidth);
876     } else {
877       // First, Calculate the download status opening string width.
878       string16 status_string =
879           l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, string16());
880       int status_string_width = font_.GetStringWidth(status_string);
881       // Then, elide the file name.
882       string16 filename_string =
883           ui::ElideFilename(download_->GetFileNameToReportUser(), font_,
884                             kTextWidth - status_string_width);
885       // Last, concat the whole string.
886       filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
887                                             filename_string);
888     }
889 
890     int mirrored_x = GetMirroredXWithWidthInView(
891         download_util::kSmallProgressIconSize, kTextWidth);
892     SkColor file_name_color = GetThemeProvider()->GetColor(
893         ThemeService::COLOR_BOOKMARK_TEXT);
894     int y =
895         box_y_ + (show_status_text_ ? kVerticalPadding :
896                                       (box_height_ - font_.GetHeight()) / 2);
897 
898     // Draw the file's name.
899     canvas->DrawStringInt(filename, font_,
900                           IsEnabled() ? file_name_color :
901                                         kFileNameDisabledColor,
902                           mirrored_x, y, kTextWidth, font_.GetHeight());
903   }
904 
905   // Load the icon.
906   IconManager* im = g_browser_process->icon_manager();
907   gfx::Image* image = im->LookupIcon(download_->GetUserVerifiedFilePath(),
908                                      IconLoader::SMALL);
909   const SkBitmap* icon = NULL;
910   if (IsDangerousMode())
911     icon = warning_icon_;
912   else if (image)
913     icon = *image;
914 
915   // We count on the fact that the icon manager will cache the icons and if one
916   // is available, it will be cached here. We *don't* want to request the icon
917   // to be loaded here, since this will also get called if the icon can't be
918   // loaded, in which case LookupIcon will always be NULL. The loading will be
919   // triggered only when we think the status might change.
920   if (icon) {
921     if (!IsDangerousMode()) {
922       if (download_->IsInProgress()) {
923         download_util::PaintDownloadProgress(canvas, this, 0, 0,
924                                              progress_angle_,
925                                              download_->PercentComplete(),
926                                              download_util::SMALL);
927       } else if (download_->IsComplete() &&
928                  complete_animation_.get() &&
929                  complete_animation_->is_animating()) {
930         if (download_->IsInterrupted()) {
931           download_util::PaintDownloadInterrupted(canvas, this, 0, 0,
932               complete_animation_->GetCurrentValue(),
933               download_util::SMALL);
934         } else {
935           download_util::PaintDownloadComplete(canvas, this, 0, 0,
936               complete_animation_->GetCurrentValue(),
937               download_util::SMALL);
938         }
939       }
940     }
941 
942     // Draw the icon image.
943     int mirrored_x = GetMirroredXWithWidthInView(
944         download_util::kSmallProgressIconOffset, icon->width());
945     if (IsEnabled()) {
946       canvas->DrawBitmapInt(*icon, mirrored_x,
947                             download_util::kSmallProgressIconOffset);
948     } else {
949       // Use an alpha to make the image look disabled.
950       SkPaint paint;
951       paint.setAlpha(120);
952       canvas->DrawBitmapInt(*icon, mirrored_x,
953                             download_util::kSmallProgressIconOffset, paint);
954     }
955   }
956 }
957 
OpenDownload()958 void DownloadItemView::OpenDownload() {
959   // We're interested in how long it takes users to open downloads.  If they
960   // open downloads super quickly, we should be concerned about clickjacking.
961   UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
962                            base::Time::Now() - creation_time_);
963   download_->OpenDownload();
964   UpdateAccessibleName();
965 }
966 
LoadIcon()967 void DownloadItemView::LoadIcon() {
968   IconManager* im = g_browser_process->icon_manager();
969   im->LoadIcon(download_->GetUserVerifiedFilePath(),
970                IconLoader::SMALL, &icon_consumer_,
971                NewCallback(this, &DownloadItemView::OnExtractIconComplete));
972 }
973 
974 // Load an icon for the file type we're downloading, and animate any in progress
975 // download state.
PaintBitmaps(gfx::Canvas * canvas,const SkBitmap * top_bitmap,const SkBitmap * center_bitmap,const SkBitmap * bottom_bitmap,int x,int y,int height,int width)976 void DownloadItemView::PaintBitmaps(gfx::Canvas* canvas,
977                                     const SkBitmap* top_bitmap,
978                                     const SkBitmap* center_bitmap,
979                                     const SkBitmap* bottom_bitmap,
980                                     int x, int y, int height, int width) {
981   int middle_height = height - top_bitmap->height() - bottom_bitmap->height();
982   // Draw the top.
983   canvas->DrawBitmapInt(*top_bitmap,
984                         0, 0, top_bitmap->width(), top_bitmap->height(),
985                         x, y, width, top_bitmap->height(), false);
986   y += top_bitmap->height();
987   // Draw the center.
988   canvas->DrawBitmapInt(*center_bitmap,
989                         0, 0, center_bitmap->width(), center_bitmap->height(),
990                         x, y, width, middle_height, false);
991   y += middle_height;
992   // Draw the bottom.
993   canvas->DrawBitmapInt(*bottom_bitmap,
994                         0, 0, bottom_bitmap->width(), bottom_bitmap->height(),
995                         x, y, width, bottom_bitmap->height(), false);
996 }
997 
SetState(State body_state,State drop_down_state)998 void DownloadItemView::SetState(State body_state, State drop_down_state) {
999   if (body_state_ == body_state && drop_down_state_ == drop_down_state)
1000     return;
1001 
1002   body_state_ = body_state;
1003   drop_down_state_ = drop_down_state;
1004   SchedulePaint();
1005 }
1006 
ClearDangerousMode()1007 void DownloadItemView::ClearDangerousMode() {
1008   DCHECK(download_->safety_state() == DownloadItem::DANGEROUS_BUT_VALIDATED &&
1009          body_state_ == DANGEROUS && drop_down_state_ == DANGEROUS);
1010 
1011   body_state_ = NORMAL;
1012   drop_down_state_ = NORMAL;
1013 
1014   // Remove the views used by the dangerous mode.
1015   RemoveChildView(save_button_);
1016   delete save_button_;
1017   save_button_ = NULL;
1018   RemoveChildView(discard_button_);
1019   delete discard_button_;
1020   discard_button_ = NULL;
1021   RemoveChildView(dangerous_download_label_);
1022   delete dangerous_download_label_;
1023   dangerous_download_label_ = NULL;
1024 
1025   // Set the accessible name back to the status and filename instead of the
1026   // download warning.
1027   UpdateAccessibleName();
1028 
1029   // We need to load the icon now that the download_ has the real path.
1030   LoadIcon();
1031   tooltip_text_ =
1032       UTF16ToWide(download_->GetFileNameToReportUser().LossyDisplayName());
1033 
1034   // Force the shelf to layout again as our size has changed.
1035   parent_->Layout();
1036   parent_->SchedulePaint();
1037 }
1038 
GetButtonSize()1039 gfx::Size DownloadItemView::GetButtonSize() {
1040   DCHECK(save_button_ && discard_button_);
1041   gfx::Size size;
1042 
1043   // We cache the size when successfully retrieved, not for performance reasons
1044   // but because if this DownloadItemView is being animated while the tab is
1045   // not showing, the native buttons are not parented and their preferred size
1046   // is 0, messing-up the layout.
1047   if (cached_button_size_.width() != 0)
1048     return cached_button_size_;
1049 
1050   size = save_button_->GetMinimumSize();
1051   gfx::Size discard_size = discard_button_->GetMinimumSize();
1052 
1053   size.SetSize(std::max(size.width(), discard_size.width()),
1054                std::max(size.height(), discard_size.height()));
1055 
1056   if (size.width() != 0)
1057     cached_button_size_ = size;
1058 
1059   return size;
1060 }
1061 
1062 // This method computes the minimum width of the label for displaying its text
1063 // on 2 lines.  It just breaks the string in 2 lines on the spaces and keeps the
1064 // configuration with minimum width.
SizeLabelToMinWidth()1065 void DownloadItemView::SizeLabelToMinWidth() {
1066   if (dangerous_download_label_sized_)
1067     return;
1068 
1069   std::wstring text = dangerous_download_label_->GetText();
1070   TrimWhitespace(text, TRIM_ALL, &text);
1071   DCHECK_EQ(std::wstring::npos, text.find(L"\n"));
1072 
1073   // Make the label big so that GetPreferredSize() is not constrained by the
1074   // current width.
1075   dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1076 
1077   gfx::Size size;
1078   int min_width = -1;
1079   string16 text16 = WideToUTF16(text);
1080   // Using BREAK_WORD can work in most cases, but it can also break
1081   // lines where it should not. Using BREAK_LINE is safer although
1082   // slower for Chinese/Japanese. This is not perf-critical at all, though.
1083   base::BreakIterator iter(&text16, base::BreakIterator::BREAK_LINE);
1084   bool status = iter.Init();
1085   DCHECK(status);
1086 
1087   string16 current_text = text16;
1088   string16 prev_text = text16;
1089   while (iter.Advance()) {
1090     size_t pos = iter.pos();
1091     if (pos >= text16.length())
1092       break;
1093     // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1094     // return false and inserting a new line after a surrogate pair
1095     // is perfectly ok.
1096     char16 line_end_char = text16[pos - 1];
1097     if (u_isUWhiteSpace(line_end_char))
1098       current_text.replace(pos - 1, 1, 1, char16('\n'));
1099     else
1100       current_text.insert(pos, 1, char16('\n'));
1101     dangerous_download_label_->SetText(UTF16ToWide(current_text));
1102     size = dangerous_download_label_->GetPreferredSize();
1103 
1104     if (min_width == -1)
1105       min_width = size.width();
1106 
1107     // If the width is growing again, it means we passed the optimal width spot.
1108     if (size.width() > min_width) {
1109       dangerous_download_label_->SetText(UTF16ToWide(prev_text));
1110       break;
1111     } else {
1112       min_width = size.width();
1113     }
1114 
1115     // Restore the string.
1116     prev_text = current_text;
1117     current_text = text16;
1118   }
1119 
1120   // If we have a line with no line breaking opportunity (which is very
1121   // unlikely), we won't cut it.
1122   if (min_width == -1)
1123     size = dangerous_download_label_->GetPreferredSize();
1124 
1125   dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1126   dangerous_download_label_sized_ = true;
1127 }
1128 
Reenable()1129 void DownloadItemView::Reenable() {
1130   disabled_while_opening_ = false;
1131   SetEnabled(true);  // Triggers a repaint.
1132 }
1133 
InDropDownButtonXCoordinateRange(int x)1134 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1135   if (x > drop_down_x_left_ && x < drop_down_x_right_)
1136     return true;
1137   return false;
1138 }
1139 
UpdateAccessibleName()1140 void DownloadItemView::UpdateAccessibleName() {
1141   string16 new_name;
1142   if (download_->safety_state() == DownloadItem::DANGEROUS) {
1143     new_name = WideToUTF16Hack(dangerous_download_label_->GetText());
1144   } else {
1145     new_name = WideToUTF16Hack(status_text_) + char16(' ') +
1146         download_->GetFileNameToReportUser().LossyDisplayName();
1147   }
1148 
1149   // If the name has changed, notify assistive technology that the name
1150   // has changed so they can announce it immediately.
1151   if (new_name != accessible_name_) {
1152     accessible_name_ = new_name;
1153     if (GetWidget()) {
1154       GetWidget()->NotifyAccessibilityEvent(
1155           this, ui::AccessibilityTypes::EVENT_NAME_CHANGED, true);
1156     }
1157   }
1158 }
1159