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/profiles/profile.h"
27 #include "chrome/browser/safe_browsing/download_feedback_service.h"
28 #include "chrome/browser/safe_browsing/download_protection_service.h"
29 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
30 #include "chrome/browser/themes/theme_properties.h"
31 #include "chrome/browser/ui/views/download/download_feedback_dialog_view.h"
32 #include "chrome/browser/ui/views/download/download_shelf_context_menu_view.h"
33 #include "chrome/browser/ui/views/download/download_shelf_view.h"
34 #include "chrome/browser/ui/views/frame/browser_view.h"
35 #include "content/public/browser/download_danger_type.h"
36 #include "grit/generated_resources.h"
37 #include "grit/theme_resources.h"
38 #include "third_party/icu/source/common/unicode/uchar.h"
39 #include "ui/base/accessibility/accessible_view_state.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/base/theme_provider.h"
43 #include "ui/events/event.h"
44 #include "ui/gfx/animation/slide_animation.h"
45 #include "ui/gfx/canvas.h"
46 #include "ui/gfx/color_utils.h"
47 #include "ui/gfx/image/image.h"
48 #include "ui/gfx/text_elider.h"
49 #include "ui/gfx/text_utils.h"
50 #include "ui/views/controls/button/label_button.h"
51 #include "ui/views/controls/label.h"
52 #include "ui/views/mouse_constants.h"
53 #include "ui/views/widget/root_view.h"
54 #include "ui/views/widget/widget.h"
56 // TODO(paulg): These may need to be adjusted when download progress
57 // animation is added, and also possibly to take into account
58 // different screen resolutions.
59 static const int kTextWidth
= 140; // Pixels
60 static const int kDangerousTextWidth
= 200; // Pixels
61 static const int kVerticalPadding
= 3; // Pixels
62 static const int kVerticalTextPadding
= 2; // Pixels
63 static const int kTooltipMaxWidth
= 800; // Pixels
65 // We add some padding before the left image so that the progress animation icon
66 // hides the corners of the left image.
67 static const int kLeftPadding
= 0; // Pixels.
69 // The space between the Save and Discard buttons when prompting for a dangerous
71 static const int kButtonPadding
= 5; // Pixels.
73 // The space on the left and right side of the dangerous download label.
74 static const int kLabelPadding
= 4; // Pixels.
76 static const SkColor kFileNameDisabledColor
= SkColorSetRGB(171, 192, 212);
78 // How long the 'download complete' animation should last for.
79 static const int kCompleteAnimationDurationMs
= 2500;
81 // How long the 'download interrupted' animation should last for.
82 static const int kInterruptedAnimationDurationMs
= 2500;
84 // How long we keep the item disabled after the user clicked it to open the
86 static const int kDisabledOnOpenDuration
= 3000;
88 // Darken light-on-dark download status text by 20% before drawing, thus
89 // creating a "muted" version of title text for both dark-on-light and
90 // light-on-dark themes.
91 static const double kDownloadItemLuminanceMod
= 0.8;
93 using content::DownloadItem
;
95 DownloadItemView::DownloadItemView(DownloadItem
* download_item
,
96 DownloadShelfView
* parent
)
97 : warning_icon_(NULL
),
99 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING
)),
101 drop_down_state_(NORMAL
),
103 progress_angle_(DownloadShelf::kStartAngleDegrees
),
104 drop_down_pressed_(false),
106 starting_drag_(false),
107 model_(download_item
),
109 discard_button_(NULL
),
110 dangerous_download_label_(NULL
),
111 dangerous_download_label_sized_(false),
112 disabled_while_opening_(false),
113 creation_time_(base::Time::Now()),
114 time_download_warning_shown_(base::Time()),
115 weak_ptr_factory_(this) {
117 download()->AddObserver(this);
118 set_context_menu_controller(this);
120 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
122 BodyImageSet normal_body_image_set
= {
123 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP
),
124 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE
),
125 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM
),
126 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP
),
127 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE
),
128 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM
),
129 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP
),
130 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE
),
131 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM
)
133 normal_body_image_set_
= normal_body_image_set
;
135 DropDownImageSet normal_drop_down_image_set
= {
136 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP
),
137 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE
),
138 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM
)
140 normal_drop_down_image_set_
= normal_drop_down_image_set
;
142 BodyImageSet hot_body_image_set
= {
143 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H
),
144 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H
),
145 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H
),
146 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H
),
147 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H
),
148 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H
),
149 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H
),
150 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H
),
151 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H
)
153 hot_body_image_set_
= hot_body_image_set
;
155 DropDownImageSet hot_drop_down_image_set
= {
156 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H
),
157 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H
),
158 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H
)
160 hot_drop_down_image_set_
= hot_drop_down_image_set
;
162 BodyImageSet pushed_body_image_set
= {
163 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P
),
164 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P
),
165 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P
),
166 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P
),
167 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P
),
168 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P
),
169 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P
),
170 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P
),
171 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P
)
173 pushed_body_image_set_
= pushed_body_image_set
;
175 DropDownImageSet pushed_drop_down_image_set
= {
176 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P
),
177 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P
),
178 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P
)
180 pushed_drop_down_image_set_
= pushed_drop_down_image_set
;
182 BodyImageSet dangerous_mode_body_image_set
= {
183 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP
),
184 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE
),
185 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM
),
186 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP
),
187 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE
),
188 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM
),
189 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD
),
190 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD
),
191 rb
.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD
)
193 dangerous_mode_body_image_set_
= dangerous_mode_body_image_set
;
195 malicious_mode_body_image_set_
= normal_body_image_set
;
199 font_list_
= rb
.GetFontList(ui::ResourceBundle::BaseFont
);
200 box_height_
= std::max
<int>(2 * kVerticalPadding
+ font_list_
.GetHeight() +
201 kVerticalTextPadding
+ font_list_
.GetHeight(),
202 2 * kVerticalPadding
+
203 normal_body_image_set_
.top_left
->height() +
204 normal_body_image_set_
.bottom_left
->height());
206 if (DownloadShelf::kSmallProgressIconSize
> box_height_
)
207 box_y_
= (DownloadShelf::kSmallProgressIconSize
- box_height_
) / 2;
211 body_hover_animation_
.reset(new gfx::SlideAnimation(this));
212 drop_hover_animation_
.reset(new gfx::SlideAnimation(this));
214 SetAccessibilityFocusable(true);
216 OnDownloadUpdated(download());
217 UpdateDropDownButtonPosition();
220 DownloadItemView::~DownloadItemView() {
221 StopDownloadProgress();
222 download()->RemoveObserver(this);
225 // Progress animation handlers.
227 void DownloadItemView::UpdateDownloadProgress() {
229 (progress_angle_
+ DownloadShelf::kUnknownIncrementDegrees
) %
230 DownloadShelf::kMaxDegrees
;
234 void DownloadItemView::StartDownloadProgress() {
235 if (progress_timer_
.IsRunning())
237 progress_timer_
.Start(FROM_HERE
,
238 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs
), this,
239 &DownloadItemView::UpdateDownloadProgress
);
242 void DownloadItemView::StopDownloadProgress() {
243 progress_timer_
.Stop();
246 void DownloadItemView::OnExtractIconComplete(gfx::Image
* icon_bitmap
) {
248 shelf_
->SchedulePaint();
251 // DownloadObserver interface.
253 // Update the progress graphic on the icon and our text status label
254 // to reflect our current bytes downloaded, time remaining.
255 void DownloadItemView::OnDownloadUpdated(DownloadItem
* download_item
) {
256 DCHECK_EQ(download(), download_item
);
258 if (IsShowingWarningDialog() && !model_
.IsDangerous()) {
259 // We have been approved.
260 ClearWarningDialog();
261 } else if (!IsShowingWarningDialog() && model_
.IsDangerous()) {
263 // Force the shelf to layout again as our size has changed.
267 base::string16 status_text
= model_
.GetStatusText();
268 switch (download()->GetState()) {
269 case DownloadItem::IN_PROGRESS
:
270 download()->IsPaused() ?
271 StopDownloadProgress() : StartDownloadProgress();
272 LoadIconIfItemPathChanged();
274 case DownloadItem::INTERRUPTED
:
275 StopDownloadProgress();
276 complete_animation_
.reset(new gfx::SlideAnimation(this));
277 complete_animation_
->SetSlideDuration(kInterruptedAnimationDurationMs
);
278 complete_animation_
->SetTweenType(gfx::Tween::LINEAR
);
279 complete_animation_
->Show();
283 case DownloadItem::COMPLETE
:
284 if (model_
.ShouldRemoveFromShelfWhenComplete()) {
285 shelf_
->RemoveDownloadView(this); // This will delete us!
288 StopDownloadProgress();
289 complete_animation_
.reset(new gfx::SlideAnimation(this));
290 complete_animation_
->SetSlideDuration(kCompleteAnimationDurationMs
);
291 complete_animation_
->SetTweenType(gfx::Tween::LINEAR
);
292 complete_animation_
->Show();
296 case DownloadItem::CANCELLED
:
297 StopDownloadProgress();
298 if (complete_animation_
)
299 complete_animation_
->Stop();
305 status_text_
= status_text
;
308 base::string16 new_tip
= model_
.GetTooltipText(font_list_
, kTooltipMaxWidth
);
309 if (new_tip
!= tooltip_text_
) {
310 tooltip_text_
= new_tip
;
311 TooltipTextChanged();
314 UpdateAccessibleName();
316 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
317 // are spaces between each DownloadItemView that the parent is responsible
319 shelf_
->SchedulePaint();
322 void DownloadItemView::OnDownloadDestroyed(DownloadItem
* download
) {
323 shelf_
->RemoveDownloadView(this); // This will delete us!
326 void DownloadItemView::OnDownloadOpened(DownloadItem
* download
) {
327 disabled_while_opening_
= true;
329 base::MessageLoop::current()->PostDelayedTask(
331 base::Bind(&DownloadItemView::Reenable
, weak_ptr_factory_
.GetWeakPtr()),
332 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration
));
334 // Notify our parent.
335 shelf_
->OpenedDownload(this);
340 // In dangerous mode we have to layout our buttons.
341 void DownloadItemView::Layout() {
342 if (IsShowingWarningDialog()) {
343 BodyImageSet
* body_image_set
=
344 (mode_
== DANGEROUS_MODE
) ? &dangerous_mode_body_image_set_
:
345 &malicious_mode_body_image_set_
;
346 int x
= kLeftPadding
+ body_image_set
->top_left
->width() +
347 warning_icon_
->width() + kLabelPadding
;
348 int y
= (height() - dangerous_download_label_
->height()) / 2;
349 dangerous_download_label_
->SetBounds(x
, y
,
350 dangerous_download_label_
->width(),
351 dangerous_download_label_
->height());
352 gfx::Size button_size
= GetButtonSize();
353 x
+= dangerous_download_label_
->width() + kLabelPadding
;
354 y
= (height() - button_size
.height()) / 2;
356 save_button_
->SetBounds(x
, y
, button_size
.width(), button_size
.height());
357 x
+= button_size
.width() + kButtonPadding
;
359 discard_button_
->SetBounds(x
, y
, button_size
.width(), button_size
.height());
360 UpdateColorsFromTheme();
364 gfx::Size
DownloadItemView::GetPreferredSize() {
367 // First, we set the height to the height of two rows or text plus margins.
368 height
= 2 * kVerticalPadding
+ 2 * font_list_
.GetHeight() +
369 kVerticalTextPadding
;
370 // Then we increase the size if the progress icon doesn't fit.
371 height
= std::max
<int>(height
, DownloadShelf::kSmallProgressIconSize
);
373 if (IsShowingWarningDialog()) {
374 BodyImageSet
* body_image_set
=
375 (mode_
== DANGEROUS_MODE
) ? &dangerous_mode_body_image_set_
:
376 &malicious_mode_body_image_set_
;
377 width
= kLeftPadding
+ body_image_set
->top_left
->width();
378 width
+= warning_icon_
->width() + kLabelPadding
;
379 width
+= dangerous_download_label_
->width() + kLabelPadding
;
380 gfx::Size button_size
= GetButtonSize();
381 // Make sure the button fits.
382 height
= std::max
<int>(height
, 2 * kVerticalPadding
+ button_size
.height());
383 // Then we make sure the warning icon fits.
384 height
= std::max
<int>(height
, 2 * kVerticalPadding
+
385 warning_icon_
->height());
387 width
+= button_size
.width() + kButtonPadding
;
388 width
+= button_size
.width();
389 width
+= body_image_set
->top_right
->width();
390 if (mode_
== MALICIOUS_MODE
)
391 width
+= normal_drop_down_image_set_
.top
->width();
393 width
= kLeftPadding
+ normal_body_image_set_
.top_left
->width();
394 width
+= DownloadShelf::kSmallProgressIconSize
;
396 width
+= normal_body_image_set_
.top_right
->width();
397 width
+= normal_drop_down_image_set_
.top
->width();
399 return gfx::Size(width
, height
);
402 // Handle a mouse click and open the context menu if the mouse is
403 // over the drop-down region.
404 bool DownloadItemView::OnMousePressed(const ui::MouseEvent
& event
) {
405 HandlePressEvent(event
, event
.IsOnlyLeftMouseButton());
409 // Handle drag (file copy) operations.
410 bool DownloadItemView::OnMouseDragged(const ui::MouseEvent
& event
) {
411 // Mouse should not activate us in dangerous mode.
412 if (IsShowingWarningDialog())
415 if (!starting_drag_
) {
416 starting_drag_
= true;
417 drag_start_point_
= event
.location();
420 if (download()->GetState() == DownloadItem::COMPLETE
) {
421 IconManager
* im
= g_browser_process
->icon_manager();
422 gfx::Image
* icon
= im
->LookupIconFromFilepath(
423 download()->GetTargetFilePath(), IconLoader::SMALL
);
425 views::Widget
* widget
= GetWidget();
427 download(), icon
, widget
? widget
->GetNativeView() : NULL
);
430 } else if (ExceededDragThreshold(event
.location() - drag_start_point_
)) {
436 void DownloadItemView::OnMouseReleased(const ui::MouseEvent
& event
) {
437 HandleClickEvent(event
, event
.IsOnlyLeftMouseButton());
440 void DownloadItemView::OnMouseCaptureLost() {
441 // Mouse should not activate us in dangerous mode.
442 if (mode_
== DANGEROUS_MODE
)
446 // Starting a drag results in a MouseCaptureLost.
448 starting_drag_
= false;
450 SetState(NORMAL
, NORMAL
);
453 void DownloadItemView::OnMouseMoved(const ui::MouseEvent
& event
) {
454 // Mouse should not activate us in dangerous mode.
455 if (mode_
== DANGEROUS_MODE
)
458 bool on_body
= !InDropDownButtonXCoordinateRange(event
.x());
459 SetState(on_body
? HOT
: NORMAL
, on_body
? NORMAL
: HOT
);
462 void DownloadItemView::OnMouseExited(const ui::MouseEvent
& event
) {
463 // Mouse should not activate us in dangerous mode.
464 if (mode_
== DANGEROUS_MODE
)
467 SetState(NORMAL
, drop_down_pressed_
? PUSHED
: NORMAL
);
470 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent
& event
) {
471 // Key press should not activate us in dangerous mode.
472 if (IsShowingWarningDialog())
475 if (event
.key_code() == ui::VKEY_SPACE
||
476 event
.key_code() == ui::VKEY_RETURN
) {
483 bool DownloadItemView::GetTooltipText(const gfx::Point
& p
,
484 base::string16
* tooltip
) const {
485 if (IsShowingWarningDialog()) {
490 tooltip
->assign(tooltip_text_
);
495 void DownloadItemView::GetAccessibleState(ui::AccessibleViewState
* state
) {
496 state
->name
= accessible_name_
;
497 state
->role
= ui::AccessibilityTypes::ROLE_PUSHBUTTON
;
498 if (model_
.IsDangerous()) {
499 state
->state
= ui::AccessibilityTypes::STATE_UNAVAILABLE
;
501 state
->state
= ui::AccessibilityTypes::STATE_HASPOPUP
;
505 void DownloadItemView::OnThemeChanged() {
506 UpdateColorsFromTheme();
509 void DownloadItemView::OnGestureEvent(ui::GestureEvent
* event
) {
510 if (event
->type() == ui::ET_GESTURE_TAP_DOWN
) {
511 HandlePressEvent(*event
, true);
516 if (event
->type() == ui::ET_GESTURE_TAP
) {
517 HandleClickEvent(*event
, true);
522 SetState(NORMAL
, NORMAL
);
523 views::View::OnGestureEvent(event
);
526 void DownloadItemView::ShowContextMenuForView(View
* source
,
527 const gfx::Point
& point
,
528 ui::MenuSourceType source_type
) {
529 // |point| is in screen coordinates. So convert it to local coordinates first.
530 gfx::Point local_point
= point
;
531 ConvertPointFromScreen(this, &local_point
);
532 ShowContextMenuImpl(local_point
, source_type
);
535 void DownloadItemView::ButtonPressed(views::Button
* sender
,
536 const ui::Event
& event
) {
537 base::TimeDelta warning_duration
;
538 if (!time_download_warning_shown_
.is_null())
539 warning_duration
= base::Time::Now() - time_download_warning_shown_
;
541 if (save_button_
&& sender
== save_button_
) {
542 // The user has confirmed a dangerous download. We'd record how quickly the
543 // user did this to detect whether we're being clickjacked.
544 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration
);
545 // This will change the state and notify us.
546 download()->ValidateDangerousDownload();
550 // WARNING: all end states after this point delete |this|.
551 DCHECK_EQ(discard_button_
, sender
);
552 if (model_
.IsMalicious()) {
553 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration
);
554 shelf_
->RemoveDownloadView(this);
557 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration
);
558 if (model_
.ShouldAllowDownloadFeedback() &&
559 !shelf_
->browser()->profile()->IsOffTheRecord()) {
560 if (!shelf_
->browser()->profile()->GetPrefs()->HasPrefPath(
561 prefs::kSafeBrowsingDownloadFeedbackEnabled
)) {
562 // Show dialog, because the dialog hasn't been shown before.
563 DownloadFeedbackDialogView::Show(
564 shelf_
->get_parent()->GetNativeWindow(),
565 shelf_
->browser()->profile(),
567 &DownloadItemView::PossiblySubmitDownloadToFeedbackService
,
568 weak_ptr_factory_
.GetWeakPtr()));
570 PossiblySubmitDownloadToFeedbackService(
571 shelf_
->browser()->profile()->GetPrefs()->GetBoolean(
572 prefs::kSafeBrowsingDownloadFeedbackEnabled
));
576 download()->Remove();
579 void DownloadItemView::AnimationProgressed(const gfx::Animation
* animation
) {
580 // We don't care if what animation (body button/drop button/complete),
581 // is calling back, as they all have to go through the same paint call.
585 void DownloadItemView::OnPaint(gfx::Canvas
* canvas
) {
586 OnPaintBackground(canvas
);
588 canvas
->DrawFocusRect(GetLocalBounds());
591 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
592 // and MALICIOUS_MODE).
594 // NORMAL_MODE: We are displaying an in-progress or completed download.
595 // .-------------------------------+-.
596 // | [icon] Filename |v|
598 // `-------------------------------+-'
599 // | | \_ Drop down button. Invokes menu. Responds
600 // | | to mouse. (NORMAL, HOT or PUSHED).
601 // | \_ Icon is overlaid on top of in-progress animation.
602 // \_ Both the body and the drop down button respond to mouse hover and can be
603 // pushed (NORMAL, HOT or PUSHED).
605 // DANGEROUS_MODE: The file could be potentially dangerous.
606 // .-------------------------------------------------------.
607 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
608 // | [ ] [destroy your computer..] [ ] [ ] |
609 // `-------------------------------------------------------'
610 // | | | | \_ No drop down button.
611 // | | | \_ Buttons are views::LabelButtons.
612 // | | \_ Text is in a label (dangerous_download_label_)
613 // | \_ Warning icon. No progress animation.
614 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
616 // MALICIOUS_MODE: The file is known malware.
617 // .---------------------------------------------+-.
618 // | [ - ] [This file is malicious.] [ Discard ] |v|
619 // | [ ] [ ] [ ] | |-.
620 // `---------------------------------------------+-' |
621 // | | | | Drop down button. Responds to
622 // | | | | mouse.(NORMAL, HOT or PUSHED)
623 // | | | \_ Button is a views::LabelButton.
624 // | | \_ Text is in a label (dangerous_download_label_)
625 // | \_ Warning icon. No progress animation.
626 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
628 void DownloadItemView::OnPaintBackground(gfx::Canvas
* canvas
) {
629 BodyImageSet
* body_image_set
= NULL
;
632 if (body_state_
== PUSHED
)
633 body_image_set
= &pushed_body_image_set_
;
634 else // NORMAL or HOT
635 body_image_set
= &normal_body_image_set_
;
638 body_image_set
= &dangerous_mode_body_image_set_
;
641 body_image_set
= &malicious_mode_body_image_set_
;
647 DropDownImageSet
* drop_down_image_set
= NULL
;
651 if (drop_down_state_
== PUSHED
)
652 drop_down_image_set
= &pushed_drop_down_image_set_
;
653 else // NORMAL or HOT
654 drop_down_image_set
= &normal_drop_down_image_set_
;
657 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
658 // drop_down_image_set == NULL.
664 int center_width
= width() - kLeftPadding
-
665 body_image_set
->left
->width() -
666 body_image_set
->right
->width() -
667 (drop_down_image_set
?
668 normal_drop_down_image_set_
.center
->width() :
671 // May be caused by animation.
672 if (center_width
<= 0)
675 // Draw status before button image to effectively lighten text. No status for
677 if (!IsShowingWarningDialog()) {
678 if (!status_text_
.empty()) {
679 int mirrored_x
= GetMirroredXWithWidthInView(
680 DownloadShelf::kSmallProgressIconSize
, kTextWidth
);
681 // Add font_list_.height() to compensate for title, which is drawn later.
682 int y
= box_y_
+ kVerticalPadding
+ font_list_
.GetHeight() +
683 kVerticalTextPadding
;
684 SkColor file_name_color
= GetThemeProvider()->GetColor(
685 ThemeProperties::COLOR_BOOKMARK_TEXT
);
686 // If text is light-on-dark, lightening it alone will do nothing.
687 // Therefore we mute luminance a wee bit before drawing in this case.
688 if (color_utils::RelativeLuminance(file_name_color
) > 0.5)
689 file_name_color
= SkColorSetRGB(
690 static_cast<int>(kDownloadItemLuminanceMod
*
691 SkColorGetR(file_name_color
)),
692 static_cast<int>(kDownloadItemLuminanceMod
*
693 SkColorGetG(file_name_color
)),
694 static_cast<int>(kDownloadItemLuminanceMod
*
695 SkColorGetB(file_name_color
)));
696 canvas
->DrawStringRect(status_text_
, font_list_
, file_name_color
,
697 gfx::Rect(mirrored_x
, y
, kTextWidth
,
698 font_list_
.GetHeight()));
702 // Paint the background images.
703 int x
= kLeftPadding
;
705 if (base::i18n::IsRTL()) {
706 // Since we do not have the mirrored images for
707 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
708 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
709 // for RTL UI, we flip the canvas to draw those images mirrored.
710 // Consequently, we do not need to mirror the x-axis of those images.
711 canvas
->Translate(gfx::Vector2d(width(), 0));
712 canvas
->Scale(-1, 1);
715 body_image_set
->top_left
, body_image_set
->left
,
716 body_image_set
->bottom_left
,
717 x
, box_y_
, box_height_
, body_image_set
->top_left
->width());
718 x
+= body_image_set
->top_left
->width();
720 body_image_set
->top
, body_image_set
->center
,
721 body_image_set
->bottom
,
722 x
, box_y_
, box_height_
, center_width
);
725 body_image_set
->top_right
, body_image_set
->right
,
726 body_image_set
->bottom_right
,
727 x
, box_y_
, box_height_
, body_image_set
->top_right
->width());
729 // Overlay our body hot state. Warning dialogs don't display body a hot state.
730 if (!IsShowingWarningDialog() &&
731 body_hover_animation_
->GetCurrentValue() > 0) {
732 canvas
->SaveLayerAlpha(
733 static_cast<int>(body_hover_animation_
->GetCurrentValue() * 255));
735 int x
= kLeftPadding
;
737 hot_body_image_set_
.top_left
, hot_body_image_set_
.left
,
738 hot_body_image_set_
.bottom_left
,
739 x
, box_y_
, box_height_
, hot_body_image_set_
.top_left
->width());
740 x
+= body_image_set
->top_left
->width();
742 hot_body_image_set_
.top
, hot_body_image_set_
.center
,
743 hot_body_image_set_
.bottom
,
744 x
, box_y_
, box_height_
, center_width
);
747 hot_body_image_set_
.top_right
, hot_body_image_set_
.right
,
748 hot_body_image_set_
.bottom_right
,
749 x
, box_y_
, box_height_
,
750 hot_body_image_set_
.top_right
->width());
754 x
+= body_image_set
->top_right
->width();
756 // Paint the drop-down.
757 if (drop_down_image_set
) {
759 drop_down_image_set
->top
, drop_down_image_set
->center
,
760 drop_down_image_set
->bottom
,
761 x
, box_y_
, box_height_
, drop_down_image_set
->top
->width());
763 // Overlay our drop-down hot state.
764 if (drop_hover_animation_
->GetCurrentValue() > 0) {
765 canvas
->SaveLayerAlpha(
766 static_cast<int>(drop_hover_animation_
->GetCurrentValue() * 255));
769 drop_down_image_set
->top
, drop_down_image_set
->center
,
770 drop_down_image_set
->bottom
,
771 x
, box_y_
, box_height_
, drop_down_image_set
->top
->width());
777 // Restore the canvas to avoid file name etc. text are drawn flipped.
778 // Consequently, the x-axis of following canvas->DrawXXX() method should be
779 // mirrored so the text and images are down in the right positions.
782 // Print the text, left aligned and always print the file extension.
783 // Last value of x was the end of the right image, just before the button.
784 // Note that in dangerous mode we use a label (as the text is multi-line).
785 if (!IsShowingWarningDialog()) {
786 base::string16 filename
;
787 if (!disabled_while_opening_
) {
788 filename
= gfx::ElideFilename(download()->GetFileNameToReportUser(),
789 font_list_
, kTextWidth
);
791 // First, Calculate the download status opening string width.
792 base::string16 status_string
=
793 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING
,
795 int status_string_width
= gfx::GetStringWidth(status_string
, font_list_
);
796 // Then, elide the file name.
797 base::string16 filename_string
=
798 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_
,
799 kTextWidth
- status_string_width
);
800 // Last, concat the whole string.
801 filename
= l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING
,
805 int mirrored_x
= GetMirroredXWithWidthInView(
806 DownloadShelf::kSmallProgressIconSize
, kTextWidth
);
807 SkColor file_name_color
= GetThemeProvider()->GetColor(
808 ThemeProperties::COLOR_BOOKMARK_TEXT
);
810 box_y_
+ (status_text_
.empty() ?
811 ((box_height_
- font_list_
.GetHeight()) / 2) : kVerticalPadding
);
813 // Draw the file's name.
814 canvas
->DrawStringRect(
815 filename
, font_list_
,
816 enabled() ? file_name_color
: kFileNameDisabledColor
,
817 gfx::Rect(mirrored_x
, y
, kTextWidth
, font_list_
.GetHeight()));
821 IconManager
* im
= g_browser_process
->icon_manager();
822 gfx::Image
* image
= im
->LookupIconFromFilepath(
823 download()->GetTargetFilePath(), IconLoader::SMALL
);
824 const gfx::ImageSkia
* icon
= NULL
;
825 if (IsShowingWarningDialog())
826 icon
= warning_icon_
;
828 icon
= image
->ToImageSkia();
830 // We count on the fact that the icon manager will cache the icons and if one
831 // is available, it will be cached here. We *don't* want to request the icon
832 // to be loaded here, since this will also get called if the icon can't be
833 // loaded, in which case LookupIcon will always be NULL. The loading will be
834 // triggered only when we think the status might change.
836 if (!IsShowingWarningDialog()) {
837 DownloadItem::DownloadState state
= download()->GetState();
838 if (state
== DownloadItem::IN_PROGRESS
) {
839 DownloadShelf::PaintDownloadProgress(canvas
,
844 model_
.PercentComplete(),
845 DownloadShelf::SMALL
);
846 } else if (complete_animation_
.get() &&
847 complete_animation_
->is_animating()) {
848 if (state
== DownloadItem::INTERRUPTED
) {
849 DownloadShelf::PaintDownloadInterrupted(
854 complete_animation_
->GetCurrentValue(),
855 DownloadShelf::SMALL
);
857 DCHECK_EQ(DownloadItem::COMPLETE
, state
);
858 DownloadShelf::PaintDownloadComplete(
863 complete_animation_
->GetCurrentValue(),
864 DownloadShelf::SMALL
);
869 // Draw the icon image.
872 if (IsShowingWarningDialog()) {
873 icon_x
= kLeftPadding
+ body_image_set
->top_left
->width();
874 icon_y
= (height() - icon
->height()) / 2;
876 icon_x
= DownloadShelf::kSmallProgressIconOffset
;
877 icon_y
= DownloadShelf::kSmallProgressIconOffset
;
879 icon_x
= GetMirroredXWithWidthInView(icon_x
, icon
->width());
881 canvas
->DrawImageInt(*icon
, icon_x
, icon_y
);
883 // Use an alpha to make the image look disabled.
886 canvas
->DrawImageInt(*icon
, icon_x
, icon_y
, paint
);
891 void DownloadItemView::OnFocus() {
893 // We render differently when focused.
897 void DownloadItemView::OnBlur() {
899 // We render differently when focused.
903 void DownloadItemView::OpenDownload() {
904 DCHECK(!IsShowingWarningDialog());
905 // We're interested in how long it takes users to open downloads. If they
906 // open downloads super quickly, we should be concerned about clickjacking.
907 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
908 base::Time::Now() - creation_time_
);
909 download()->OpenDownload();
910 UpdateAccessibleName();
913 bool DownloadItemView::SubmitDownloadToFeedbackService() {
914 #if defined(FULL_SAFE_BROWSING)
915 SafeBrowsingService
* sb_service
= g_browser_process
->safe_browsing_service();
918 safe_browsing::DownloadProtectionService
* download_protection_service
=
919 sb_service
->download_protection_service();
920 if (!download_protection_service
)
922 download_protection_service
->feedback_service()->BeginFeedbackForDownload(
924 // WARNING: we are deleted at this point. Don't access 'this'.
932 void DownloadItemView::PossiblySubmitDownloadToFeedbackService(bool enabled
) {
933 if (!enabled
|| !SubmitDownloadToFeedbackService())
934 download()->Remove();
935 // WARNING: 'this' is deleted at this point. Don't access 'this'.
938 void DownloadItemView::LoadIcon() {
939 IconManager
* im
= g_browser_process
->icon_manager();
940 last_download_item_path_
= download()->GetTargetFilePath();
941 im
->LoadIcon(last_download_item_path_
,
943 base::Bind(&DownloadItemView::OnExtractIconComplete
,
944 base::Unretained(this)),
945 &cancelable_task_tracker_
);
948 void DownloadItemView::LoadIconIfItemPathChanged() {
949 base::FilePath current_download_path
= download()->GetTargetFilePath();
950 if (last_download_item_path_
== current_download_path
)
956 void DownloadItemView::UpdateColorsFromTheme() {
957 if (dangerous_download_label_
&& GetThemeProvider()) {
958 dangerous_download_label_
->SetEnabledColor(
959 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT
));
963 void DownloadItemView::ShowContextMenuImpl(const gfx::Point
& p
,
964 ui::MenuSourceType source_type
) {
965 gfx::Point point
= p
;
968 // Similar hack as in MenuButton.
969 // We're about to show the menu from a mouse press. By showing from the
970 // mouse press event we block RootView in mouse dispatching. This also
971 // appears to cause RootView to get a mouse pressed BEFORE the mouse
972 // release is seen, which means RootView sends us another mouse press no
973 // matter where the user pressed. To force RootView to recalculate the
974 // mouse target during the mouse press we explicitly set the mouse handler
976 static_cast<views::internal::RootView
*>(GetWidget()->GetRootView())->
977 SetMouseHandler(NULL
);
979 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
980 // to drop down arrow button.
981 if (source_type
!= ui::MENU_SOURCE_MOUSE
&&
982 source_type
!= ui::MENU_SOURCE_TOUCH
) {
983 drop_down_pressed_
= true;
984 SetState(NORMAL
, PUSHED
);
985 point
.SetPoint(drop_down_x_left_
, box_y_
);
986 size
.SetSize(drop_down_x_right_
- drop_down_x_left_
, box_height_
);
988 // Post a task to release the button. When we call the Run method on the menu
989 // below, it runs an inner message loop that might cause us to be deleted.
990 // Posting a task with a WeakPtr lets us safely handle the button release.
991 base::MessageLoop::current()->PostNonNestableTask(
993 base::Bind(&DownloadItemView::ReleaseDropDown
,
994 weak_ptr_factory_
.GetWeakPtr()));
995 views::View::ConvertPointToScreen(this, &point
);
997 if (!context_menu_
.get()) {
999 new DownloadShelfContextMenuView(download(), shelf_
->GetNavigator()));
1001 context_menu_
->Run(GetWidget()->GetTopLevelWidget(),
1002 gfx::Rect(point
, size
), source_type
);
1003 // We could be deleted now.
1006 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent
& event
,
1007 bool active_event
) {
1008 // The event should not activate us in dangerous mode.
1009 if (mode_
== DANGEROUS_MODE
)
1012 // Stop any completion animation.
1013 if (complete_animation_
.get() && complete_animation_
->is_animating())
1014 complete_animation_
->End();
1017 if (InDropDownButtonXCoordinateRange(event
.x())) {
1018 if (context_menu_
.get()) {
1019 // Ignore two close clicks. This typically happens when the user clicks
1020 // the button to close the menu.
1021 base::TimeDelta delta
=
1022 base::TimeTicks::Now() - context_menu_
->close_time();
1023 if (delta
.InMilliseconds() < views::kMinimumMsBetweenButtonClicks
)
1026 drop_down_pressed_
= true;
1027 SetState(NORMAL
, PUSHED
);
1028 // We are setting is_mouse_gesture to false when calling ShowContextMenu
1029 // so that the positioning of the context menu will be similar to a
1030 // keyboard invocation. I.e. we want the menu to always be positioned
1031 // next to the drop down button instead of the next to the pointer.
1032 ShowContextMenuImpl(event
.location(), ui::MENU_SOURCE_KEYBOARD
);
1033 // Once called, it is possible that *this was deleted (e.g.: due to
1034 // invoking the 'Discard' action.)
1035 } else if (!IsShowingWarningDialog()) {
1036 SetState(PUSHED
, NORMAL
);
1041 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent
& event
,
1042 bool active_event
) {
1043 // Mouse should not activate us in dangerous mode.
1044 if (mode_
== DANGEROUS_MODE
)
1048 !InDropDownButtonXCoordinateRange(event
.x()) &&
1049 !IsShowingWarningDialog()) {
1053 SetState(NORMAL
, NORMAL
);
1056 // Load an icon for the file type we're downloading, and animate any in progress
1058 void DownloadItemView::PaintImages(gfx::Canvas
* canvas
,
1059 const gfx::ImageSkia
* top_image
,
1060 const gfx::ImageSkia
* center_image
,
1061 const gfx::ImageSkia
* bottom_image
,
1062 int x
, int y
, int height
, int width
) {
1063 int middle_height
= height
- top_image
->height() - bottom_image
->height();
1065 canvas
->DrawImageInt(*top_image
,
1066 0, 0, top_image
->width(), top_image
->height(),
1067 x
, y
, width
, top_image
->height(), false);
1068 y
+= top_image
->height();
1070 canvas
->DrawImageInt(*center_image
,
1071 0, 0, center_image
->width(), center_image
->height(),
1072 x
, y
, width
, middle_height
, false);
1075 canvas
->DrawImageInt(*bottom_image
,
1076 0, 0, bottom_image
->width(), bottom_image
->height(),
1077 x
, y
, width
, bottom_image
->height(), false);
1080 void DownloadItemView::SetState(State new_body_state
, State new_drop_state
) {
1081 // If we are showing a warning dialog, we don't change body state.
1082 if (IsShowingWarningDialog()) {
1083 new_body_state
= NORMAL
;
1085 // Current body_state_ should always be NORMAL for warning dialogs.
1086 DCHECK_EQ(NORMAL
, body_state_
);
1087 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1088 DCHECK_NE(DANGEROUS_MODE
, mode_
);
1090 // Avoid extra SchedulePaint()s if the state is going to be the same.
1091 if (body_state_
== new_body_state
&& drop_down_state_
== new_drop_state
)
1094 AnimateStateTransition(body_state_
, new_body_state
,
1095 body_hover_animation_
.get());
1096 AnimateStateTransition(drop_down_state_
, new_drop_state
,
1097 drop_hover_animation_
.get());
1098 body_state_
= new_body_state
;
1099 drop_down_state_
= new_drop_state
;
1103 void DownloadItemView::ClearWarningDialog() {
1104 DCHECK(download()->GetDangerType() ==
1105 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED
);
1106 DCHECK(mode_
== DANGEROUS_MODE
|| mode_
== MALICIOUS_MODE
);
1108 mode_
= NORMAL_MODE
;
1109 body_state_
= NORMAL
;
1110 drop_down_state_
= NORMAL
;
1112 // Remove the views used by the warning dialog.
1114 RemoveChildView(save_button_
);
1115 delete save_button_
;
1116 save_button_
= NULL
;
1118 RemoveChildView(discard_button_
);
1119 delete discard_button_
;
1120 discard_button_
= NULL
;
1121 RemoveChildView(dangerous_download_label_
);
1122 delete dangerous_download_label_
;
1123 dangerous_download_label_
= NULL
;
1124 dangerous_download_label_sized_
= false;
1125 cached_button_size_
.SetSize(0,0);
1127 // Set the accessible name back to the status and filename instead of the
1128 // download warning.
1129 UpdateAccessibleName();
1130 UpdateDropDownButtonPosition();
1132 // We need to load the icon now that the download has the real path.
1135 // Force the shelf to layout again as our size has changed.
1137 shelf_
->SchedulePaint();
1139 TooltipTextChanged();
1142 void DownloadItemView::ShowWarningDialog() {
1143 DCHECK(mode_
!= DANGEROUS_MODE
&& mode_
!= MALICIOUS_MODE
);
1144 time_download_warning_shown_
= base::Time::Now();
1145 content::DownloadDangerType danger_type
= download()->GetDangerType();
1146 RecordDangerousDownloadWarningShown(danger_type
);
1147 #if defined(FULL_SAFE_BROWSING)
1148 if (model_
.ShouldAllowDownloadFeedback()) {
1149 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1153 mode_
= model_
.MightBeMalicious() ? MALICIOUS_MODE
: DANGEROUS_MODE
;
1155 body_state_
= NORMAL
;
1156 drop_down_state_
= NORMAL
;
1157 if (mode_
== DANGEROUS_MODE
) {
1158 save_button_
= new views::LabelButton(
1159 this, model_
.GetWarningConfirmButtonText());
1160 save_button_
->SetStyle(views::Button::STYLE_BUTTON
);
1161 AddChildView(save_button_
);
1163 int discard_button_message
= model_
.IsMalicious() ?
1164 IDS_DISMISS_DOWNLOAD
: IDS_DISCARD_DOWNLOAD
;
1165 discard_button_
= new views::LabelButton(
1166 this, l10n_util::GetStringUTF16(discard_button_message
));
1167 discard_button_
->SetStyle(views::Button::STYLE_BUTTON
);
1168 AddChildView(discard_button_
);
1170 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1171 switch (danger_type
) {
1172 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL
:
1173 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT
:
1174 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT
:
1175 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST
:
1176 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED
:
1177 warning_icon_
= rb
.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING
);
1180 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS
:
1181 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT
:
1182 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED
:
1183 case content::DOWNLOAD_DANGER_TYPE_MAX
:
1187 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE
:
1188 warning_icon_
= rb
.GetImageSkiaNamed(IDR_WARNING
);
1190 base::string16 dangerous_label
=
1191 model_
.GetWarningText(font_list_
, kTextWidth
);
1192 dangerous_download_label_
= new views::Label(dangerous_label
);
1193 dangerous_download_label_
->SetMultiLine(true);
1194 dangerous_download_label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1195 dangerous_download_label_
->SetAutoColorReadabilityEnabled(false);
1196 AddChildView(dangerous_download_label_
);
1197 SizeLabelToMinWidth();
1198 UpdateDropDownButtonPosition();
1199 TooltipTextChanged();
1202 gfx::Size
DownloadItemView::GetButtonSize() {
1203 DCHECK(discard_button_
&& (mode_
== MALICIOUS_MODE
|| save_button_
));
1206 // We cache the size when successfully retrieved, not for performance reasons
1207 // but because if this DownloadItemView is being animated while the tab is
1208 // not showing, the native buttons are not parented and their preferred size
1209 // is 0, messing-up the layout.
1210 if (cached_button_size_
.width() != 0)
1211 return cached_button_size_
;
1214 size
= save_button_
->GetMinimumSize();
1215 gfx::Size discard_size
= discard_button_
->GetMinimumSize();
1217 size
.SetSize(std::max(size
.width(), discard_size
.width()),
1218 std::max(size
.height(), discard_size
.height()));
1220 if (size
.width() != 0)
1221 cached_button_size_
= size
;
1226 // This method computes the minimum width of the label for displaying its text
1227 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1228 // configuration with minimum width.
1229 void DownloadItemView::SizeLabelToMinWidth() {
1230 if (dangerous_download_label_sized_
)
1233 base::string16 label_text
= dangerous_download_label_
->text();
1234 TrimWhitespace(label_text
, TRIM_ALL
, &label_text
);
1235 DCHECK_EQ(base::string16::npos
, label_text
.find('\n'));
1237 // Make the label big so that GetPreferredSize() is not constrained by the
1239 dangerous_download_label_
->SetBounds(0, 0, 1000, 1000);
1241 // Use a const string from here. BreakIterator requies that text.data() not
1242 // change during its lifetime.
1243 const base::string16
original_text(label_text
);
1244 // Using BREAK_WORD can work in most cases, but it can also break
1245 // lines where it should not. Using BREAK_LINE is safer although
1246 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1247 base::i18n::BreakIterator
iter(original_text
,
1248 base::i18n::BreakIterator::BREAK_LINE
);
1249 bool status
= iter
.Init();
1252 base::string16 prev_text
= original_text
;
1253 gfx::Size size
= dangerous_download_label_
->GetPreferredSize();
1254 int min_width
= size
.width();
1256 // Go through the string and try each line break (starting with no line break)
1257 // searching for the optimal line break position. Stop if we find one that
1258 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1259 // a short string (e.g.: "This file is malicious") from being broken up
1261 while (iter
.Advance() && min_width
> kDangerousTextWidth
) {
1262 size_t pos
= iter
.pos();
1263 if (pos
>= original_text
.length())
1265 base::string16 current_text
= original_text
;
1266 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1267 // return false and inserting a new line after a surrogate pair
1269 base::char16 line_end_char
= current_text
[pos
- 1];
1270 if (u_isUWhiteSpace(line_end_char
))
1271 current_text
.replace(pos
- 1, 1, 1, base::char16('\n'));
1273 current_text
.insert(pos
, 1, base::char16('\n'));
1274 dangerous_download_label_
->SetText(current_text
);
1275 size
= dangerous_download_label_
->GetPreferredSize();
1277 // If the width is growing again, it means we passed the optimal width spot.
1278 if (size
.width() > min_width
) {
1279 dangerous_download_label_
->SetText(prev_text
);
1282 min_width
= size
.width();
1284 prev_text
= current_text
;
1287 dangerous_download_label_
->SetBounds(0, 0, size
.width(), size
.height());
1288 dangerous_download_label_sized_
= true;
1291 void DownloadItemView::Reenable() {
1292 disabled_while_opening_
= false;
1293 SetEnabled(true); // Triggers a repaint.
1296 void DownloadItemView::ReleaseDropDown() {
1297 drop_down_pressed_
= false;
1298 SetState(NORMAL
, NORMAL
);
1301 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x
) {
1302 if (x
> drop_down_x_left_
&& x
< drop_down_x_right_
)
1307 void DownloadItemView::UpdateAccessibleName() {
1308 base::string16 new_name
;
1309 if (IsShowingWarningDialog()) {
1310 new_name
= dangerous_download_label_
->text();
1312 new_name
= status_text_
+ base::char16(' ') +
1313 download()->GetFileNameToReportUser().LossyDisplayName();
1316 // If the name has changed, notify assistive technology that the name
1317 // has changed so they can announce it immediately.
1318 if (new_name
!= accessible_name_
) {
1319 accessible_name_
= new_name
;
1320 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_NAME_CHANGED
, true);
1324 void DownloadItemView::UpdateDropDownButtonPosition() {
1325 gfx::Size size
= GetPreferredSize();
1326 if (base::i18n::IsRTL()) {
1327 // Drop down button is glued to the left of the download shelf.
1328 drop_down_x_left_
= 0;
1329 drop_down_x_right_
= normal_drop_down_image_set_
.top
->width();
1331 // Drop down button is glued to the right of the download shelf.
1333 size
.width() - normal_drop_down_image_set_
.top
->width();
1334 drop_down_x_right_
= size
.width();
1338 void DownloadItemView::AnimateStateTransition(State from
, State to
,
1339 gfx::SlideAnimation
* animation
) {
1340 if (from
== NORMAL
&& to
== HOT
) {
1342 } else if (from
== HOT
&& to
== NORMAL
) {
1344 } else if (from
!= to
) {
1345 animation
->Reset((to
== HOT
) ? 1.0 : 0.0);