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.
5 #include "chrome/browser/ui/views/download/download_item_view.h"
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/drag_download_item.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/l10n/l10n_util.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/base/theme_provider.h"
38 #include "ui/events/event.h"
39 #include "ui/gfx/animation/slide_animation.h"
40 #include "ui/gfx/canvas.h"
41 #include "ui/gfx/color_utils.h"
42 #include "ui/gfx/image/image.h"
43 #include "ui/gfx/text_elider.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"
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 kVerticalPadding = 3; // Pixels
55 static const int kVerticalTextPadding = 2; // Pixels
56 static const int kTooltipMaxWidth = 800; // Pixels
58 // We add some padding before the left image so that the progress animation icon
59 // hides the corners of the left image.
60 static const int kLeftPadding = 0; // Pixels.
62 // The space between the Save and Discard buttons when prompting for a dangerous
64 static const int kButtonPadding = 5; // Pixels.
66 // The space on the left and right side of the dangerous download label.
67 static const int kLabelPadding = 4; // Pixels.
69 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
71 // How long the 'download complete' animation should last for.
72 static const int kCompleteAnimationDurationMs = 2500;
74 // How long the 'download interrupted' animation should last for.
75 static const int kInterruptedAnimationDurationMs = 2500;
77 // How long we keep the item disabled after the user clicked it to open the
79 static const int kDisabledOnOpenDuration = 3000;
81 // Darken light-on-dark download status text by 20% before drawing, thus
82 // creating a "muted" version of title text for both dark-on-light and
83 // light-on-dark themes.
84 static const double kDownloadItemLuminanceMod = 0.8;
86 using content::DownloadItem;
88 DownloadItemView::DownloadItemView(DownloadItem* download_item,
89 DownloadShelfView* parent)
90 : warning_icon_(NULL),
92 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
94 drop_down_state_(NORMAL),
96 progress_angle_(DownloadShelf::kStartAngleDegrees),
97 drop_down_pressed_(false),
99 starting_drag_(false),
100 model_(download_item),
102 discard_button_(NULL),
103 dangerous_download_label_(NULL),
104 dangerous_download_label_sized_(false),
105 disabled_while_opening_(false),
106 creation_time_(base::Time::Now()),
107 time_download_warning_shown_(base::Time()),
108 weak_ptr_factory_(this) {
110 download()->AddObserver(this);
111 set_context_menu_controller(this);
113 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
115 BodyImageSet normal_body_image_set = {
116 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
117 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
118 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
119 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
120 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
121 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
122 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
123 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
124 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
126 normal_body_image_set_ = normal_body_image_set;
128 DropDownImageSet normal_drop_down_image_set = {
129 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
130 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
131 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
133 normal_drop_down_image_set_ = normal_drop_down_image_set;
135 BodyImageSet hot_body_image_set = {
136 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
137 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
138 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
139 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
140 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
141 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
142 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
143 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
144 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
146 hot_body_image_set_ = hot_body_image_set;
148 DropDownImageSet hot_drop_down_image_set = {
149 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
150 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
151 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
153 hot_drop_down_image_set_ = hot_drop_down_image_set;
155 BodyImageSet pushed_body_image_set = {
156 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
157 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
158 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
159 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
160 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
161 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
162 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
163 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
164 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
166 pushed_body_image_set_ = pushed_body_image_set;
168 DropDownImageSet pushed_drop_down_image_set = {
169 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
170 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
171 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
173 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
175 BodyImageSet dangerous_mode_body_image_set = {
176 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
177 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
178 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
179 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
180 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
181 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
182 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
183 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
184 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
186 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
188 malicious_mode_body_image_set_ = normal_body_image_set;
192 font_list_ = rb.GetFontList(ui::ResourceBundle::BaseFont);
193 box_height_ = std::max<int>(2 * kVerticalPadding + font_list_.GetHeight() +
194 kVerticalTextPadding + font_list_.GetHeight(),
195 2 * kVerticalPadding +
196 normal_body_image_set_.top_left->height() +
197 normal_body_image_set_.bottom_left->height());
199 if (DownloadShelf::kSmallProgressIconSize > box_height_)
200 box_y_ = (DownloadShelf::kSmallProgressIconSize - box_height_) / 2;
204 body_hover_animation_.reset(new gfx::SlideAnimation(this));
205 drop_hover_animation_.reset(new gfx::SlideAnimation(this));
207 set_accessibility_focusable(true);
209 OnDownloadUpdated(download());
210 UpdateDropDownButtonPosition();
213 DownloadItemView::~DownloadItemView() {
214 StopDownloadProgress();
215 download()->RemoveObserver(this);
218 // Progress animation handlers.
220 void DownloadItemView::UpdateDownloadProgress() {
222 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
223 DownloadShelf::kMaxDegrees;
227 void DownloadItemView::StartDownloadProgress() {
228 if (progress_timer_.IsRunning())
230 progress_timer_.Start(FROM_HERE,
231 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs), this,
232 &DownloadItemView::UpdateDownloadProgress);
235 void DownloadItemView::StopDownloadProgress() {
236 progress_timer_.Stop();
239 void DownloadItemView::OnExtractIconComplete(gfx::Image* icon_bitmap) {
241 shelf_->SchedulePaint();
244 // DownloadObserver interface.
246 // Update the progress graphic on the icon and our text status label
247 // to reflect our current bytes downloaded, time remaining.
248 void DownloadItemView::OnDownloadUpdated(DownloadItem* download_item) {
249 DCHECK_EQ(download(), download_item);
251 if (IsShowingWarningDialog() && !model_.IsDangerous()) {
252 // We have been approved.
253 ClearWarningDialog();
254 } else if (!IsShowingWarningDialog() && model_.IsDangerous()) {
256 // Force the shelf to layout again as our size has changed.
260 string16 status_text = model_.GetStatusText();
261 switch (download()->GetState()) {
262 case DownloadItem::IN_PROGRESS:
263 download()->IsPaused() ?
264 StopDownloadProgress() : StartDownloadProgress();
265 LoadIconIfItemPathChanged();
267 case DownloadItem::INTERRUPTED:
268 StopDownloadProgress();
269 complete_animation_.reset(new gfx::SlideAnimation(this));
270 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
271 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
272 complete_animation_->Show();
276 case DownloadItem::COMPLETE:
277 if (model_.ShouldRemoveFromShelfWhenComplete()) {
278 shelf_->RemoveDownloadView(this); // This will delete us!
281 StopDownloadProgress();
282 complete_animation_.reset(new gfx::SlideAnimation(this));
283 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
284 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
285 complete_animation_->Show();
289 case DownloadItem::CANCELLED:
290 StopDownloadProgress();
291 if (complete_animation_)
292 complete_animation_->Stop();
298 status_text_ = status_text;
301 string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth);
302 if (new_tip != tooltip_text_) {
303 tooltip_text_ = new_tip;
304 TooltipTextChanged();
307 UpdateAccessibleName();
309 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
310 // are spaces between each DownloadItemView that the parent is responsible
312 shelf_->SchedulePaint();
315 void DownloadItemView::OnDownloadDestroyed(DownloadItem* download) {
316 shelf_->RemoveDownloadView(this); // This will delete us!
319 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
320 disabled_while_opening_ = true;
322 base::MessageLoop::current()->PostDelayedTask(
324 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
325 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
327 // Notify our parent.
328 shelf_->OpenedDownload(this);
333 // In dangerous mode we have to layout our buttons.
334 void DownloadItemView::Layout() {
335 if (IsShowingWarningDialog()) {
336 BodyImageSet* body_image_set =
337 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
338 &malicious_mode_body_image_set_;
339 int x = kLeftPadding + body_image_set->top_left->width() +
340 warning_icon_->width() + kLabelPadding;
341 int y = (height() - dangerous_download_label_->height()) / 2;
342 dangerous_download_label_->SetBounds(x, y,
343 dangerous_download_label_->width(),
344 dangerous_download_label_->height());
345 gfx::Size button_size = GetButtonSize();
346 x += dangerous_download_label_->width() + kLabelPadding;
347 y = (height() - button_size.height()) / 2;
349 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
350 x += button_size.width() + kButtonPadding;
352 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
353 UpdateColorsFromTheme();
357 gfx::Size DownloadItemView::GetPreferredSize() {
360 // First, we set the height to the height of two rows or text plus margins.
361 height = 2 * kVerticalPadding + 2 * font_list_.GetHeight() +
362 kVerticalTextPadding;
363 // Then we increase the size if the progress icon doesn't fit.
364 height = std::max<int>(height, DownloadShelf::kSmallProgressIconSize);
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());
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();
386 width = kLeftPadding + normal_body_image_set_.top_left->width();
387 width += DownloadShelf::kSmallProgressIconSize;
389 width += normal_body_image_set_.top_right->width();
390 width += normal_drop_down_image_set_.top->width();
392 return gfx::Size(width, height);
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());
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())
408 if (!starting_drag_) {
409 starting_drag_ = true;
410 drag_start_point_ = event.location();
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);
418 views::Widget* widget = GetWidget();
420 download(), icon, widget ? widget->GetNativeView() : NULL);
423 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
429 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
430 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
433 void DownloadItemView::OnMouseCaptureLost() {
434 // Mouse should not activate us in dangerous mode.
435 if (mode_ == DANGEROUS_MODE)
439 // Starting a drag results in a MouseCaptureLost.
441 starting_drag_ = false;
443 SetState(NORMAL, NORMAL);
446 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
447 // Mouse should not activate us in dangerous mode.
448 if (mode_ == DANGEROUS_MODE)
451 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
452 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
455 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
456 // Mouse should not activate us in dangerous mode.
457 if (mode_ == DANGEROUS_MODE)
460 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
463 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
464 // Key press should not activate us in dangerous mode.
465 if (IsShowingWarningDialog())
468 if (event.key_code() == ui::VKEY_SPACE ||
469 event.key_code() == ui::VKEY_RETURN) {
476 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
477 string16* tooltip) const {
478 if (IsShowingWarningDialog()) {
483 tooltip->assign(tooltip_text_);
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;
494 state->state = ui::AccessibilityTypes::STATE_HASPOPUP;
498 void DownloadItemView::OnThemeChanged() {
499 UpdateColorsFromTheme();
502 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
503 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
504 HandlePressEvent(*event, true);
509 if (event->type() == ui::ET_GESTURE_TAP) {
510 HandleClickEvent(*event, true);
515 SetState(NORMAL, NORMAL);
516 views::View::OnGestureEvent(event);
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);
528 void DownloadItemView::ButtonPressed(views::Button* sender,
529 const ui::Event& event) {
530 base::TimeDelta warning_duration;
531 if (!time_download_warning_shown_.is_null())
532 warning_duration = base::Time::Now() - time_download_warning_shown_;
534 if (save_button_ && sender == save_button_) {
535 // The user has confirmed a dangerous download. We'd record how quickly the
536 // user did this to detect whether we're being clickjacked.
537 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
538 // This will change the state and notify us.
539 download()->ValidateDangerousDownload();
543 // WARNING: all end states after this point delete |this|.
544 DCHECK_EQ(discard_button_, sender);
545 if (model_.IsMalicious()) {
546 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
547 shelf_->RemoveDownloadView(this);
550 if (model_.ShouldAllowDownloadFeedback() && BeginDownloadFeedback())
552 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration);
553 download()->Remove();
556 void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) {
557 // We don't care if what animation (body button/drop button/complete),
558 // is calling back, as they all have to go through the same paint call.
562 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
563 // and MALICIOUS_MODE).
565 // NORMAL_MODE: We are displaying an in-progress or completed download.
566 // .-------------------------------+-.
567 // | [icon] Filename |v|
569 // `-------------------------------+-'
570 // | | \_ Drop down button. Invokes menu. Responds
571 // | | to mouse. (NORMAL, HOT or PUSHED).
572 // | \_ Icon is overlaid on top of in-progress animation.
573 // \_ Both the body and the drop down button respond to mouse hover and can be
574 // pushed (NORMAL, HOT or PUSHED).
576 // DANGEROUS_MODE: The file could be potentially dangerous.
577 // .-------------------------------------------------------.
578 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
579 // | [ ] [destroy your computer..] [ ] [ ] |
580 // `-------------------------------------------------------'
581 // | | | | \_ No drop down button.
582 // | | | \_ Buttons are views::LabelButtons.
583 // | | \_ Text is in a label (dangerous_download_label_)
584 // | \_ Warning icon. No progress animation.
585 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
587 // MALICIOUS_MODE: The file is known malware.
588 // .---------------------------------------------+-.
589 // | [ - ] [This file is malicious.] [ Discard ] |v|
590 // | [ ] [ ] [ ] | |-.
591 // `---------------------------------------------+-' |
592 // | | | | Drop down button. Responds to
593 // | | | | mouse.(NORMAL, HOT or PUSHED)
594 // | | | \_ Button is a views::LabelButton.
595 // | | \_ Text is in a label (dangerous_download_label_)
596 // | \_ Warning icon. No progress animation.
597 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
599 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
600 BodyImageSet* body_image_set = NULL;
603 if (body_state_ == PUSHED)
604 body_image_set = &pushed_body_image_set_;
605 else // NORMAL or HOT
606 body_image_set = &normal_body_image_set_;
609 body_image_set = &dangerous_mode_body_image_set_;
612 body_image_set = &malicious_mode_body_image_set_;
618 DropDownImageSet* drop_down_image_set = NULL;
622 if (drop_down_state_ == PUSHED)
623 drop_down_image_set = &pushed_drop_down_image_set_;
624 else // NORMAL or HOT
625 drop_down_image_set = &normal_drop_down_image_set_;
628 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
629 // drop_down_image_set == NULL.
635 int center_width = width() - kLeftPadding -
636 body_image_set->left->width() -
637 body_image_set->right->width() -
638 (drop_down_image_set ?
639 normal_drop_down_image_set_.center->width() :
642 // May be caused by animation.
643 if (center_width <= 0)
646 // Draw status before button image to effectively lighten text. No status for
648 if (!IsShowingWarningDialog()) {
649 if (!status_text_.empty()) {
650 int mirrored_x = GetMirroredXWithWidthInView(
651 DownloadShelf::kSmallProgressIconSize, kTextWidth);
652 // Add font_list_.height() to compensate for title, which is drawn later.
653 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() +
654 kVerticalTextPadding;
655 SkColor file_name_color = GetThemeProvider()->GetColor(
656 ThemeProperties::COLOR_BOOKMARK_TEXT);
657 // If text is light-on-dark, lightening it alone will do nothing.
658 // Therefore we mute luminance a wee bit before drawing in this case.
659 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
660 file_name_color = SkColorSetRGB(
661 static_cast<int>(kDownloadItemLuminanceMod *
662 SkColorGetR(file_name_color)),
663 static_cast<int>(kDownloadItemLuminanceMod *
664 SkColorGetG(file_name_color)),
665 static_cast<int>(kDownloadItemLuminanceMod *
666 SkColorGetB(file_name_color)));
667 canvas->DrawStringRect(status_text_, font_list_, file_name_color,
668 gfx::Rect(mirrored_x, y, kTextWidth,
669 font_list_.GetHeight()));
673 // Paint the background images.
674 int x = kLeftPadding;
676 if (base::i18n::IsRTL()) {
677 // Since we do not have the mirrored images for
678 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
679 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
680 // for RTL UI, we flip the canvas to draw those images mirrored.
681 // Consequently, we do not need to mirror the x-axis of those images.
682 canvas->Translate(gfx::Vector2d(width(), 0));
683 canvas->Scale(-1, 1);
686 body_image_set->top_left, body_image_set->left,
687 body_image_set->bottom_left,
688 x, box_y_, box_height_, body_image_set->top_left->width());
689 x += body_image_set->top_left->width();
691 body_image_set->top, body_image_set->center,
692 body_image_set->bottom,
693 x, box_y_, box_height_, center_width);
696 body_image_set->top_right, body_image_set->right,
697 body_image_set->bottom_right,
698 x, box_y_, box_height_, body_image_set->top_right->width());
700 // Overlay our body hot state. Warning dialogs don't display body a hot state.
701 if (!IsShowingWarningDialog() &&
702 body_hover_animation_->GetCurrentValue() > 0) {
703 canvas->SaveLayerAlpha(
704 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
706 int x = kLeftPadding;
708 hot_body_image_set_.top_left, hot_body_image_set_.left,
709 hot_body_image_set_.bottom_left,
710 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
711 x += body_image_set->top_left->width();
713 hot_body_image_set_.top, hot_body_image_set_.center,
714 hot_body_image_set_.bottom,
715 x, box_y_, box_height_, center_width);
718 hot_body_image_set_.top_right, hot_body_image_set_.right,
719 hot_body_image_set_.bottom_right,
720 x, box_y_, box_height_,
721 hot_body_image_set_.top_right->width());
725 x += body_image_set->top_right->width();
727 // Paint the drop-down.
728 if (drop_down_image_set) {
730 drop_down_image_set->top, drop_down_image_set->center,
731 drop_down_image_set->bottom,
732 x, box_y_, box_height_, drop_down_image_set->top->width());
734 // Overlay our drop-down hot state.
735 if (drop_hover_animation_->GetCurrentValue() > 0) {
736 canvas->SaveLayerAlpha(
737 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
740 drop_down_image_set->top, drop_down_image_set->center,
741 drop_down_image_set->bottom,
742 x, box_y_, box_height_, drop_down_image_set->top->width());
748 // Restore the canvas to avoid file name etc. text are drawn flipped.
749 // Consequently, the x-axis of following canvas->DrawXXX() method should be
750 // mirrored so the text and images are down in the right positions.
753 // Print the text, left aligned and always print the file extension.
754 // Last value of x was the end of the right image, just before the button.
755 // Note that in dangerous mode we use a label (as the text is multi-line).
756 if (!IsShowingWarningDialog()) {
758 if (!disabled_while_opening_) {
759 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(),
760 font_list_, kTextWidth);
762 // First, Calculate the download status opening string width.
763 string16 status_string =
764 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, string16());
765 int status_string_width = font_list_.GetStringWidth(status_string);
766 // Then, elide the file name.
767 string16 filename_string =
768 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_,
769 kTextWidth - status_string_width);
770 // Last, concat the whole string.
771 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
775 int mirrored_x = GetMirroredXWithWidthInView(
776 DownloadShelf::kSmallProgressIconSize, kTextWidth);
777 SkColor file_name_color = GetThemeProvider()->GetColor(
778 ThemeProperties::COLOR_BOOKMARK_TEXT);
780 box_y_ + (status_text_.empty() ?
781 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
783 // Draw the file's name.
784 canvas->DrawStringRect(
785 filename, font_list_,
786 enabled() ? file_name_color : kFileNameDisabledColor,
787 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight()));
791 IconManager* im = g_browser_process->icon_manager();
792 gfx::Image* image = im->LookupIconFromFilepath(
793 download()->GetTargetFilePath(), IconLoader::SMALL);
794 const gfx::ImageSkia* icon = NULL;
795 if (IsShowingWarningDialog())
796 icon = warning_icon_;
798 icon = image->ToImageSkia();
800 // We count on the fact that the icon manager will cache the icons and if one
801 // is available, it will be cached here. We *don't* want to request the icon
802 // to be loaded here, since this will also get called if the icon can't be
803 // loaded, in which case LookupIcon will always be NULL. The loading will be
804 // triggered only when we think the status might change.
806 if (!IsShowingWarningDialog()) {
807 DownloadItem::DownloadState state = download()->GetState();
808 if (state == DownloadItem::IN_PROGRESS) {
809 DownloadShelf::PaintDownloadProgress(canvas,
814 model_.PercentComplete(),
815 DownloadShelf::SMALL);
816 } else if (complete_animation_.get() &&
817 complete_animation_->is_animating()) {
818 if (state == DownloadItem::INTERRUPTED) {
819 DownloadShelf::PaintDownloadInterrupted(
824 complete_animation_->GetCurrentValue(),
825 DownloadShelf::SMALL);
827 DCHECK_EQ(DownloadItem::COMPLETE, state);
828 DownloadShelf::PaintDownloadComplete(
833 complete_animation_->GetCurrentValue(),
834 DownloadShelf::SMALL);
839 // Draw the icon image.
842 if (IsShowingWarningDialog()) {
843 icon_x = kLeftPadding + body_image_set->top_left->width();
844 icon_y = (height() - icon->height()) / 2;
846 icon_x = DownloadShelf::kSmallProgressIconOffset;
847 icon_y = DownloadShelf::kSmallProgressIconOffset;
849 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
851 canvas->DrawImageInt(*icon, icon_x, icon_y);
853 // Use an alpha to make the image look disabled.
856 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
861 void DownloadItemView::OpenDownload() {
862 DCHECK(!IsShowingWarningDialog());
863 // We're interested in how long it takes users to open downloads. If they
864 // open downloads super quickly, we should be concerned about clickjacking.
865 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
866 base::Time::Now() - creation_time_);
867 download()->OpenDownload();
868 UpdateAccessibleName();
871 bool DownloadItemView::BeginDownloadFeedback() {
872 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
875 safe_browsing::DownloadProtectionService* download_protection_service =
876 sb_service->download_protection_service();
877 if (!download_protection_service)
879 base::TimeDelta warning_duration = base::TimeDelta();
880 if (!time_download_warning_shown_.is_null())
881 warning_duration = base::Time::Now() - time_download_warning_shown_;
882 UMA_HISTOGRAM_LONG_TIMES("clickjacking.report_and_discard_download",
884 download_protection_service->feedback_service()->BeginFeedbackForDownload(
886 // WARNING: we are deleted at this point. Don't access 'this'.
890 void DownloadItemView::LoadIcon() {
891 IconManager* im = g_browser_process->icon_manager();
892 last_download_item_path_ = download()->GetTargetFilePath();
893 im->LoadIcon(last_download_item_path_,
895 base::Bind(&DownloadItemView::OnExtractIconComplete,
896 base::Unretained(this)),
897 &cancelable_task_tracker_);
900 void DownloadItemView::LoadIconIfItemPathChanged() {
901 base::FilePath current_download_path = download()->GetTargetFilePath();
902 if (last_download_item_path_ == current_download_path)
908 void DownloadItemView::UpdateColorsFromTheme() {
909 if (dangerous_download_label_ && GetThemeProvider()) {
910 dangerous_download_label_->SetEnabledColor(
911 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
915 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
916 ui::MenuSourceType source_type) {
917 gfx::Point point = p;
920 // Similar hack as in MenuButton.
921 // We're about to show the menu from a mouse press. By showing from the
922 // mouse press event we block RootView in mouse dispatching. This also
923 // appears to cause RootView to get a mouse pressed BEFORE the mouse
924 // release is seen, which means RootView sends us another mouse press no
925 // matter where the user pressed. To force RootView to recalculate the
926 // mouse target during the mouse press we explicitly set the mouse handler
928 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
929 SetMouseHandler(NULL);
931 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
932 // to drop down arrow button.
933 if (source_type != ui::MENU_SOURCE_MOUSE &&
934 source_type != ui::MENU_SOURCE_TOUCH) {
935 drop_down_pressed_ = true;
936 SetState(NORMAL, PUSHED);
937 point.SetPoint(drop_down_x_left_, box_y_);
938 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
940 // Post a task to release the button. When we call the Run method on the menu
941 // below, it runs an inner message loop that might cause us to be deleted.
942 // Posting a task with a WeakPtr lets us safely handle the button release.
943 base::MessageLoop::current()->PostNonNestableTask(
945 base::Bind(&DownloadItemView::ReleaseDropDown,
946 weak_ptr_factory_.GetWeakPtr()));
947 views::View::ConvertPointToScreen(this, &point);
949 if (!context_menu_.get()) {
951 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator()));
953 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
954 gfx::Rect(point, size), source_type);
955 // We could be deleted now.
958 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event,
960 // The event should not activate us in dangerous mode.
961 if (mode_ == DANGEROUS_MODE)
964 // Stop any completion animation.
965 if (complete_animation_.get() && complete_animation_->is_animating())
966 complete_animation_->End();
969 if (InDropDownButtonXCoordinateRange(event.x())) {
970 drop_down_pressed_ = true;
971 SetState(NORMAL, PUSHED);
972 // We are setting is_mouse_gesture to false when calling ShowContextMenu
973 // so that the positioning of the context menu will be similar to a
974 // keyboard invocation. I.e. we want the menu to always be positioned
975 // next to the drop down button instead of the next to the pointer.
976 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD);
977 // Once called, it is possible that *this was deleted (e.g.: due to
978 // invoking the 'Discard' action.)
979 } else if (!IsShowingWarningDialog()) {
980 SetState(PUSHED, NORMAL);
985 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event,
987 // Mouse should not activate us in dangerous mode.
988 if (mode_ == DANGEROUS_MODE)
992 !InDropDownButtonXCoordinateRange(event.x()) &&
993 !IsShowingWarningDialog()) {
997 SetState(NORMAL, NORMAL);
1000 // Load an icon for the file type we're downloading, and animate any in progress
1002 void DownloadItemView::PaintImages(gfx::Canvas* canvas,
1003 const gfx::ImageSkia* top_image,
1004 const gfx::ImageSkia* center_image,
1005 const gfx::ImageSkia* bottom_image,
1006 int x, int y, int height, int width) {
1007 int middle_height = height - top_image->height() - bottom_image->height();
1009 canvas->DrawImageInt(*top_image,
1010 0, 0, top_image->width(), top_image->height(),
1011 x, y, width, top_image->height(), false);
1012 y += top_image->height();
1014 canvas->DrawImageInt(*center_image,
1015 0, 0, center_image->width(), center_image->height(),
1016 x, y, width, middle_height, false);
1019 canvas->DrawImageInt(*bottom_image,
1020 0, 0, bottom_image->width(), bottom_image->height(),
1021 x, y, width, bottom_image->height(), false);
1024 void DownloadItemView::SetState(State new_body_state, State new_drop_state) {
1025 // If we are showing a warning dialog, we don't change body state.
1026 if (IsShowingWarningDialog()) {
1027 new_body_state = NORMAL;
1029 // Current body_state_ should always be NORMAL for warning dialogs.
1030 DCHECK_EQ(NORMAL, body_state_);
1031 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1032 DCHECK_NE(DANGEROUS_MODE, mode_);
1034 // Avoid extra SchedulePaint()s if the state is going to be the same.
1035 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state)
1038 AnimateStateTransition(body_state_, new_body_state,
1039 body_hover_animation_.get());
1040 AnimateStateTransition(drop_down_state_, new_drop_state,
1041 drop_hover_animation_.get());
1042 body_state_ = new_body_state;
1043 drop_down_state_ = new_drop_state;
1047 void DownloadItemView::ClearWarningDialog() {
1048 DCHECK(download()->GetDangerType() ==
1049 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1050 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1052 mode_ = NORMAL_MODE;
1053 body_state_ = NORMAL;
1054 drop_down_state_ = NORMAL;
1056 // Remove the views used by the warning dialog.
1058 RemoveChildView(save_button_);
1059 delete save_button_;
1060 save_button_ = NULL;
1062 RemoveChildView(discard_button_);
1063 delete discard_button_;
1064 discard_button_ = NULL;
1065 RemoveChildView(dangerous_download_label_);
1066 delete dangerous_download_label_;
1067 dangerous_download_label_ = NULL;
1068 dangerous_download_label_sized_ = false;
1069 cached_button_size_.SetSize(0,0);
1071 // Set the accessible name back to the status and filename instead of the
1072 // download warning.
1073 UpdateAccessibleName();
1074 UpdateDropDownButtonPosition();
1076 // We need to load the icon now that the download has the real path.
1079 // Force the shelf to layout again as our size has changed.
1081 shelf_->SchedulePaint();
1083 TooltipTextChanged();
1086 void DownloadItemView::ShowWarningDialog() {
1087 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
1088 time_download_warning_shown_ = base::Time::Now();
1089 if (model_.ShouldAllowDownloadFeedback()) {
1090 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1091 download()->GetDangerType());
1093 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
1095 body_state_ = NORMAL;
1096 drop_down_state_ = NORMAL;
1097 if (mode_ == DANGEROUS_MODE) {
1098 save_button_ = new views::LabelButton(
1099 this, model_.GetWarningConfirmButtonText());
1100 save_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
1101 AddChildView(save_button_);
1103 int discard_button_message = model_.IsMalicious() ?
1104 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD;
1105 if (!model_.IsMalicious() && model_.ShouldAllowDownloadFeedback())
1106 discard_button_message = IDS_REPORT_AND_DISCARD_DOWNLOAD;
1107 discard_button_ = new views::LabelButton(
1108 this, l10n_util::GetStringUTF16(discard_button_message));
1109 discard_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
1110 AddChildView(discard_button_);
1112 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1113 switch (download()->GetDangerType()) {
1114 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
1115 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
1116 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
1117 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
1118 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
1119 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING);
1122 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
1123 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
1124 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
1125 case content::DOWNLOAD_DANGER_TYPE_MAX:
1129 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1130 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
1132 string16 dangerous_label = model_.GetWarningText(font_list_, kTextWidth);
1133 dangerous_download_label_ = new views::Label(dangerous_label);
1134 dangerous_download_label_->SetMultiLine(true);
1135 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1136 dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
1137 AddChildView(dangerous_download_label_);
1138 SizeLabelToMinWidth();
1139 UpdateDropDownButtonPosition();
1140 TooltipTextChanged();
1143 gfx::Size DownloadItemView::GetButtonSize() {
1144 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
1147 // We cache the size when successfully retrieved, not for performance reasons
1148 // but because if this DownloadItemView is being animated while the tab is
1149 // not showing, the native buttons are not parented and their preferred size
1150 // is 0, messing-up the layout.
1151 if (cached_button_size_.width() != 0)
1152 return cached_button_size_;
1155 size = save_button_->GetMinimumSize();
1156 gfx::Size discard_size = discard_button_->GetMinimumSize();
1158 size.SetSize(std::max(size.width(), discard_size.width()),
1159 std::max(size.height(), discard_size.height()));
1161 if (size.width() != 0)
1162 cached_button_size_ = size;
1167 // This method computes the minimum width of the label for displaying its text
1168 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1169 // configuration with minimum width.
1170 void DownloadItemView::SizeLabelToMinWidth() {
1171 if (dangerous_download_label_sized_)
1174 string16 label_text = dangerous_download_label_->text();
1175 TrimWhitespace(label_text, TRIM_ALL, &label_text);
1176 DCHECK_EQ(string16::npos, label_text.find('\n'));
1178 // Make the label big so that GetPreferredSize() is not constrained by the
1180 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1182 // Use a const string from here. BreakIterator requies that text.data() not
1183 // change during its lifetime.
1184 const string16 original_text(label_text);
1185 // Using BREAK_WORD can work in most cases, but it can also break
1186 // lines where it should not. Using BREAK_LINE is safer although
1187 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1188 base::i18n::BreakIterator iter(original_text,
1189 base::i18n::BreakIterator::BREAK_LINE);
1190 bool status = iter.Init();
1193 string16 prev_text = original_text;
1194 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1195 int min_width = size.width();
1197 // Go through the string and try each line break (starting with no line break)
1198 // searching for the optimal line break position. Stop if we find one that
1199 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1200 // a short string (e.g.: "This file is malicious") from being broken up
1202 while (iter.Advance() && min_width > kDangerousTextWidth) {
1203 size_t pos = iter.pos();
1204 if (pos >= original_text.length())
1206 string16 current_text = original_text;
1207 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1208 // return false and inserting a new line after a surrogate pair
1210 char16 line_end_char = current_text[pos - 1];
1211 if (u_isUWhiteSpace(line_end_char))
1212 current_text.replace(pos - 1, 1, 1, char16('\n'));
1214 current_text.insert(pos, 1, char16('\n'));
1215 dangerous_download_label_->SetText(current_text);
1216 size = dangerous_download_label_->GetPreferredSize();
1218 // If the width is growing again, it means we passed the optimal width spot.
1219 if (size.width() > min_width) {
1220 dangerous_download_label_->SetText(prev_text);
1223 min_width = size.width();
1225 prev_text = current_text;
1228 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1229 dangerous_download_label_sized_ = true;
1232 void DownloadItemView::Reenable() {
1233 disabled_while_opening_ = false;
1234 SetEnabled(true); // Triggers a repaint.
1237 void DownloadItemView::ReleaseDropDown() {
1238 drop_down_pressed_ = false;
1239 SetState(NORMAL, NORMAL);
1242 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1243 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1248 void DownloadItemView::UpdateAccessibleName() {
1250 if (IsShowingWarningDialog()) {
1251 new_name = dangerous_download_label_->text();
1253 new_name = status_text_ + char16(' ') +
1254 download()->GetFileNameToReportUser().LossyDisplayName();
1257 // If the name has changed, notify assistive technology that the name
1258 // has changed so they can announce it immediately.
1259 if (new_name != accessible_name_) {
1260 accessible_name_ = new_name;
1261 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_NAME_CHANGED, true);
1265 void DownloadItemView::UpdateDropDownButtonPosition() {
1266 gfx::Size size = GetPreferredSize();
1267 if (base::i18n::IsRTL()) {
1268 // Drop down button is glued to the left of the download shelf.
1269 drop_down_x_left_ = 0;
1270 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
1272 // Drop down button is glued to the right of the download shelf.
1274 size.width() - normal_drop_down_image_set_.top->width();
1275 drop_down_x_right_ = size.width();
1279 void DownloadItemView::AnimateStateTransition(State from, State to,
1280 gfx::SlideAnimation* animation) {
1281 if (from == NORMAL && to == HOT) {
1283 } else if (from == HOT && to == NORMAL) {
1285 } else if (from != to) {
1286 animation->Reset((to == HOT) ? 1.0 : 0.0);