[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / download / download_item_view.cc
blob4db589a06c096392ab15f12a7b6bdc812f02f9a9
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"
7 #include <algorithm>
8 #include <vector>
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 // 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
70 // download.
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
85 // downloaded item.
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),
98 shelf_(parent),
99 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
100 body_state_(NORMAL),
101 drop_down_state_(NORMAL),
102 mode_(NORMAL_MODE),
103 progress_angle_(DownloadShelf::kStartAngleDegrees),
104 drop_down_pressed_(false),
105 dragging_(false),
106 starting_drag_(false),
107 model_(download_item),
108 save_button_(NULL),
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) {
116 DCHECK(download());
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;
197 LoadIcon();
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;
208 else
209 box_y_ = 0;
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() {
228 progress_angle_ =
229 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
230 DownloadShelf::kMaxDegrees;
231 SchedulePaint();
234 void DownloadItemView::StartDownloadProgress() {
235 if (progress_timer_.IsRunning())
236 return;
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) {
247 if (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()) {
262 ShowWarningDialog();
263 // Force the shelf to layout again as our size has changed.
264 shelf_->Layout();
265 SchedulePaint();
266 } else {
267 base::string16 status_text = model_.GetStatusText();
268 switch (download()->GetState()) {
269 case DownloadItem::IN_PROGRESS:
270 download()->IsPaused() ?
271 StopDownloadProgress() : StartDownloadProgress();
272 LoadIconIfItemPathChanged();
273 break;
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();
280 SchedulePaint();
281 LoadIcon();
282 break;
283 case DownloadItem::COMPLETE:
284 if (model_.ShouldRemoveFromShelfWhenComplete()) {
285 shelf_->RemoveDownloadView(this); // This will delete us!
286 return;
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();
293 SchedulePaint();
294 LoadIcon();
295 break;
296 case DownloadItem::CANCELLED:
297 StopDownloadProgress();
298 if (complete_animation_)
299 complete_animation_->Stop();
300 LoadIcon();
301 break;
302 default:
303 NOTREACHED();
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
318 // for painting.
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;
328 SetEnabled(false);
329 base::MessageLoop::current()->PostDelayedTask(
330 FROM_HERE,
331 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
332 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
334 // Notify our parent.
335 shelf_->OpenedDownload(this);
338 // View overrides
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;
355 if (save_button_) {
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() const {
365 int width, height;
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 const 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());
386 if (save_button_)
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();
392 } else {
393 width = kLeftPadding + normal_body_image_set_.top_left->width();
394 width += DownloadShelf::kSmallProgressIconSize;
395 width += kTextWidth;
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());
406 return true;
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())
413 return true;
415 if (!starting_drag_) {
416 starting_drag_ = true;
417 drag_start_point_ = event.location();
419 if (dragging_) {
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);
424 views::Widget* widget = GetWidget();
425 DragDownloadItem(
426 download(), icon, widget ? widget->GetNativeView() : NULL);
428 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
429 dragging_ = true;
431 return true;
434 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
435 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
438 void DownloadItemView::OnMouseCaptureLost() {
439 // Mouse should not activate us in dangerous mode.
440 if (mode_ == DANGEROUS_MODE)
441 return;
443 if (dragging_) {
444 // Starting a drag results in a MouseCaptureLost.
445 dragging_ = false;
446 starting_drag_ = false;
448 SetState(NORMAL, NORMAL);
451 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
452 // Mouse should not activate us in dangerous mode.
453 if (mode_ == DANGEROUS_MODE)
454 return;
456 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
457 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
460 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
461 // Mouse should not activate us in dangerous mode.
462 if (mode_ == DANGEROUS_MODE)
463 return;
465 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
468 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
469 // Key press should not activate us in dangerous mode.
470 if (IsShowingWarningDialog())
471 return true;
473 if (event.key_code() == ui::VKEY_SPACE ||
474 event.key_code() == ui::VKEY_RETURN) {
475 // OpenDownload may delete this, so don't add any code after this line.
476 OpenDownload();
477 return true;
479 return false;
482 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
483 base::string16* tooltip) const {
484 if (IsShowingWarningDialog()) {
485 tooltip->clear();
486 return false;
489 tooltip->assign(tooltip_text_);
491 return true;
494 void DownloadItemView::GetAccessibleState(ui::AXViewState* state) {
495 state->name = accessible_name_;
496 state->role = ui::AX_ROLE_BUTTON;
497 if (model_.IsDangerous())
498 state->AddStateFlag(ui::AX_STATE_DISABLED);
499 else
500 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
503 void DownloadItemView::OnThemeChanged() {
504 UpdateColorsFromTheme();
507 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
508 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
509 HandlePressEvent(*event, true);
510 event->SetHandled();
511 return;
514 if (event->type() == ui::ET_GESTURE_TAP) {
515 HandleClickEvent(*event, true);
516 event->SetHandled();
517 return;
520 SetState(NORMAL, NORMAL);
521 views::View::OnGestureEvent(event);
524 void DownloadItemView::ShowContextMenuForView(View* source,
525 const gfx::Point& point,
526 ui::MenuSourceType source_type) {
527 // |point| is in screen coordinates. So convert it to local coordinates first.
528 gfx::Point local_point = point;
529 ConvertPointFromScreen(this, &local_point);
530 ShowContextMenuImpl(local_point, source_type);
533 void DownloadItemView::ButtonPressed(views::Button* sender,
534 const ui::Event& event) {
535 base::TimeDelta warning_duration;
536 if (!time_download_warning_shown_.is_null())
537 warning_duration = base::Time::Now() - time_download_warning_shown_;
539 if (save_button_ && sender == save_button_) {
540 // The user has confirmed a dangerous download. We'd record how quickly the
541 // user did this to detect whether we're being clickjacked.
542 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
543 // This will change the state and notify us.
544 download()->ValidateDangerousDownload();
545 return;
548 // WARNING: all end states after this point delete |this|.
549 DCHECK_EQ(discard_button_, sender);
550 if (model_.IsMalicious()) {
551 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
552 shelf_->RemoveDownloadView(this);
553 return;
555 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration);
556 if (model_.ShouldAllowDownloadFeedback() &&
557 !shelf_->browser()->profile()->IsOffTheRecord()) {
558 if (!shelf_->browser()->profile()->GetPrefs()->HasPrefPath(
559 prefs::kSafeBrowsingDownloadFeedbackEnabled)) {
560 // Show dialog, because the dialog hasn't been shown before.
561 DownloadFeedbackDialogView::Show(
562 shelf_->get_parent()->GetNativeWindow(),
563 shelf_->browser()->profile(),
564 base::Bind(
565 &DownloadItemView::PossiblySubmitDownloadToFeedbackService,
566 weak_ptr_factory_.GetWeakPtr()));
567 } else {
568 PossiblySubmitDownloadToFeedbackService(
569 shelf_->browser()->profile()->GetPrefs()->GetBoolean(
570 prefs::kSafeBrowsingDownloadFeedbackEnabled));
572 return;
574 download()->Remove();
577 void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) {
578 // We don't care if what animation (body button/drop button/complete),
579 // is calling back, as they all have to go through the same paint call.
580 SchedulePaint();
583 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
584 OnPaintBackground(canvas);
585 if (HasFocus())
586 canvas->DrawFocusRect(GetLocalBounds());
589 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
590 // and MALICIOUS_MODE).
592 // NORMAL_MODE: We are displaying an in-progress or completed download.
593 // .-------------------------------+-.
594 // | [icon] Filename |v|
595 // | [ ] Status | |
596 // `-------------------------------+-'
597 // | | \_ Drop down button. Invokes menu. Responds
598 // | | to mouse. (NORMAL, HOT or PUSHED).
599 // | \_ Icon is overlaid on top of in-progress animation.
600 // \_ Both the body and the drop down button respond to mouse hover and can be
601 // pushed (NORMAL, HOT or PUSHED).
603 // DANGEROUS_MODE: The file could be potentially dangerous.
604 // .-------------------------------------------------------.
605 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
606 // | [ ] [destroy your computer..] [ ] [ ] |
607 // `-------------------------------------------------------'
608 // | | | | \_ No drop down button.
609 // | | | \_ Buttons are views::LabelButtons.
610 // | | \_ Text is in a label (dangerous_download_label_)
611 // | \_ Warning icon. No progress animation.
612 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
614 // MALICIOUS_MODE: The file is known malware.
615 // .---------------------------------------------+-.
616 // | [ - ] [This file is malicious.] [ Discard ] |v|
617 // | [ ] [ ] [ ] | |-.
618 // `---------------------------------------------+-' |
619 // | | | | Drop down button. Responds to
620 // | | | | mouse.(NORMAL, HOT or PUSHED)
621 // | | | \_ Button is a views::LabelButton.
622 // | | \_ Text is in a label (dangerous_download_label_)
623 // | \_ Warning icon. No progress animation.
624 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
626 void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) {
627 BodyImageSet* body_image_set = NULL;
628 switch (mode_) {
629 case NORMAL_MODE:
630 if (body_state_ == PUSHED)
631 body_image_set = &pushed_body_image_set_;
632 else // NORMAL or HOT
633 body_image_set = &normal_body_image_set_;
634 break;
635 case DANGEROUS_MODE:
636 body_image_set = &dangerous_mode_body_image_set_;
637 break;
638 case MALICIOUS_MODE:
639 body_image_set = &malicious_mode_body_image_set_;
640 break;
641 default:
642 NOTREACHED();
645 DropDownImageSet* drop_down_image_set = NULL;
646 switch (mode_) {
647 case NORMAL_MODE:
648 case MALICIOUS_MODE:
649 if (drop_down_state_ == PUSHED)
650 drop_down_image_set = &pushed_drop_down_image_set_;
651 else // NORMAL or HOT
652 drop_down_image_set = &normal_drop_down_image_set_;
653 break;
654 case DANGEROUS_MODE:
655 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
656 // drop_down_image_set == NULL.
657 break;
658 default:
659 NOTREACHED();
662 int center_width = width() - kLeftPadding -
663 body_image_set->left->width() -
664 body_image_set->right->width() -
665 (drop_down_image_set ?
666 normal_drop_down_image_set_.center->width() :
669 // May be caused by animation.
670 if (center_width <= 0)
671 return;
673 // Draw status before button image to effectively lighten text. No status for
674 // warning dialogs.
675 if (!IsShowingWarningDialog()) {
676 if (!status_text_.empty()) {
677 int mirrored_x = GetMirroredXWithWidthInView(
678 DownloadShelf::kSmallProgressIconSize, kTextWidth);
679 // Add font_list_.height() to compensate for title, which is drawn later.
680 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() +
681 kVerticalTextPadding;
682 SkColor file_name_color = GetThemeProvider()->GetColor(
683 ThemeProperties::COLOR_BOOKMARK_TEXT);
684 // If text is light-on-dark, lightening it alone will do nothing.
685 // Therefore we mute luminance a wee bit before drawing in this case.
686 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
687 file_name_color = SkColorSetRGB(
688 static_cast<int>(kDownloadItemLuminanceMod *
689 SkColorGetR(file_name_color)),
690 static_cast<int>(kDownloadItemLuminanceMod *
691 SkColorGetG(file_name_color)),
692 static_cast<int>(kDownloadItemLuminanceMod *
693 SkColorGetB(file_name_color)));
694 canvas->DrawStringRect(status_text_, font_list_, file_name_color,
695 gfx::Rect(mirrored_x, y, kTextWidth,
696 font_list_.GetHeight()));
700 // Paint the background images.
701 int x = kLeftPadding;
702 canvas->Save();
703 if (base::i18n::IsRTL()) {
704 // Since we do not have the mirrored images for
705 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
706 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
707 // for RTL UI, we flip the canvas to draw those images mirrored.
708 // Consequently, we do not need to mirror the x-axis of those images.
709 canvas->Translate(gfx::Vector2d(width(), 0));
710 canvas->Scale(-1, 1);
712 PaintImages(canvas,
713 body_image_set->top_left, body_image_set->left,
714 body_image_set->bottom_left,
715 x, box_y_, box_height_, body_image_set->top_left->width());
716 x += body_image_set->top_left->width();
717 PaintImages(canvas,
718 body_image_set->top, body_image_set->center,
719 body_image_set->bottom,
720 x, box_y_, box_height_, center_width);
721 x += center_width;
722 PaintImages(canvas,
723 body_image_set->top_right, body_image_set->right,
724 body_image_set->bottom_right,
725 x, box_y_, box_height_, body_image_set->top_right->width());
727 // Overlay our body hot state. Warning dialogs don't display body a hot state.
728 if (!IsShowingWarningDialog() &&
729 body_hover_animation_->GetCurrentValue() > 0) {
730 canvas->SaveLayerAlpha(
731 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
733 int x = kLeftPadding;
734 PaintImages(canvas,
735 hot_body_image_set_.top_left, hot_body_image_set_.left,
736 hot_body_image_set_.bottom_left,
737 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
738 x += body_image_set->top_left->width();
739 PaintImages(canvas,
740 hot_body_image_set_.top, hot_body_image_set_.center,
741 hot_body_image_set_.bottom,
742 x, box_y_, box_height_, center_width);
743 x += center_width;
744 PaintImages(canvas,
745 hot_body_image_set_.top_right, hot_body_image_set_.right,
746 hot_body_image_set_.bottom_right,
747 x, box_y_, box_height_,
748 hot_body_image_set_.top_right->width());
749 canvas->Restore();
752 x += body_image_set->top_right->width();
754 // Paint the drop-down.
755 if (drop_down_image_set) {
756 PaintImages(canvas,
757 drop_down_image_set->top, drop_down_image_set->center,
758 drop_down_image_set->bottom,
759 x, box_y_, box_height_, drop_down_image_set->top->width());
761 // Overlay our drop-down hot state.
762 if (drop_hover_animation_->GetCurrentValue() > 0) {
763 canvas->SaveLayerAlpha(
764 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
766 PaintImages(canvas,
767 drop_down_image_set->top, drop_down_image_set->center,
768 drop_down_image_set->bottom,
769 x, box_y_, box_height_, drop_down_image_set->top->width());
771 canvas->Restore();
775 // Restore the canvas to avoid file name etc. text are drawn flipped.
776 // Consequently, the x-axis of following canvas->DrawXXX() method should be
777 // mirrored so the text and images are down in the right positions.
778 canvas->Restore();
780 // Print the text, left aligned and always print the file extension.
781 // Last value of x was the end of the right image, just before the button.
782 // Note that in dangerous mode we use a label (as the text is multi-line).
783 if (!IsShowingWarningDialog()) {
784 base::string16 filename;
785 if (!disabled_while_opening_) {
786 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(),
787 font_list_, kTextWidth);
788 } else {
789 // First, Calculate the download status opening string width.
790 base::string16 status_string =
791 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
792 base::string16());
793 int status_string_width = gfx::GetStringWidth(status_string, font_list_);
794 // Then, elide the file name.
795 base::string16 filename_string =
796 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_,
797 kTextWidth - status_string_width);
798 // Last, concat the whole string.
799 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
800 filename_string);
803 int mirrored_x = GetMirroredXWithWidthInView(
804 DownloadShelf::kSmallProgressIconSize, kTextWidth);
805 SkColor file_name_color = GetThemeProvider()->GetColor(
806 ThemeProperties::COLOR_BOOKMARK_TEXT);
807 int y =
808 box_y_ + (status_text_.empty() ?
809 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
811 // Draw the file's name.
812 canvas->DrawStringRect(
813 filename, font_list_,
814 enabled() ? file_name_color : kFileNameDisabledColor,
815 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight()));
818 // Load the icon.
819 IconManager* im = g_browser_process->icon_manager();
820 gfx::Image* image = im->LookupIconFromFilepath(
821 download()->GetTargetFilePath(), IconLoader::SMALL);
822 const gfx::ImageSkia* icon = NULL;
823 if (IsShowingWarningDialog())
824 icon = warning_icon_;
825 else if (image)
826 icon = image->ToImageSkia();
828 // We count on the fact that the icon manager will cache the icons and if one
829 // is available, it will be cached here. We *don't* want to request the icon
830 // to be loaded here, since this will also get called if the icon can't be
831 // loaded, in which case LookupIcon will always be NULL. The loading will be
832 // triggered only when we think the status might change.
833 if (icon) {
834 if (!IsShowingWarningDialog()) {
835 DownloadItem::DownloadState state = download()->GetState();
836 if (state == DownloadItem::IN_PROGRESS) {
837 DownloadShelf::PaintDownloadProgress(canvas,
838 this,
841 progress_angle_,
842 model_.PercentComplete(),
843 DownloadShelf::SMALL);
844 } else if (complete_animation_.get() &&
845 complete_animation_->is_animating()) {
846 if (state == DownloadItem::INTERRUPTED) {
847 DownloadShelf::PaintDownloadInterrupted(
848 canvas,
849 this,
852 complete_animation_->GetCurrentValue(),
853 DownloadShelf::SMALL);
854 } else {
855 DCHECK_EQ(DownloadItem::COMPLETE, state);
856 DownloadShelf::PaintDownloadComplete(
857 canvas,
858 this,
861 complete_animation_->GetCurrentValue(),
862 DownloadShelf::SMALL);
867 // Draw the icon image.
868 int icon_x, icon_y;
870 if (IsShowingWarningDialog()) {
871 icon_x = kLeftPadding + body_image_set->top_left->width();
872 icon_y = (height() - icon->height()) / 2;
873 } else {
874 icon_x = DownloadShelf::kSmallProgressIconOffset;
875 icon_y = DownloadShelf::kSmallProgressIconOffset;
877 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
878 if (enabled()) {
879 canvas->DrawImageInt(*icon, icon_x, icon_y);
880 } else {
881 // Use an alpha to make the image look disabled.
882 SkPaint paint;
883 paint.setAlpha(120);
884 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
889 void DownloadItemView::OnFocus() {
890 View::OnFocus();
891 // We render differently when focused.
892 SchedulePaint();
895 void DownloadItemView::OnBlur() {
896 View::OnBlur();
897 // We render differently when focused.
898 SchedulePaint();
901 void DownloadItemView::OpenDownload() {
902 DCHECK(!IsShowingWarningDialog());
903 // We're interested in how long it takes users to open downloads. If they
904 // open downloads super quickly, we should be concerned about clickjacking.
905 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
906 base::Time::Now() - creation_time_);
908 UpdateAccessibleName();
910 // Calling download()->OpenDownload may delete this, so this must be
911 // the last thing we do.
912 download()->OpenDownload();
915 bool DownloadItemView::SubmitDownloadToFeedbackService() {
916 #if defined(FULL_SAFE_BROWSING)
917 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
918 if (!sb_service)
919 return false;
920 safe_browsing::DownloadProtectionService* download_protection_service =
921 sb_service->download_protection_service();
922 if (!download_protection_service)
923 return false;
924 download_protection_service->feedback_service()->BeginFeedbackForDownload(
925 download());
926 // WARNING: we are deleted at this point. Don't access 'this'.
927 return true;
928 #else
929 NOTREACHED();
930 return false;
931 #endif
934 void DownloadItemView::PossiblySubmitDownloadToFeedbackService(bool enabled) {
935 if (!enabled || !SubmitDownloadToFeedbackService())
936 download()->Remove();
937 // WARNING: 'this' is deleted at this point. Don't access 'this'.
940 void DownloadItemView::LoadIcon() {
941 IconManager* im = g_browser_process->icon_manager();
942 last_download_item_path_ = download()->GetTargetFilePath();
943 im->LoadIcon(last_download_item_path_,
944 IconLoader::SMALL,
945 base::Bind(&DownloadItemView::OnExtractIconComplete,
946 base::Unretained(this)),
947 &cancelable_task_tracker_);
950 void DownloadItemView::LoadIconIfItemPathChanged() {
951 base::FilePath current_download_path = download()->GetTargetFilePath();
952 if (last_download_item_path_ == current_download_path)
953 return;
955 LoadIcon();
958 void DownloadItemView::UpdateColorsFromTheme() {
959 if (dangerous_download_label_ && GetThemeProvider()) {
960 dangerous_download_label_->SetEnabledColor(
961 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
965 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
966 ui::MenuSourceType source_type) {
967 gfx::Point point = p;
968 gfx::Size size;
970 // Similar hack as in MenuButton.
971 // We're about to show the menu from a mouse press. By showing from the
972 // mouse press event we block RootView in mouse dispatching. This also
973 // appears to cause RootView to get a mouse pressed BEFORE the mouse
974 // release is seen, which means RootView sends us another mouse press no
975 // matter where the user pressed. To force RootView to recalculate the
976 // mouse target during the mouse press we explicitly set the mouse handler
977 // to NULL.
978 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
979 SetMouseHandler(NULL);
981 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
982 // to drop down arrow button.
983 if (source_type != ui::MENU_SOURCE_MOUSE &&
984 source_type != ui::MENU_SOURCE_TOUCH) {
985 drop_down_pressed_ = true;
986 SetState(NORMAL, PUSHED);
987 point.SetPoint(drop_down_x_left_, box_y_);
988 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
990 // Post a task to release the button. When we call the Run method on the menu
991 // below, it runs an inner message loop that might cause us to be deleted.
992 // Posting a task with a WeakPtr lets us safely handle the button release.
993 base::MessageLoop::current()->PostNonNestableTask(
994 FROM_HERE,
995 base::Bind(&DownloadItemView::ReleaseDropDown,
996 weak_ptr_factory_.GetWeakPtr()));
997 views::View::ConvertPointToScreen(this, &point);
999 if (!context_menu_.get()) {
1000 context_menu_.reset(
1001 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator()));
1003 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
1004 gfx::Rect(point, size), source_type);
1005 // We could be deleted now.
1008 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event,
1009 bool active_event) {
1010 // The event should not activate us in dangerous mode.
1011 if (mode_ == DANGEROUS_MODE)
1012 return;
1014 // Stop any completion animation.
1015 if (complete_animation_.get() && complete_animation_->is_animating())
1016 complete_animation_->End();
1018 if (active_event) {
1019 if (InDropDownButtonXCoordinateRange(event.x())) {
1020 if (context_menu_.get()) {
1021 // Ignore two close clicks. This typically happens when the user clicks
1022 // the button to close the menu.
1023 base::TimeDelta delta =
1024 base::TimeTicks::Now() - context_menu_->close_time();
1025 if (delta.InMilliseconds() < views::kMinimumMsBetweenButtonClicks)
1026 return;
1028 drop_down_pressed_ = true;
1029 SetState(NORMAL, PUSHED);
1030 // We are setting is_mouse_gesture to false when calling ShowContextMenu
1031 // so that the positioning of the context menu will be similar to a
1032 // keyboard invocation. I.e. we want the menu to always be positioned
1033 // next to the drop down button instead of the next to the pointer.
1034 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD);
1035 // Once called, it is possible that *this was deleted (e.g.: due to
1036 // invoking the 'Discard' action.)
1037 } else if (!IsShowingWarningDialog()) {
1038 SetState(PUSHED, NORMAL);
1043 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event,
1044 bool active_event) {
1045 // Mouse should not activate us in dangerous mode.
1046 if (mode_ == DANGEROUS_MODE)
1047 return;
1049 SetState(NORMAL, NORMAL);
1051 if (!active_event ||
1052 InDropDownButtonXCoordinateRange(event.x()) ||
1053 IsShowingWarningDialog()) {
1054 return;
1057 // OpenDownload may delete this, so don't add any code after this line.
1058 OpenDownload();
1061 // Load an icon for the file type we're downloading, and animate any in progress
1062 // download state.
1063 void DownloadItemView::PaintImages(gfx::Canvas* canvas,
1064 const gfx::ImageSkia* top_image,
1065 const gfx::ImageSkia* center_image,
1066 const gfx::ImageSkia* bottom_image,
1067 int x, int y, int height, int width) {
1068 int middle_height = height - top_image->height() - bottom_image->height();
1069 // Draw the top.
1070 canvas->DrawImageInt(*top_image,
1071 0, 0, top_image->width(), top_image->height(),
1072 x, y, width, top_image->height(), false);
1073 y += top_image->height();
1074 // Draw the center.
1075 canvas->DrawImageInt(*center_image,
1076 0, 0, center_image->width(), center_image->height(),
1077 x, y, width, middle_height, false);
1078 y += middle_height;
1079 // Draw the bottom.
1080 canvas->DrawImageInt(*bottom_image,
1081 0, 0, bottom_image->width(), bottom_image->height(),
1082 x, y, width, bottom_image->height(), false);
1085 void DownloadItemView::SetState(State new_body_state, State new_drop_state) {
1086 // If we are showing a warning dialog, we don't change body state.
1087 if (IsShowingWarningDialog()) {
1088 new_body_state = NORMAL;
1090 // Current body_state_ should always be NORMAL for warning dialogs.
1091 DCHECK_EQ(NORMAL, body_state_);
1092 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1093 DCHECK_NE(DANGEROUS_MODE, mode_);
1095 // Avoid extra SchedulePaint()s if the state is going to be the same.
1096 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state)
1097 return;
1099 AnimateStateTransition(body_state_, new_body_state,
1100 body_hover_animation_.get());
1101 AnimateStateTransition(drop_down_state_, new_drop_state,
1102 drop_hover_animation_.get());
1103 body_state_ = new_body_state;
1104 drop_down_state_ = new_drop_state;
1105 SchedulePaint();
1108 void DownloadItemView::ClearWarningDialog() {
1109 DCHECK(download()->GetDangerType() ==
1110 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1111 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1113 mode_ = NORMAL_MODE;
1114 body_state_ = NORMAL;
1115 drop_down_state_ = NORMAL;
1117 // Remove the views used by the warning dialog.
1118 if (save_button_) {
1119 RemoveChildView(save_button_);
1120 delete save_button_;
1121 save_button_ = NULL;
1123 RemoveChildView(discard_button_);
1124 delete discard_button_;
1125 discard_button_ = NULL;
1126 RemoveChildView(dangerous_download_label_);
1127 delete dangerous_download_label_;
1128 dangerous_download_label_ = NULL;
1129 dangerous_download_label_sized_ = false;
1130 cached_button_size_.SetSize(0,0);
1132 // Set the accessible name back to the status and filename instead of the
1133 // download warning.
1134 UpdateAccessibleName();
1135 UpdateDropDownButtonPosition();
1137 // We need to load the icon now that the download has the real path.
1138 LoadIcon();
1140 // Force the shelf to layout again as our size has changed.
1141 shelf_->Layout();
1142 shelf_->SchedulePaint();
1144 TooltipTextChanged();
1147 void DownloadItemView::ShowWarningDialog() {
1148 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
1149 time_download_warning_shown_ = base::Time::Now();
1150 content::DownloadDangerType danger_type = download()->GetDangerType();
1151 RecordDangerousDownloadWarningShown(danger_type);
1152 #if defined(FULL_SAFE_BROWSING)
1153 if (model_.ShouldAllowDownloadFeedback()) {
1154 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1155 danger_type);
1157 #endif
1158 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
1160 body_state_ = NORMAL;
1161 drop_down_state_ = NORMAL;
1162 if (mode_ == DANGEROUS_MODE) {
1163 save_button_ = new views::LabelButton(
1164 this, model_.GetWarningConfirmButtonText());
1165 save_button_->SetStyle(views::Button::STYLE_BUTTON);
1166 AddChildView(save_button_);
1168 int discard_button_message = model_.IsMalicious() ?
1169 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD;
1170 discard_button_ = new views::LabelButton(
1171 this, l10n_util::GetStringUTF16(discard_button_message));
1172 discard_button_->SetStyle(views::Button::STYLE_BUTTON);
1173 AddChildView(discard_button_);
1175 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1176 switch (danger_type) {
1177 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
1178 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
1179 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
1180 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
1181 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
1182 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING);
1183 break;
1185 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
1186 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
1187 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
1188 case content::DOWNLOAD_DANGER_TYPE_MAX:
1189 NOTREACHED();
1190 // fallthrough
1192 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1193 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
1195 base::string16 dangerous_label =
1196 model_.GetWarningText(font_list_, kTextWidth);
1197 dangerous_download_label_ = new views::Label(dangerous_label);
1198 dangerous_download_label_->SetMultiLine(true);
1199 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1200 dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
1201 AddChildView(dangerous_download_label_);
1202 SizeLabelToMinWidth();
1203 UpdateDropDownButtonPosition();
1204 TooltipTextChanged();
1207 gfx::Size DownloadItemView::GetButtonSize() const {
1208 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
1209 gfx::Size size;
1211 // We cache the size when successfully retrieved, not for performance reasons
1212 // but because if this DownloadItemView is being animated while the tab is
1213 // not showing, the native buttons are not parented and their preferred size
1214 // is 0, messing-up the layout.
1215 if (cached_button_size_.width() != 0)
1216 return cached_button_size_;
1218 if (save_button_)
1219 size = save_button_->GetMinimumSize();
1220 gfx::Size discard_size = discard_button_->GetMinimumSize();
1222 size.SetSize(std::max(size.width(), discard_size.width()),
1223 std::max(size.height(), discard_size.height()));
1225 if (size.width() != 0)
1226 cached_button_size_ = size;
1228 return size;
1231 // This method computes the minimum width of the label for displaying its text
1232 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1233 // configuration with minimum width.
1234 void DownloadItemView::SizeLabelToMinWidth() {
1235 if (dangerous_download_label_sized_)
1236 return;
1238 base::string16 label_text = dangerous_download_label_->text();
1239 base::TrimWhitespace(label_text, base::TRIM_ALL, &label_text);
1240 DCHECK_EQ(base::string16::npos, label_text.find('\n'));
1242 // Make the label big so that GetPreferredSize() is not constrained by the
1243 // current width.
1244 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1246 // Use a const string from here. BreakIterator requies that text.data() not
1247 // change during its lifetime.
1248 const base::string16 original_text(label_text);
1249 // Using BREAK_WORD can work in most cases, but it can also break
1250 // lines where it should not. Using BREAK_LINE is safer although
1251 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1252 base::i18n::BreakIterator iter(original_text,
1253 base::i18n::BreakIterator::BREAK_LINE);
1254 bool status = iter.Init();
1255 DCHECK(status);
1257 base::string16 prev_text = original_text;
1258 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1259 int min_width = size.width();
1261 // Go through the string and try each line break (starting with no line break)
1262 // searching for the optimal line break position. Stop if we find one that
1263 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1264 // a short string (e.g.: "This file is malicious") from being broken up
1265 // unnecessarily.
1266 while (iter.Advance() && min_width > kDangerousTextWidth) {
1267 size_t pos = iter.pos();
1268 if (pos >= original_text.length())
1269 break;
1270 base::string16 current_text = original_text;
1271 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1272 // return false and inserting a new line after a surrogate pair
1273 // is perfectly ok.
1274 base::char16 line_end_char = current_text[pos - 1];
1275 if (u_isUWhiteSpace(line_end_char))
1276 current_text.replace(pos - 1, 1, 1, base::char16('\n'));
1277 else
1278 current_text.insert(pos, 1, base::char16('\n'));
1279 dangerous_download_label_->SetText(current_text);
1280 size = dangerous_download_label_->GetPreferredSize();
1282 // If the width is growing again, it means we passed the optimal width spot.
1283 if (size.width() > min_width) {
1284 dangerous_download_label_->SetText(prev_text);
1285 break;
1286 } else {
1287 min_width = size.width();
1289 prev_text = current_text;
1292 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1293 dangerous_download_label_sized_ = true;
1296 void DownloadItemView::Reenable() {
1297 disabled_while_opening_ = false;
1298 SetEnabled(true); // Triggers a repaint.
1301 void DownloadItemView::ReleaseDropDown() {
1302 drop_down_pressed_ = false;
1303 SetState(NORMAL, NORMAL);
1306 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1307 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1308 return true;
1309 return false;
1312 void DownloadItemView::UpdateAccessibleName() {
1313 base::string16 new_name;
1314 if (IsShowingWarningDialog()) {
1315 new_name = dangerous_download_label_->text();
1316 } else {
1317 new_name = status_text_ + base::char16(' ') +
1318 download()->GetFileNameToReportUser().LossyDisplayName();
1321 // If the name has changed, notify assistive technology that the name
1322 // has changed so they can announce it immediately.
1323 if (new_name != accessible_name_) {
1324 accessible_name_ = new_name;
1325 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true);
1329 void DownloadItemView::UpdateDropDownButtonPosition() {
1330 gfx::Size size = GetPreferredSize();
1331 if (base::i18n::IsRTL()) {
1332 // Drop down button is glued to the left of the download shelf.
1333 drop_down_x_left_ = 0;
1334 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
1335 } else {
1336 // Drop down button is glued to the right of the download shelf.
1337 drop_down_x_left_ =
1338 size.width() - normal_drop_down_image_set_.top->width();
1339 drop_down_x_right_ = size.width();
1343 void DownloadItemView::AnimateStateTransition(State from, State to,
1344 gfx::SlideAnimation* animation) {
1345 if (from == NORMAL && to == HOT) {
1346 animation->Show();
1347 } else if (from == HOT && to == NORMAL) {
1348 animation->Hide();
1349 } else if (from != to) {
1350 animation->Reset((to == HOT) ? 1.0 : 0.0);