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/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"
57 using content::DownloadItem;
58 using extensions::ExperienceSamplingEvent;
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
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.
73 // The space between the Save and Discard buttons when prompting for a dangerous
75 static const int kButtonPadding = 5; // Pixels.
77 // The space on the left and right side of the dangerous download label.
78 static const int kLabelPadding = 4; // Pixels.
80 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
82 // How long the 'download complete' animation should last for.
83 static const int kCompleteAnimationDurationMs = 2500;
85 // How long the 'download interrupted' animation should last for.
86 static const int kInterruptedAnimationDurationMs = 2500;
88 // How long we keep the item disabled after the user clicked it to open the
90 static const int kDisabledOnOpenDuration = 3000;
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;
99 // Callback for DownloadShelf paint functions to mirror the progress animation
101 void RTLMirrorXForView(views::View* containing_view, gfx::Rect* bounds) {
102 bounds->set_x(containing_view->GetMirroredXForRect(*bounds));
107 DownloadItemView::DownloadItemView(DownloadItem* download_item,
108 DownloadShelfView* parent)
109 : warning_icon_(NULL),
111 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
113 drop_down_state_(NORMAL),
115 progress_angle_(DownloadShelf::kStartAngleDegrees),
116 drop_down_pressed_(false),
118 starting_drag_(false),
119 model_(download_item),
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) {
129 download()->AddObserver(this);
130 set_context_menu_controller(this);
132 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
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)
145 normal_body_image_set_ = normal_body_image_set;
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)
152 normal_drop_down_image_set_ = normal_drop_down_image_set;
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)
165 hot_body_image_set_ = hot_body_image_set;
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)
172 hot_drop_down_image_set_ = hot_drop_down_image_set;
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)
185 pushed_body_image_set_ = pushed_body_image_set;
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)
192 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
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)
205 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
207 malicious_mode_body_image_set_ = normal_body_image_set;
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());
218 if (DownloadShelf::kSmallProgressIconSize > box_height_)
219 box_y_ = (DownloadShelf::kSmallProgressIconSize - box_height_) / 2;
223 body_hover_animation_.reset(new gfx::SlideAnimation(this));
224 drop_hover_animation_.reset(new gfx::SlideAnimation(this));
226 SetAccessibilityFocusable(true);
228 OnDownloadUpdated(download());
229 UpdateDropDownButtonPosition();
232 DownloadItemView::~DownloadItemView() {
233 StopDownloadProgress();
234 download()->RemoveObserver(this);
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);
243 // Progress animation handlers.
245 void DownloadItemView::UpdateDownloadProgress() {
247 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
248 DownloadShelf::kMaxDegrees;
252 void DownloadItemView::StartDownloadProgress() {
253 if (progress_timer_.IsRunning())
255 progress_timer_.Start(FROM_HERE,
256 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs), this,
257 &DownloadItemView::UpdateDownloadProgress);
260 void DownloadItemView::StopDownloadProgress() {
261 progress_timer_.Stop();
264 void DownloadItemView::OnExtractIconComplete(gfx::Image* icon_bitmap) {
266 shelf_->SchedulePaint();
269 // DownloadObserver interface.
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);
276 if (IsShowingWarningDialog() && !model_.IsDangerous()) {
277 // We have been approved.
278 ClearWarningDialog();
279 } else if (!IsShowingWarningDialog() && model_.IsDangerous()) {
281 // Force the shelf to layout again as our size has changed.
285 base::string16 status_text = model_.GetStatusText();
286 switch (download()->GetState()) {
287 case DownloadItem::IN_PROGRESS:
288 download()->IsPaused() ?
289 StopDownloadProgress() : StartDownloadProgress();
290 LoadIconIfItemPathChanged();
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();
301 case DownloadItem::COMPLETE:
302 if (model_.ShouldRemoveFromShelfWhenComplete()) {
303 shelf_->RemoveDownloadView(this); // This will delete us!
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();
314 case DownloadItem::CANCELLED:
315 StopDownloadProgress();
316 if (complete_animation_)
317 complete_animation_->Stop();
323 status_text_ = status_text;
326 base::string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth);
327 if (new_tip != tooltip_text_) {
328 tooltip_text_ = new_tip;
329 TooltipTextChanged();
332 UpdateAccessibleName();
334 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
335 // are spaces between each DownloadItemView that the parent is responsible
337 shelf_->SchedulePaint();
340 void DownloadItemView::OnDownloadDestroyed(DownloadItem* download) {
341 shelf_->RemoveDownloadView(this); // This will delete us!
344 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
345 disabled_while_opening_ = true;
347 base::MessageLoop::current()->PostDelayedTask(
349 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
350 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
352 // Notify our parent.
353 shelf_->OpenedDownload(this);
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;
374 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
375 x += button_size.width() + kButtonPadding;
377 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
378 UpdateColorsFromTheme();
382 gfx::Size DownloadItemView::GetPreferredSize() const {
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);
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());
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();
411 width = kLeftPadding + normal_body_image_set_.top_left->width();
412 width += DownloadShelf::kSmallProgressIconSize;
414 width += normal_body_image_set_.top_right->width();
415 width += normal_drop_down_image_set_.top->width();
417 return gfx::Size(width, height);
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());
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())
433 if (!starting_drag_) {
434 starting_drag_ = true;
435 drag_start_point_ = event.location();
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();
444 download(), icon, widget ? widget->GetNativeView() : NULL);
446 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
452 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
453 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
456 void DownloadItemView::OnMouseCaptureLost() {
457 // Mouse should not activate us in dangerous mode.
458 if (mode_ == DANGEROUS_MODE)
462 // Starting a drag results in a MouseCaptureLost.
464 starting_drag_ = false;
466 SetState(NORMAL, NORMAL);
469 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
470 // Mouse should not activate us in dangerous mode.
471 if (mode_ == DANGEROUS_MODE)
474 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
475 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
478 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
479 // Mouse should not activate us in dangerous mode.
480 if (mode_ == DANGEROUS_MODE)
483 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
486 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
487 // Key press should not activate us in dangerous mode.
488 if (IsShowingWarningDialog())
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.
500 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
501 base::string16* tooltip) const {
502 if (IsShowingWarningDialog()) {
507 tooltip->assign(tooltip_text_);
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);
518 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
521 void DownloadItemView::OnThemeChanged() {
522 UpdateColorsFromTheme();
525 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
526 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
527 HandlePressEvent(*event, true);
532 if (event->type() == ui::ET_GESTURE_TAP) {
533 HandleClickEvent(*event, true);
538 SetState(NORMAL, NORMAL);
539 views::View::OnGestureEvent(event);
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);
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_;
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);
567 // This will change the state and notify us.
568 download()->ValidateDangerousDownload();
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);
581 shelf_->RemoveDownloadView(this);
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(),
595 &DownloadItemView::PossiblySubmitDownloadToFeedbackService,
596 weak_ptr_factory_.GetWeakPtr()));
598 PossiblySubmitDownloadToFeedbackService(
599 shelf_->browser()->profile()->GetPrefs()->GetBoolean(
600 prefs::kSafeBrowsingExtendedReportingEnabled));
604 download()->Remove();
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.
613 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
614 OnPaintBackground(canvas);
616 canvas->DrawFocusRect(GetLocalBounds());
619 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
620 // and MALICIOUS_MODE).
622 // NORMAL_MODE: We are displaying an in-progress or completed download.
623 // .-------------------------------+-.
624 // | [icon] Filename |v|
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).
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)
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)
656 void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) {
657 BodyImageSet* body_image_set = NULL;
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_;
666 body_image_set = &dangerous_mode_body_image_set_;
669 body_image_set = &malicious_mode_body_image_set_;
675 DropDownImageSet* drop_down_image_set = NULL;
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_;
685 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
686 // drop_down_image_set == NULL.
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() :
699 // May be caused by animation.
700 if (center_width <= 0)
703 // Draw status before button image to effectively lighten text. No status for
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()));
730 // Paint the background images.
731 int x = kLeftPadding;
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);
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();
748 body_image_set->top, body_image_set->center,
749 body_image_set->bottom,
750 x, box_y_, box_height_, center_width);
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());
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));
763 int x = kLeftPadding;
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();
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);
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());
782 x += body_image_set->top_right->width();
784 // Paint the drop-down.
785 if (drop_down_image_set) {
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());
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));
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());
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.
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);
819 // First, Calculate the download status opening string width.
820 base::string16 status_string =
821 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
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,
833 int mirrored_x = GetMirroredXWithWidthInView(
834 DownloadShelf::kSmallProgressIconSize, kTextWidth);
835 SkColor file_name_color = GetThemeProvider()->GetColor(
836 ThemeProperties::COLOR_BOOKMARK_TEXT);
838 box_y_ + (status_text_.empty() ?
839 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
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()));
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_;
856 icon = image->ToImageSkia();
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.
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,
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(
884 complete_animation_->GetCurrentValue(),
885 DownloadShelf::SMALL);
887 DCHECK_EQ(DownloadItem::COMPLETE, state);
888 DownloadShelf::PaintDownloadComplete(
893 complete_animation_->GetCurrentValue(),
894 DownloadShelf::SMALL);
899 // Draw the icon image.
902 if (IsShowingWarningDialog()) {
903 icon_x = kLeftPadding + body_image_set->top_left->width();
904 icon_y = (height() - icon->height()) / 2;
906 icon_x = DownloadShelf::kSmallProgressIconOffset;
907 icon_y = DownloadShelf::kSmallProgressIconOffset;
909 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
911 canvas->DrawImageInt(*icon, icon_x, icon_y);
913 // Use an alpha to make the image look disabled.
916 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
921 void DownloadItemView::OnFocus() {
923 // We render differently when focused.
927 void DownloadItemView::OnBlur() {
929 // We render differently when focused.
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_);
940 UpdateAccessibleName();
942 // Calling download()->OpenDownload may delete this, so this must be
943 // the last thing we do.
944 download()->OpenDownload();
947 bool DownloadItemView::SubmitDownloadToFeedbackService() {
948 #if defined(FULL_SAFE_BROWSING)
949 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
952 safe_browsing::DownloadProtectionService* download_protection_service =
953 sb_service->download_protection_service();
954 if (!download_protection_service)
956 download_protection_service->feedback_service()->BeginFeedbackForDownload(
958 // WARNING: we are deleted at this point. Don't access 'this'.
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'.
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_,
977 base::Bind(&DownloadItemView::OnExtractIconComplete,
978 base::Unretained(this)),
979 &cancelable_task_tracker_);
982 void DownloadItemView::LoadIconIfItemPathChanged() {
983 base::FilePath current_download_path = download()->GetTargetFilePath();
984 if (last_download_item_path_ == current_download_path)
990 void DownloadItemView::UpdateColorsFromTheme() {
991 if (dangerous_download_label_ && GetThemeProvider()) {
992 dangerous_download_label_->SetEnabledColor(
993 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
997 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
998 ui::MenuSourceType source_type) {
999 gfx::Point point = p;
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
1010 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
1011 SetMouseHandler(NULL);
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_);
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(
1027 base::Bind(&DownloadItemView::ReleaseDropDown,
1028 weak_ptr_factory_.GetWeakPtr()));
1029 views::View::ConvertPointToScreen(this, &point);
1031 if (!context_menu_.get()) {
1032 context_menu_.reset(
1033 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator()));
1035 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
1036 gfx::Rect(point, size), source_type);
1037 // We could be deleted now.
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)
1046 // Stop any completion animation.
1047 if (complete_animation_.get() && complete_animation_->is_animating())
1048 complete_animation_->End();
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)
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);
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)
1081 SetState(NORMAL, NORMAL);
1083 if (!active_event ||
1084 InDropDownButtonXCoordinateRange(event.x()) ||
1085 IsShowingWarningDialog()) {
1089 // OpenDownload may delete this, so don't add any code after this line.
1093 // Load an icon for the file type we're downloading, and animate any in progress
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();
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();
1107 canvas->DrawImageInt(*center_image,
1108 0, 0, center_image->width(), center_image->height(),
1109 x, y, width, middle_height, false);
1112 canvas->DrawImageInt(*bottom_image,
1113 0, 0, bottom_image->width(), bottom_image->height(),
1114 x, y, width, bottom_image->height(), false);
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;
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_);
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)
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;
1140 void DownloadItemView::ClearWarningDialog() {
1141 DCHECK(download()->GetDangerType() ==
1142 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1143 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1145 mode_ = NORMAL_MODE;
1146 body_state_ = NORMAL;
1147 drop_down_state_ = NORMAL;
1149 // ExperienceSampling: User proceeded through the warning.
1150 if (sampling_event_.get()) {
1151 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
1152 sampling_event_.reset(NULL);
1154 // Remove the views used by the warning dialog.
1156 RemoveChildView(save_button_);
1157 delete save_button_;
1158 save_button_ = NULL;
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);
1169 // Set the accessible name back to the status and filename instead of the
1170 // download warning.
1171 UpdateAccessibleName();
1172 UpdateDropDownButtonPosition();
1174 // We need to load the icon now that the download has the real path.
1177 // Force the shelf to layout again as our size has changed.
1179 shelf_->SchedulePaint();
1181 TooltipTextChanged();
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(
1195 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
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()));
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_);
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_);
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);
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:
1240 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1241 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
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();
1255 gfx::Size DownloadItemView::GetButtonSize() const {
1256 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
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_;
1267 size = save_button_->GetMinimumSize();
1268 gfx::Size discard_size = discard_button_->GetMinimumSize();
1270 size.SetSize(std::max(size.width(), discard_size.width()),
1271 std::max(size.height(), discard_size.height()));
1273 if (size.width() != 0)
1274 cached_button_size_ = size;
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_)
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'));
1290 // Make the label big so that GetPreferredSize() is not constrained by the
1292 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
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();
1305 base::string16 prev_text = original_text;
1306 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1307 int min_width = size.width();
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
1314 while (iter.Advance() && min_width > kDangerousTextWidth) {
1315 size_t pos = iter.pos();
1316 if (pos >= original_text.length())
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
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'));
1326 current_text.insert(pos, 1, base::char16('\n'));
1327 dangerous_download_label_->SetText(current_text);
1328 size = dangerous_download_label_->GetPreferredSize();
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);
1335 min_width = size.width();
1337 prev_text = current_text;
1340 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1341 dangerous_download_label_sized_ = true;
1344 void DownloadItemView::Reenable() {
1345 disabled_while_opening_ = false;
1346 SetEnabled(true); // Triggers a repaint.
1349 void DownloadItemView::ReleaseDropDown() {
1350 drop_down_pressed_ = false;
1351 SetState(NORMAL, NORMAL);
1354 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1355 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1360 void DownloadItemView::UpdateAccessibleName() {
1361 base::string16 new_name;
1362 if (IsShowingWarningDialog()) {
1363 new_name = dangerous_download_label_->text();
1365 new_name = status_text_ + base::char16(' ') +
1366 download()->GetFileNameToReportUser().LossyDisplayName();
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);
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();
1384 // Drop down button is glued to the right of the download shelf.
1386 size.width() - normal_drop_down_image_set_.top->width();
1387 drop_down_x_right_ = size.width();
1391 void DownloadItemView::AnimateStateTransition(State from, State to,
1392 gfx::SlideAnimation* animation) {
1393 if (from == NORMAL && to == HOT) {
1395 } else if (from == HOT && to == NORMAL) {
1397 } else if (from != to) {
1398 animation->Reset((to == HOT) ? 1.0 : 0.0);