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