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