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/accessibility/ax_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 using content::DownloadItem;
58 // TODO(paulg): These may need to be adjusted when download progress
59 // animation is added, and also possibly to take into account
60 // different screen resolutions.
61 static const int kTextWidth = 140; // Pixels
62 static const int kDangerousTextWidth = 200; // Pixels
63 static const int kVerticalPadding = 3; // Pixels
64 static const int kVerticalTextPadding = 2; // Pixels
65 static const int kTooltipMaxWidth = 800; // Pixels
67 // We add some padding before the left image so that the progress animation icon
68 // hides the corners of the left image.
69 static const int kLeftPadding = 0; // Pixels.
71 // The space between the Save and Discard buttons when prompting for a dangerous
73 static const int kButtonPadding = 5; // Pixels.
75 // The space on the left and right side of the dangerous download label.
76 static const int kLabelPadding = 4; // Pixels.
78 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
80 // How long the 'download complete' animation should last for.
81 static const int kCompleteAnimationDurationMs = 2500;
83 // How long the 'download interrupted' animation should last for.
84 static const int kInterruptedAnimationDurationMs = 2500;
86 // How long we keep the item disabled after the user clicked it to open the
88 static const int kDisabledOnOpenDuration = 3000;
90 // Darken light-on-dark download status text by 20% before drawing, thus
91 // creating a "muted" version of title text for both dark-on-light and
92 // light-on-dark themes.
93 static const double kDownloadItemLuminanceMod = 0.8;
97 // Callback for DownloadShelf paint functions to mirror the progress animation
99 void RTLMirrorXForView(views::View* containing_view, gfx::Rect* bounds) {
100 bounds->set_x(containing_view->GetMirroredXForRect(*bounds));
105 DownloadItemView::DownloadItemView(DownloadItem* download_item,
106 DownloadShelfView* parent)
107 : warning_icon_(NULL),
109 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
111 drop_down_state_(NORMAL),
113 progress_angle_(DownloadShelf::kStartAngleDegrees),
114 drop_down_pressed_(false),
116 starting_drag_(false),
117 model_(download_item),
119 discard_button_(NULL),
120 dangerous_download_label_(NULL),
121 dangerous_download_label_sized_(false),
122 disabled_while_opening_(false),
123 creation_time_(base::Time::Now()),
124 time_download_warning_shown_(base::Time()),
125 weak_ptr_factory_(this) {
127 download()->AddObserver(this);
128 set_context_menu_controller(this);
130 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
132 BodyImageSet normal_body_image_set = {
133 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
134 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
135 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
136 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
137 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
138 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
139 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
140 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
141 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
143 normal_body_image_set_ = normal_body_image_set;
145 DropDownImageSet normal_drop_down_image_set = {
146 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
147 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
148 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
150 normal_drop_down_image_set_ = normal_drop_down_image_set;
152 BodyImageSet hot_body_image_set = {
153 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
154 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
155 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
156 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
157 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
158 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
159 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
160 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
161 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
163 hot_body_image_set_ = hot_body_image_set;
165 DropDownImageSet hot_drop_down_image_set = {
166 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
167 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
168 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
170 hot_drop_down_image_set_ = hot_drop_down_image_set;
172 BodyImageSet pushed_body_image_set = {
173 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
174 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
175 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
176 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
177 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
178 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
179 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
180 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
181 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
183 pushed_body_image_set_ = pushed_body_image_set;
185 DropDownImageSet pushed_drop_down_image_set = {
186 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
187 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
188 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
190 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
192 BodyImageSet dangerous_mode_body_image_set = {
193 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
194 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
195 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
196 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
197 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
198 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
199 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
200 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
201 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
203 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
205 malicious_mode_body_image_set_ = normal_body_image_set;
209 font_list_ = rb.GetFontList(ui::ResourceBundle::BaseFont);
210 box_height_ = std::max<int>(2 * kVerticalPadding + font_list_.GetHeight() +
211 kVerticalTextPadding + font_list_.GetHeight(),
212 2 * kVerticalPadding +
213 normal_body_image_set_.top_left->height() +
214 normal_body_image_set_.bottom_left->height());
216 if (DownloadShelf::kSmallProgressIconSize > box_height_)
217 box_y_ = (DownloadShelf::kSmallProgressIconSize - box_height_) / 2;
221 body_hover_animation_.reset(new gfx::SlideAnimation(this));
222 drop_hover_animation_.reset(new gfx::SlideAnimation(this));
224 SetAccessibilityFocusable(true);
226 OnDownloadUpdated(download());
227 UpdateDropDownButtonPosition();
230 DownloadItemView::~DownloadItemView() {
231 StopDownloadProgress();
232 download()->RemoveObserver(this);
235 // Progress animation handlers.
237 void DownloadItemView::UpdateDownloadProgress() {
239 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
240 DownloadShelf::kMaxDegrees;
244 void DownloadItemView::StartDownloadProgress() {
245 if (progress_timer_.IsRunning())
247 progress_timer_.Start(FROM_HERE,
248 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs), this,
249 &DownloadItemView::UpdateDownloadProgress);
252 void DownloadItemView::StopDownloadProgress() {
253 progress_timer_.Stop();
256 void DownloadItemView::OnExtractIconComplete(gfx::Image* icon_bitmap) {
258 shelf_->SchedulePaint();
261 // DownloadObserver interface.
263 // Update the progress graphic on the icon and our text status label
264 // to reflect our current bytes downloaded, time remaining.
265 void DownloadItemView::OnDownloadUpdated(DownloadItem* download_item) {
266 DCHECK_EQ(download(), download_item);
268 if (IsShowingWarningDialog() && !model_.IsDangerous()) {
269 // We have been approved.
270 ClearWarningDialog();
271 } else if (!IsShowingWarningDialog() && model_.IsDangerous()) {
273 // Force the shelf to layout again as our size has changed.
277 base::string16 status_text = model_.GetStatusText();
278 switch (download()->GetState()) {
279 case DownloadItem::IN_PROGRESS:
280 download()->IsPaused() ?
281 StopDownloadProgress() : StartDownloadProgress();
282 LoadIconIfItemPathChanged();
284 case DownloadItem::INTERRUPTED:
285 StopDownloadProgress();
286 complete_animation_.reset(new gfx::SlideAnimation(this));
287 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
288 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
289 complete_animation_->Show();
293 case DownloadItem::COMPLETE:
294 if (model_.ShouldRemoveFromShelfWhenComplete()) {
295 shelf_->RemoveDownloadView(this); // This will delete us!
298 StopDownloadProgress();
299 complete_animation_.reset(new gfx::SlideAnimation(this));
300 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
301 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
302 complete_animation_->Show();
306 case DownloadItem::CANCELLED:
307 StopDownloadProgress();
308 if (complete_animation_)
309 complete_animation_->Stop();
315 status_text_ = status_text;
318 base::string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth);
319 if (new_tip != tooltip_text_) {
320 tooltip_text_ = new_tip;
321 TooltipTextChanged();
324 UpdateAccessibleName();
326 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
327 // are spaces between each DownloadItemView that the parent is responsible
329 shelf_->SchedulePaint();
332 void DownloadItemView::OnDownloadDestroyed(DownloadItem* download) {
333 shelf_->RemoveDownloadView(this); // This will delete us!
336 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
337 disabled_while_opening_ = true;
339 base::MessageLoop::current()->PostDelayedTask(
341 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
342 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
344 // Notify our parent.
345 shelf_->OpenedDownload(this);
350 // In dangerous mode we have to layout our buttons.
351 void DownloadItemView::Layout() {
352 if (IsShowingWarningDialog()) {
353 BodyImageSet* body_image_set =
354 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
355 &malicious_mode_body_image_set_;
356 int x = kLeftPadding + body_image_set->top_left->width() +
357 warning_icon_->width() + kLabelPadding;
358 int y = (height() - dangerous_download_label_->height()) / 2;
359 dangerous_download_label_->SetBounds(x, y,
360 dangerous_download_label_->width(),
361 dangerous_download_label_->height());
362 gfx::Size button_size = GetButtonSize();
363 x += dangerous_download_label_->width() + kLabelPadding;
364 y = (height() - button_size.height()) / 2;
366 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
367 x += button_size.width() + kButtonPadding;
369 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
370 UpdateColorsFromTheme();
374 gfx::Size DownloadItemView::GetPreferredSize() const {
377 // First, we set the height to the height of two rows or text plus margins.
378 height = 2 * kVerticalPadding + 2 * font_list_.GetHeight() +
379 kVerticalTextPadding;
380 // Then we increase the size if the progress icon doesn't fit.
381 height = std::max<int>(height, DownloadShelf::kSmallProgressIconSize);
383 if (IsShowingWarningDialog()) {
384 const BodyImageSet* body_image_set =
385 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
386 &malicious_mode_body_image_set_;
387 width = kLeftPadding + body_image_set->top_left->width();
388 width += warning_icon_->width() + kLabelPadding;
389 width += dangerous_download_label_->width() + kLabelPadding;
390 gfx::Size button_size = GetButtonSize();
391 // Make sure the button fits.
392 height = std::max<int>(height, 2 * kVerticalPadding + button_size.height());
393 // Then we make sure the warning icon fits.
394 height = std::max<int>(height, 2 * kVerticalPadding +
395 warning_icon_->height());
397 width += button_size.width() + kButtonPadding;
398 width += button_size.width();
399 width += body_image_set->top_right->width();
400 if (mode_ == MALICIOUS_MODE)
401 width += normal_drop_down_image_set_.top->width();
403 width = kLeftPadding + normal_body_image_set_.top_left->width();
404 width += DownloadShelf::kSmallProgressIconSize;
406 width += normal_body_image_set_.top_right->width();
407 width += normal_drop_down_image_set_.top->width();
409 return gfx::Size(width, height);
412 // Handle a mouse click and open the context menu if the mouse is
413 // over the drop-down region.
414 bool DownloadItemView::OnMousePressed(const ui::MouseEvent& event) {
415 HandlePressEvent(event, event.IsOnlyLeftMouseButton());
419 // Handle drag (file copy) operations.
420 bool DownloadItemView::OnMouseDragged(const ui::MouseEvent& event) {
421 // Mouse should not activate us in dangerous mode.
422 if (IsShowingWarningDialog())
425 if (!starting_drag_) {
426 starting_drag_ = true;
427 drag_start_point_ = event.location();
430 if (download()->GetState() == DownloadItem::COMPLETE) {
431 IconManager* im = g_browser_process->icon_manager();
432 gfx::Image* icon = im->LookupIconFromFilepath(
433 download()->GetTargetFilePath(), IconLoader::SMALL);
434 views::Widget* widget = GetWidget();
436 download(), icon, widget ? widget->GetNativeView() : NULL);
438 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
444 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
445 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
448 void DownloadItemView::OnMouseCaptureLost() {
449 // Mouse should not activate us in dangerous mode.
450 if (mode_ == DANGEROUS_MODE)
454 // Starting a drag results in a MouseCaptureLost.
456 starting_drag_ = false;
458 SetState(NORMAL, NORMAL);
461 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
462 // Mouse should not activate us in dangerous mode.
463 if (mode_ == DANGEROUS_MODE)
466 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
467 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
470 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
471 // Mouse should not activate us in dangerous mode.
472 if (mode_ == DANGEROUS_MODE)
475 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
478 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
479 // Key press should not activate us in dangerous mode.
480 if (IsShowingWarningDialog())
483 if (event.key_code() == ui::VKEY_SPACE ||
484 event.key_code() == ui::VKEY_RETURN) {
485 // OpenDownload may delete this, so don't add any code after this line.
492 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
493 base::string16* tooltip) const {
494 if (IsShowingWarningDialog()) {
499 tooltip->assign(tooltip_text_);
504 void DownloadItemView::GetAccessibleState(ui::AXViewState* state) {
505 state->name = accessible_name_;
506 state->role = ui::AX_ROLE_BUTTON;
507 if (model_.IsDangerous())
508 state->AddStateFlag(ui::AX_STATE_DISABLED);
510 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
513 void DownloadItemView::OnThemeChanged() {
514 UpdateColorsFromTheme();
517 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
518 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
519 HandlePressEvent(*event, true);
524 if (event->type() == ui::ET_GESTURE_TAP) {
525 HandleClickEvent(*event, true);
530 SetState(NORMAL, NORMAL);
531 views::View::OnGestureEvent(event);
534 void DownloadItemView::ShowContextMenuForView(View* source,
535 const gfx::Point& point,
536 ui::MenuSourceType source_type) {
537 // |point| is in screen coordinates. So convert it to local coordinates first.
538 gfx::Point local_point = point;
539 ConvertPointFromScreen(this, &local_point);
540 ShowContextMenuImpl(local_point, source_type);
543 void DownloadItemView::ButtonPressed(views::Button* sender,
544 const ui::Event& event) {
545 base::TimeDelta warning_duration;
546 if (!time_download_warning_shown_.is_null())
547 warning_duration = base::Time::Now() - time_download_warning_shown_;
549 if (save_button_ && sender == save_button_) {
550 // The user has confirmed a dangerous download. We'd record how quickly the
551 // user did this to detect whether we're being clickjacked.
552 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
553 // This will change the state and notify us.
554 download()->ValidateDangerousDownload();
558 // WARNING: all end states after this point delete |this|.
559 DCHECK_EQ(discard_button_, sender);
560 if (model_.IsMalicious()) {
561 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
562 shelf_->RemoveDownloadView(this);
565 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration);
566 if (model_.ShouldAllowDownloadFeedback() &&
567 !shelf_->browser()->profile()->IsOffTheRecord()) {
568 if (!shelf_->browser()->profile()->GetPrefs()->HasPrefPath(
569 prefs::kSafeBrowsingExtendedReportingEnabled)) {
570 // Show dialog, because the dialog hasn't been shown before.
571 DownloadFeedbackDialogView::Show(
572 shelf_->get_parent()->GetNativeWindow(),
573 shelf_->browser()->profile(),
574 shelf_->GetNavigator(),
576 &DownloadItemView::PossiblySubmitDownloadToFeedbackService,
577 weak_ptr_factory_.GetWeakPtr()));
579 PossiblySubmitDownloadToFeedbackService(
580 shelf_->browser()->profile()->GetPrefs()->GetBoolean(
581 prefs::kSafeBrowsingExtendedReportingEnabled));
585 download()->Remove();
588 void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) {
589 // We don't care if what animation (body button/drop button/complete),
590 // is calling back, as they all have to go through the same paint call.
594 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
595 OnPaintBackground(canvas);
597 canvas->DrawFocusRect(GetLocalBounds());
600 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
601 // and MALICIOUS_MODE).
603 // NORMAL_MODE: We are displaying an in-progress or completed download.
604 // .-------------------------------+-.
605 // | [icon] Filename |v|
607 // `-------------------------------+-'
608 // | | \_ Drop down button. Invokes menu. Responds
609 // | | to mouse. (NORMAL, HOT or PUSHED).
610 // | \_ Icon is overlaid on top of in-progress animation.
611 // \_ Both the body and the drop down button respond to mouse hover and can be
612 // pushed (NORMAL, HOT or PUSHED).
614 // DANGEROUS_MODE: The file could be potentially dangerous.
615 // .-------------------------------------------------------.
616 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
617 // | [ ] [destroy your computer..] [ ] [ ] |
618 // `-------------------------------------------------------'
619 // | | | | \_ No drop down button.
620 // | | | \_ Buttons are views::LabelButtons.
621 // | | \_ Text is in a label (dangerous_download_label_)
622 // | \_ Warning icon. No progress animation.
623 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
625 // MALICIOUS_MODE: The file is known malware.
626 // .---------------------------------------------+-.
627 // | [ - ] [This file is malicious.] [ Discard ] |v|
628 // | [ ] [ ] [ ] | |-.
629 // `---------------------------------------------+-' |
630 // | | | | Drop down button. Responds to
631 // | | | | mouse.(NORMAL, HOT or PUSHED)
632 // | | | \_ Button is a views::LabelButton.
633 // | | \_ Text is in a label (dangerous_download_label_)
634 // | \_ Warning icon. No progress animation.
635 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
637 void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) {
638 BodyImageSet* body_image_set = NULL;
641 if (body_state_ == PUSHED)
642 body_image_set = &pushed_body_image_set_;
643 else // NORMAL or HOT
644 body_image_set = &normal_body_image_set_;
647 body_image_set = &dangerous_mode_body_image_set_;
650 body_image_set = &malicious_mode_body_image_set_;
656 DropDownImageSet* drop_down_image_set = NULL;
660 if (drop_down_state_ == PUSHED)
661 drop_down_image_set = &pushed_drop_down_image_set_;
662 else // NORMAL or HOT
663 drop_down_image_set = &normal_drop_down_image_set_;
666 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
667 // drop_down_image_set == NULL.
673 int center_width = width() - kLeftPadding -
674 body_image_set->left->width() -
675 body_image_set->right->width() -
676 (drop_down_image_set ?
677 normal_drop_down_image_set_.center->width() :
680 // May be caused by animation.
681 if (center_width <= 0)
684 // Draw status before button image to effectively lighten text. No status for
686 if (!IsShowingWarningDialog()) {
687 if (!status_text_.empty()) {
688 int mirrored_x = GetMirroredXWithWidthInView(
689 DownloadShelf::kSmallProgressIconSize, kTextWidth);
690 // Add font_list_.height() to compensate for title, which is drawn later.
691 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() +
692 kVerticalTextPadding;
693 SkColor file_name_color = GetThemeProvider()->GetColor(
694 ThemeProperties::COLOR_BOOKMARK_TEXT);
695 // If text is light-on-dark, lightening it alone will do nothing.
696 // Therefore we mute luminance a wee bit before drawing in this case.
697 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
698 file_name_color = SkColorSetRGB(
699 static_cast<int>(kDownloadItemLuminanceMod *
700 SkColorGetR(file_name_color)),
701 static_cast<int>(kDownloadItemLuminanceMod *
702 SkColorGetG(file_name_color)),
703 static_cast<int>(kDownloadItemLuminanceMod *
704 SkColorGetB(file_name_color)));
705 canvas->DrawStringRect(status_text_, font_list_, file_name_color,
706 gfx::Rect(mirrored_x, y, kTextWidth,
707 font_list_.GetHeight()));
711 // Paint the background images.
712 int x = kLeftPadding;
714 if (base::i18n::IsRTL()) {
715 // Since we do not have the mirrored images for
716 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
717 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
718 // for RTL UI, we flip the canvas to draw those images mirrored.
719 // Consequently, we do not need to mirror the x-axis of those images.
720 canvas->Translate(gfx::Vector2d(width(), 0));
721 canvas->Scale(-1, 1);
724 body_image_set->top_left, body_image_set->left,
725 body_image_set->bottom_left,
726 x, box_y_, box_height_, body_image_set->top_left->width());
727 x += body_image_set->top_left->width();
729 body_image_set->top, body_image_set->center,
730 body_image_set->bottom,
731 x, box_y_, box_height_, center_width);
734 body_image_set->top_right, body_image_set->right,
735 body_image_set->bottom_right,
736 x, box_y_, box_height_, body_image_set->top_right->width());
738 // Overlay our body hot state. Warning dialogs don't display body a hot state.
739 if (!IsShowingWarningDialog() &&
740 body_hover_animation_->GetCurrentValue() > 0) {
741 canvas->SaveLayerAlpha(
742 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
744 int x = kLeftPadding;
746 hot_body_image_set_.top_left, hot_body_image_set_.left,
747 hot_body_image_set_.bottom_left,
748 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
749 x += body_image_set->top_left->width();
751 hot_body_image_set_.top, hot_body_image_set_.center,
752 hot_body_image_set_.bottom,
753 x, box_y_, box_height_, center_width);
756 hot_body_image_set_.top_right, hot_body_image_set_.right,
757 hot_body_image_set_.bottom_right,
758 x, box_y_, box_height_,
759 hot_body_image_set_.top_right->width());
763 x += body_image_set->top_right->width();
765 // Paint the drop-down.
766 if (drop_down_image_set) {
768 drop_down_image_set->top, drop_down_image_set->center,
769 drop_down_image_set->bottom,
770 x, box_y_, box_height_, drop_down_image_set->top->width());
772 // Overlay our drop-down hot state.
773 if (drop_hover_animation_->GetCurrentValue() > 0) {
774 canvas->SaveLayerAlpha(
775 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
778 drop_down_image_set->top, drop_down_image_set->center,
779 drop_down_image_set->bottom,
780 x, box_y_, box_height_, drop_down_image_set->top->width());
786 // Restore the canvas to avoid file name etc. text are drawn flipped.
787 // Consequently, the x-axis of following canvas->DrawXXX() method should be
788 // mirrored so the text and images are down in the right positions.
791 // Print the text, left aligned and always print the file extension.
792 // Last value of x was the end of the right image, just before the button.
793 // Note that in dangerous mode we use a label (as the text is multi-line).
794 if (!IsShowingWarningDialog()) {
795 base::string16 filename;
796 if (!disabled_while_opening_) {
797 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(),
798 font_list_, kTextWidth);
800 // First, Calculate the download status opening string width.
801 base::string16 status_string =
802 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
804 int status_string_width = gfx::GetStringWidth(status_string, font_list_);
805 // Then, elide the file name.
806 base::string16 filename_string =
807 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_,
808 kTextWidth - status_string_width);
809 // Last, concat the whole string.
810 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
814 int mirrored_x = GetMirroredXWithWidthInView(
815 DownloadShelf::kSmallProgressIconSize, kTextWidth);
816 SkColor file_name_color = GetThemeProvider()->GetColor(
817 ThemeProperties::COLOR_BOOKMARK_TEXT);
819 box_y_ + (status_text_.empty() ?
820 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
822 // Draw the file's name.
823 canvas->DrawStringRect(
824 filename, font_list_,
825 enabled() ? file_name_color : kFileNameDisabledColor,
826 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight()));
830 IconManager* im = g_browser_process->icon_manager();
831 gfx::Image* image = im->LookupIconFromFilepath(
832 download()->GetTargetFilePath(), IconLoader::SMALL);
833 const gfx::ImageSkia* icon = NULL;
834 if (IsShowingWarningDialog())
835 icon = warning_icon_;
837 icon = image->ToImageSkia();
839 // We count on the fact that the icon manager will cache the icons and if one
840 // is available, it will be cached here. We *don't* want to request the icon
841 // to be loaded here, since this will also get called if the icon can't be
842 // loaded, in which case LookupIcon will always be NULL. The loading will be
843 // triggered only when we think the status might change.
845 if (!IsShowingWarningDialog()) {
846 DownloadItem::DownloadState state = download()->GetState();
847 DownloadShelf::BoundsAdjusterCallback rtl_mirror =
848 base::Bind(&RTLMirrorXForView, base::Unretained(this));
849 if (state == DownloadItem::IN_PROGRESS) {
850 DownloadShelf::PaintDownloadProgress(canvas,
855 model_.PercentComplete(),
856 DownloadShelf::SMALL);
857 } else if (complete_animation_.get() &&
858 complete_animation_->is_animating()) {
859 if (state == DownloadItem::INTERRUPTED) {
860 DownloadShelf::PaintDownloadInterrupted(
865 complete_animation_->GetCurrentValue(),
866 DownloadShelf::SMALL);
868 DCHECK_EQ(DownloadItem::COMPLETE, state);
869 DownloadShelf::PaintDownloadComplete(
874 complete_animation_->GetCurrentValue(),
875 DownloadShelf::SMALL);
880 // Draw the icon image.
883 if (IsShowingWarningDialog()) {
884 icon_x = kLeftPadding + body_image_set->top_left->width();
885 icon_y = (height() - icon->height()) / 2;
887 icon_x = DownloadShelf::kSmallProgressIconOffset;
888 icon_y = DownloadShelf::kSmallProgressIconOffset;
890 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
892 canvas->DrawImageInt(*icon, icon_x, icon_y);
894 // Use an alpha to make the image look disabled.
897 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
902 void DownloadItemView::OnFocus() {
904 // We render differently when focused.
908 void DownloadItemView::OnBlur() {
910 // We render differently when focused.
914 void DownloadItemView::OpenDownload() {
915 DCHECK(!IsShowingWarningDialog());
916 // We're interested in how long it takes users to open downloads. If they
917 // open downloads super quickly, we should be concerned about clickjacking.
918 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
919 base::Time::Now() - creation_time_);
921 UpdateAccessibleName();
923 // Calling download()->OpenDownload may delete this, so this must be
924 // the last thing we do.
925 download()->OpenDownload();
928 bool DownloadItemView::SubmitDownloadToFeedbackService() {
929 #if defined(FULL_SAFE_BROWSING)
930 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
933 safe_browsing::DownloadProtectionService* download_protection_service =
934 sb_service->download_protection_service();
935 if (!download_protection_service)
937 download_protection_service->feedback_service()->BeginFeedbackForDownload(
939 // WARNING: we are deleted at this point. Don't access 'this'.
947 void DownloadItemView::PossiblySubmitDownloadToFeedbackService(bool enabled) {
948 if (!enabled || !SubmitDownloadToFeedbackService())
949 download()->Remove();
950 // WARNING: 'this' is deleted at this point. Don't access 'this'.
953 void DownloadItemView::LoadIcon() {
954 IconManager* im = g_browser_process->icon_manager();
955 last_download_item_path_ = download()->GetTargetFilePath();
956 im->LoadIcon(last_download_item_path_,
958 base::Bind(&DownloadItemView::OnExtractIconComplete,
959 base::Unretained(this)),
960 &cancelable_task_tracker_);
963 void DownloadItemView::LoadIconIfItemPathChanged() {
964 base::FilePath current_download_path = download()->GetTargetFilePath();
965 if (last_download_item_path_ == current_download_path)
971 void DownloadItemView::UpdateColorsFromTheme() {
972 if (dangerous_download_label_ && GetThemeProvider()) {
973 dangerous_download_label_->SetEnabledColor(
974 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
978 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
979 ui::MenuSourceType source_type) {
980 gfx::Point point = p;
983 // Similar hack as in MenuButton.
984 // We're about to show the menu from a mouse press. By showing from the
985 // mouse press event we block RootView in mouse dispatching. This also
986 // appears to cause RootView to get a mouse pressed BEFORE the mouse
987 // release is seen, which means RootView sends us another mouse press no
988 // matter where the user pressed. To force RootView to recalculate the
989 // mouse target during the mouse press we explicitly set the mouse handler
991 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
992 SetMouseHandler(NULL);
994 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
995 // to drop down arrow button.
996 if (source_type != ui::MENU_SOURCE_MOUSE &&
997 source_type != ui::MENU_SOURCE_TOUCH) {
998 drop_down_pressed_ = true;
999 SetState(NORMAL, PUSHED);
1000 point.SetPoint(drop_down_x_left_, box_y_);
1001 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
1003 // Post a task to release the button. When we call the Run method on the menu
1004 // below, it runs an inner message loop that might cause us to be deleted.
1005 // Posting a task with a WeakPtr lets us safely handle the button release.
1006 base::MessageLoop::current()->PostNonNestableTask(
1008 base::Bind(&DownloadItemView::ReleaseDropDown,
1009 weak_ptr_factory_.GetWeakPtr()));
1010 views::View::ConvertPointToScreen(this, &point);
1012 if (!context_menu_.get()) {
1013 context_menu_.reset(
1014 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator()));
1016 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
1017 gfx::Rect(point, size), source_type);
1018 // We could be deleted now.
1021 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event,
1022 bool active_event) {
1023 // The event should not activate us in dangerous mode.
1024 if (mode_ == DANGEROUS_MODE)
1027 // Stop any completion animation.
1028 if (complete_animation_.get() && complete_animation_->is_animating())
1029 complete_animation_->End();
1032 if (InDropDownButtonXCoordinateRange(event.x())) {
1033 if (context_menu_.get()) {
1034 // Ignore two close clicks. This typically happens when the user clicks
1035 // the button to close the menu.
1036 base::TimeDelta delta =
1037 base::TimeTicks::Now() - context_menu_->close_time();
1038 if (delta.InMilliseconds() < views::kMinimumMsBetweenButtonClicks)
1041 drop_down_pressed_ = true;
1042 SetState(NORMAL, PUSHED);
1043 // We are setting is_mouse_gesture to false when calling ShowContextMenu
1044 // so that the positioning of the context menu will be similar to a
1045 // keyboard invocation. I.e. we want the menu to always be positioned
1046 // next to the drop down button instead of the next to the pointer.
1047 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD);
1048 // Once called, it is possible that *this was deleted (e.g.: due to
1049 // invoking the 'Discard' action.)
1050 } else if (!IsShowingWarningDialog()) {
1051 SetState(PUSHED, NORMAL);
1056 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event,
1057 bool active_event) {
1058 // Mouse should not activate us in dangerous mode.
1059 if (mode_ == DANGEROUS_MODE)
1062 SetState(NORMAL, NORMAL);
1064 if (!active_event ||
1065 InDropDownButtonXCoordinateRange(event.x()) ||
1066 IsShowingWarningDialog()) {
1070 // OpenDownload may delete this, so don't add any code after this line.
1074 // Load an icon for the file type we're downloading, and animate any in progress
1076 void DownloadItemView::PaintImages(gfx::Canvas* canvas,
1077 const gfx::ImageSkia* top_image,
1078 const gfx::ImageSkia* center_image,
1079 const gfx::ImageSkia* bottom_image,
1080 int x, int y, int height, int width) {
1081 int middle_height = height - top_image->height() - bottom_image->height();
1083 canvas->DrawImageInt(*top_image,
1084 0, 0, top_image->width(), top_image->height(),
1085 x, y, width, top_image->height(), false);
1086 y += top_image->height();
1088 canvas->DrawImageInt(*center_image,
1089 0, 0, center_image->width(), center_image->height(),
1090 x, y, width, middle_height, false);
1093 canvas->DrawImageInt(*bottom_image,
1094 0, 0, bottom_image->width(), bottom_image->height(),
1095 x, y, width, bottom_image->height(), false);
1098 void DownloadItemView::SetState(State new_body_state, State new_drop_state) {
1099 // If we are showing a warning dialog, we don't change body state.
1100 if (IsShowingWarningDialog()) {
1101 new_body_state = NORMAL;
1103 // Current body_state_ should always be NORMAL for warning dialogs.
1104 DCHECK_EQ(NORMAL, body_state_);
1105 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1106 DCHECK_NE(DANGEROUS_MODE, mode_);
1108 // Avoid extra SchedulePaint()s if the state is going to be the same.
1109 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state)
1112 AnimateStateTransition(body_state_, new_body_state,
1113 body_hover_animation_.get());
1114 AnimateStateTransition(drop_down_state_, new_drop_state,
1115 drop_hover_animation_.get());
1116 body_state_ = new_body_state;
1117 drop_down_state_ = new_drop_state;
1121 void DownloadItemView::ClearWarningDialog() {
1122 DCHECK(download()->GetDangerType() ==
1123 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1124 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1126 mode_ = NORMAL_MODE;
1127 body_state_ = NORMAL;
1128 drop_down_state_ = NORMAL;
1130 // Remove the views used by the warning dialog.
1132 RemoveChildView(save_button_);
1133 delete save_button_;
1134 save_button_ = NULL;
1136 RemoveChildView(discard_button_);
1137 delete discard_button_;
1138 discard_button_ = NULL;
1139 RemoveChildView(dangerous_download_label_);
1140 delete dangerous_download_label_;
1141 dangerous_download_label_ = NULL;
1142 dangerous_download_label_sized_ = false;
1143 cached_button_size_.SetSize(0,0);
1145 // Set the accessible name back to the status and filename instead of the
1146 // download warning.
1147 UpdateAccessibleName();
1148 UpdateDropDownButtonPosition();
1150 // We need to load the icon now that the download has the real path.
1153 // Force the shelf to layout again as our size has changed.
1155 shelf_->SchedulePaint();
1157 TooltipTextChanged();
1160 void DownloadItemView::ShowWarningDialog() {
1161 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
1162 time_download_warning_shown_ = base::Time::Now();
1163 content::DownloadDangerType danger_type = download()->GetDangerType();
1164 RecordDangerousDownloadWarningShown(danger_type);
1165 #if defined(FULL_SAFE_BROWSING)
1166 if (model_.ShouldAllowDownloadFeedback()) {
1167 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1171 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
1173 body_state_ = NORMAL;
1174 drop_down_state_ = NORMAL;
1175 if (mode_ == DANGEROUS_MODE) {
1176 save_button_ = new views::LabelButton(
1177 this, model_.GetWarningConfirmButtonText());
1178 save_button_->SetStyle(views::Button::STYLE_BUTTON);
1179 AddChildView(save_button_);
1181 int discard_button_message = model_.IsMalicious() ?
1182 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD;
1183 discard_button_ = new views::LabelButton(
1184 this, l10n_util::GetStringUTF16(discard_button_message));
1185 discard_button_->SetStyle(views::Button::STYLE_BUTTON);
1186 AddChildView(discard_button_);
1188 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1189 switch (danger_type) {
1190 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
1191 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
1192 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
1193 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
1194 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
1195 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING);
1198 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
1199 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
1200 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
1201 case content::DOWNLOAD_DANGER_TYPE_MAX:
1205 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1206 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
1208 base::string16 dangerous_label =
1209 model_.GetWarningText(font_list_, kTextWidth);
1210 dangerous_download_label_ = new views::Label(dangerous_label);
1211 dangerous_download_label_->SetMultiLine(true);
1212 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1213 dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
1214 AddChildView(dangerous_download_label_);
1215 SizeLabelToMinWidth();
1216 UpdateDropDownButtonPosition();
1217 TooltipTextChanged();
1220 gfx::Size DownloadItemView::GetButtonSize() const {
1221 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
1224 // We cache the size when successfully retrieved, not for performance reasons
1225 // but because if this DownloadItemView is being animated while the tab is
1226 // not showing, the native buttons are not parented and their preferred size
1227 // is 0, messing-up the layout.
1228 if (cached_button_size_.width() != 0)
1229 return cached_button_size_;
1232 size = save_button_->GetMinimumSize();
1233 gfx::Size discard_size = discard_button_->GetMinimumSize();
1235 size.SetSize(std::max(size.width(), discard_size.width()),
1236 std::max(size.height(), discard_size.height()));
1238 if (size.width() != 0)
1239 cached_button_size_ = size;
1244 // This method computes the minimum width of the label for displaying its text
1245 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1246 // configuration with minimum width.
1247 void DownloadItemView::SizeLabelToMinWidth() {
1248 if (dangerous_download_label_sized_)
1251 base::string16 label_text = dangerous_download_label_->text();
1252 base::TrimWhitespace(label_text, base::TRIM_ALL, &label_text);
1253 DCHECK_EQ(base::string16::npos, label_text.find('\n'));
1255 // Make the label big so that GetPreferredSize() is not constrained by the
1257 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1259 // Use a const string from here. BreakIterator requies that text.data() not
1260 // change during its lifetime.
1261 const base::string16 original_text(label_text);
1262 // Using BREAK_WORD can work in most cases, but it can also break
1263 // lines where it should not. Using BREAK_LINE is safer although
1264 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1265 base::i18n::BreakIterator iter(original_text,
1266 base::i18n::BreakIterator::BREAK_LINE);
1267 bool status = iter.Init();
1270 base::string16 prev_text = original_text;
1271 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1272 int min_width = size.width();
1274 // Go through the string and try each line break (starting with no line break)
1275 // searching for the optimal line break position. Stop if we find one that
1276 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1277 // a short string (e.g.: "This file is malicious") from being broken up
1279 while (iter.Advance() && min_width > kDangerousTextWidth) {
1280 size_t pos = iter.pos();
1281 if (pos >= original_text.length())
1283 base::string16 current_text = original_text;
1284 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1285 // return false and inserting a new line after a surrogate pair
1287 base::char16 line_end_char = current_text[pos - 1];
1288 if (u_isUWhiteSpace(line_end_char))
1289 current_text.replace(pos - 1, 1, 1, base::char16('\n'));
1291 current_text.insert(pos, 1, base::char16('\n'));
1292 dangerous_download_label_->SetText(current_text);
1293 size = dangerous_download_label_->GetPreferredSize();
1295 // If the width is growing again, it means we passed the optimal width spot.
1296 if (size.width() > min_width) {
1297 dangerous_download_label_->SetText(prev_text);
1300 min_width = size.width();
1302 prev_text = current_text;
1305 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1306 dangerous_download_label_sized_ = true;
1309 void DownloadItemView::Reenable() {
1310 disabled_while_opening_ = false;
1311 SetEnabled(true); // Triggers a repaint.
1314 void DownloadItemView::ReleaseDropDown() {
1315 drop_down_pressed_ = false;
1316 SetState(NORMAL, NORMAL);
1319 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1320 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1325 void DownloadItemView::UpdateAccessibleName() {
1326 base::string16 new_name;
1327 if (IsShowingWarningDialog()) {
1328 new_name = dangerous_download_label_->text();
1330 new_name = status_text_ + base::char16(' ') +
1331 download()->GetFileNameToReportUser().LossyDisplayName();
1334 // If the name has changed, notify assistive technology that the name
1335 // has changed so they can announce it immediately.
1336 if (new_name != accessible_name_) {
1337 accessible_name_ = new_name;
1338 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true);
1342 void DownloadItemView::UpdateDropDownButtonPosition() {
1343 gfx::Size size = GetPreferredSize();
1344 if (base::i18n::IsRTL()) {
1345 // Drop down button is glued to the left of the download shelf.
1346 drop_down_x_left_ = 0;
1347 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
1349 // Drop down button is glued to the right of the download shelf.
1351 size.width() - normal_drop_down_image_set_.top->width();
1352 drop_down_x_right_ = size.width();
1356 void DownloadItemView::AnimateStateTransition(State from, State to,
1357 gfx::SlideAnimation* animation) {
1358 if (from == NORMAL && to == HOT) {
1360 } else if (from == HOT && to == NORMAL) {
1362 } else if (from != to) {
1363 animation->Reset((to == HOT) ? 1.0 : 0.0);