Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / views / download / download_item_view.cc
blob05021166db170e3a78dd6f51ed9068a85a41036c
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/extensions/api/experience_sampling_private/experience_sampling.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/safe_browsing/download_feedback_service.h"
29 #include "chrome/browser/safe_browsing/download_protection_service.h"
30 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
31 #include "chrome/browser/themes/theme_properties.h"
32 #include "chrome/browser/ui/views/download/download_feedback_dialog_view.h"
33 #include "chrome/browser/ui/views/download/download_shelf_context_menu_view.h"
34 #include "chrome/browser/ui/views/download/download_shelf_view.h"
35 #include "chrome/browser/ui/views/frame/browser_view.h"
36 #include "chrome/common/pref_names.h"
37 #include "chrome/grit/generated_resources.h"
38 #include "content/public/browser/download_danger_type.h"
39 #include "grit/theme_resources.h"
40 #include "third_party/icu/source/common/unicode/uchar.h"
41 #include "ui/accessibility/ax_view_state.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/resource/resource_bundle.h"
44 #include "ui/base/theme_provider.h"
45 #include "ui/events/event.h"
46 #include "ui/gfx/animation/slide_animation.h"
47 #include "ui/gfx/canvas.h"
48 #include "ui/gfx/color_utils.h"
49 #include "ui/gfx/image/image.h"
50 #include "ui/gfx/text_elider.h"
51 #include "ui/gfx/text_utils.h"
52 #include "ui/views/controls/button/label_button.h"
53 #include "ui/views/controls/label.h"
54 #include "ui/views/mouse_constants.h"
55 #include "ui/views/widget/root_view.h"
56 #include "ui/views/widget/widget.h"
58 using content::DownloadItem;
59 using extensions::ExperienceSamplingEvent;
61 // TODO(paulg): These may need to be adjusted when download progress
62 // animation is added, and also possibly to take into account
63 // different screen resolutions.
64 static const int kTextWidth = 140; // Pixels
65 static const int kDangerousTextWidth = 200; // Pixels
66 static const int kVerticalPadding = 3; // Pixels
67 static const int kVerticalTextPadding = 2; // Pixels
68 static const int kTooltipMaxWidth = 800; // Pixels
70 // We add some padding before the left image so that the progress animation icon
71 // hides the corners of the left image.
72 static const int kLeftPadding = 0; // Pixels.
74 // The space between the Save and Discard buttons when prompting for a dangerous
75 // download.
76 static const int kButtonPadding = 5; // Pixels.
78 // The space on the left and right side of the dangerous download label.
79 static const int kLabelPadding = 4; // Pixels.
81 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
83 // How long the 'download complete' animation should last for.
84 static const int kCompleteAnimationDurationMs = 2500;
86 // How long the 'download interrupted' animation should last for.
87 static const int kInterruptedAnimationDurationMs = 2500;
89 // How long we keep the item disabled after the user clicked it to open the
90 // downloaded item.
91 static const int kDisabledOnOpenDuration = 3000;
93 // Darken light-on-dark download status text by 20% before drawing, thus
94 // creating a "muted" version of title text for both dark-on-light and
95 // light-on-dark themes.
96 static const double kDownloadItemLuminanceMod = 0.8;
98 namespace {
100 // Callback for DownloadShelf paint functions to mirror the progress animation
101 // in RTL locales.
102 void RTLMirrorXForView(views::View* containing_view, gfx::Rect* bounds) {
103 bounds->set_x(containing_view->GetMirroredXForRect(*bounds));
106 } // namespace
108 DownloadItemView::DownloadItemView(DownloadItem* download_item,
109 DownloadShelfView* parent)
110 : warning_icon_(NULL),
111 shelf_(parent),
112 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
113 body_state_(NORMAL),
114 drop_down_state_(NORMAL),
115 mode_(NORMAL_MODE),
116 progress_angle_(DownloadShelf::kStartAngleDegrees),
117 drop_down_pressed_(false),
118 dragging_(false),
119 starting_drag_(false),
120 model_(download_item),
121 save_button_(NULL),
122 discard_button_(NULL),
123 dangerous_download_label_(NULL),
124 dangerous_download_label_sized_(false),
125 disabled_while_opening_(false),
126 creation_time_(base::Time::Now()),
127 time_download_warning_shown_(base::Time()),
128 weak_ptr_factory_(this) {
129 DCHECK(download());
130 download()->AddObserver(this);
131 set_context_menu_controller(this);
133 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
135 BodyImageSet normal_body_image_set = {
136 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
137 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
138 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
139 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
140 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
141 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
142 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
143 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
144 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
146 normal_body_image_set_ = normal_body_image_set;
148 DropDownImageSet normal_drop_down_image_set = {
149 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
150 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
151 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
153 normal_drop_down_image_set_ = normal_drop_down_image_set;
155 BodyImageSet hot_body_image_set = {
156 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
157 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
158 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
159 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
160 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
161 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
162 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
163 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
164 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
166 hot_body_image_set_ = hot_body_image_set;
168 DropDownImageSet hot_drop_down_image_set = {
169 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
170 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
171 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
173 hot_drop_down_image_set_ = hot_drop_down_image_set;
175 BodyImageSet pushed_body_image_set = {
176 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
177 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
178 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
179 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
180 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
181 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
182 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
183 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
184 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
186 pushed_body_image_set_ = pushed_body_image_set;
188 DropDownImageSet pushed_drop_down_image_set = {
189 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
190 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
191 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
193 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
195 BodyImageSet dangerous_mode_body_image_set = {
196 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
197 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
198 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
199 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
200 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
201 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
202 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
203 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
204 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
206 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
208 malicious_mode_body_image_set_ = normal_body_image_set;
210 LoadIcon();
212 font_list_ = rb.GetFontList(ui::ResourceBundle::BaseFont);
213 box_height_ = std::max<int>(2 * kVerticalPadding + font_list_.GetHeight() +
214 kVerticalTextPadding + font_list_.GetHeight(),
215 2 * kVerticalPadding +
216 normal_body_image_set_.top_left->height() +
217 normal_body_image_set_.bottom_left->height());
219 if (DownloadShelf::kSmallProgressIconSize > box_height_)
220 box_y_ = (DownloadShelf::kSmallProgressIconSize - box_height_) / 2;
221 else
222 box_y_ = 0;
224 body_hover_animation_.reset(new gfx::SlideAnimation(this));
225 drop_hover_animation_.reset(new gfx::SlideAnimation(this));
227 SetAccessibilityFocusable(true);
229 OnDownloadUpdated(download());
230 UpdateDropDownButtonPosition();
233 DownloadItemView::~DownloadItemView() {
234 StopDownloadProgress();
235 download()->RemoveObserver(this);
237 // ExperienceSampling: If the user took no action to remove the warning
238 // before it disappeared, then the user effectively dismissed the download
239 // without keeping it.
240 if (sampling_event_.get())
241 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kIgnore);
244 // Progress animation handlers.
246 void DownloadItemView::UpdateDownloadProgress() {
247 progress_angle_ =
248 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
249 DownloadShelf::kMaxDegrees;
250 SchedulePaint();
253 void DownloadItemView::StartDownloadProgress() {
254 if (progress_timer_.IsRunning())
255 return;
256 progress_timer_.Start(FROM_HERE,
257 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs), this,
258 &DownloadItemView::UpdateDownloadProgress);
261 void DownloadItemView::StopDownloadProgress() {
262 progress_timer_.Stop();
265 void DownloadItemView::OnExtractIconComplete(gfx::Image* icon_bitmap) {
266 if (icon_bitmap)
267 shelf_->SchedulePaint();
270 // DownloadObserver interface.
272 // Update the progress graphic on the icon and our text status label
273 // to reflect our current bytes downloaded, time remaining.
274 void DownloadItemView::OnDownloadUpdated(DownloadItem* download_item) {
275 DCHECK_EQ(download(), download_item);
277 if (!model_.ShouldShowInShelf()) {
278 shelf_->RemoveDownloadView(this); // This will delete us!
279 return;
282 if (IsShowingWarningDialog() && !model_.IsDangerous()) {
283 // We have been approved.
284 ClearWarningDialog();
285 } else if (!IsShowingWarningDialog() && model_.IsDangerous()) {
286 ShowWarningDialog();
287 // Force the shelf to layout again as our size has changed.
288 shelf_->Layout();
289 SchedulePaint();
290 } else {
291 base::string16 status_text = model_.GetStatusText();
292 switch (download()->GetState()) {
293 case DownloadItem::IN_PROGRESS:
294 download()->IsPaused() ?
295 StopDownloadProgress() : StartDownloadProgress();
296 LoadIconIfItemPathChanged();
297 break;
298 case DownloadItem::INTERRUPTED:
299 StopDownloadProgress();
300 complete_animation_.reset(new gfx::SlideAnimation(this));
301 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
302 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
303 complete_animation_->Show();
304 SchedulePaint();
305 LoadIcon();
306 break;
307 case DownloadItem::COMPLETE:
308 if (model_.ShouldRemoveFromShelfWhenComplete()) {
309 shelf_->RemoveDownloadView(this); // This will delete us!
310 return;
312 StopDownloadProgress();
313 complete_animation_.reset(new gfx::SlideAnimation(this));
314 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
315 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
316 complete_animation_->Show();
317 SchedulePaint();
318 LoadIcon();
319 break;
320 case DownloadItem::CANCELLED:
321 StopDownloadProgress();
322 if (complete_animation_)
323 complete_animation_->Stop();
324 LoadIcon();
325 break;
326 default:
327 NOTREACHED();
329 status_text_ = status_text;
332 base::string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth);
333 if (new_tip != tooltip_text_) {
334 tooltip_text_ = new_tip;
335 TooltipTextChanged();
338 UpdateAccessibleName();
340 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
341 // are spaces between each DownloadItemView that the parent is responsible
342 // for painting.
343 shelf_->SchedulePaint();
346 void DownloadItemView::OnDownloadDestroyed(DownloadItem* download) {
347 shelf_->RemoveDownloadView(this); // This will delete us!
350 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
351 disabled_while_opening_ = true;
352 SetEnabled(false);
353 base::MessageLoop::current()->PostDelayedTask(
354 FROM_HERE,
355 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
356 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
358 // Notify our parent.
359 shelf_->OpenedDownload(this);
362 // View overrides
364 // In dangerous mode we have to layout our buttons.
365 void DownloadItemView::Layout() {
366 if (IsShowingWarningDialog()) {
367 BodyImageSet* body_image_set =
368 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
369 &malicious_mode_body_image_set_;
370 int x = kLeftPadding + body_image_set->top_left->width() +
371 warning_icon_->width() + kLabelPadding;
372 int y = (height() - dangerous_download_label_->height()) / 2;
373 dangerous_download_label_->SetBounds(x, y,
374 dangerous_download_label_->width(),
375 dangerous_download_label_->height());
376 gfx::Size button_size = GetButtonSize();
377 x += dangerous_download_label_->width() + kLabelPadding;
378 y = (height() - button_size.height()) / 2;
379 if (save_button_) {
380 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
381 x += button_size.width() + kButtonPadding;
383 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
384 UpdateColorsFromTheme();
388 gfx::Size DownloadItemView::GetPreferredSize() const {
389 int width, height;
391 // First, we set the height to the height of two rows or text plus margins.
392 height = 2 * kVerticalPadding + 2 * font_list_.GetHeight() +
393 kVerticalTextPadding;
394 // Then we increase the size if the progress icon doesn't fit.
395 height = std::max<int>(height, DownloadShelf::kSmallProgressIconSize);
397 if (IsShowingWarningDialog()) {
398 const BodyImageSet* body_image_set =
399 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
400 &malicious_mode_body_image_set_;
401 width = kLeftPadding + body_image_set->top_left->width();
402 width += warning_icon_->width() + kLabelPadding;
403 width += dangerous_download_label_->width() + kLabelPadding;
404 gfx::Size button_size = GetButtonSize();
405 // Make sure the button fits.
406 height = std::max<int>(height, 2 * kVerticalPadding + button_size.height());
407 // Then we make sure the warning icon fits.
408 height = std::max<int>(height, 2 * kVerticalPadding +
409 warning_icon_->height());
410 if (save_button_)
411 width += button_size.width() + kButtonPadding;
412 width += button_size.width();
413 width += body_image_set->top_right->width();
414 if (mode_ == MALICIOUS_MODE)
415 width += normal_drop_down_image_set_.top->width();
416 } else {
417 width = kLeftPadding + normal_body_image_set_.top_left->width();
418 width += DownloadShelf::kSmallProgressIconSize;
419 width += kTextWidth;
420 width += normal_body_image_set_.top_right->width();
421 width += normal_drop_down_image_set_.top->width();
423 return gfx::Size(width, height);
426 // Handle a mouse click and open the context menu if the mouse is
427 // over the drop-down region.
428 bool DownloadItemView::OnMousePressed(const ui::MouseEvent& event) {
429 HandlePressEvent(event, event.IsOnlyLeftMouseButton());
430 return true;
433 // Handle drag (file copy) operations.
434 bool DownloadItemView::OnMouseDragged(const ui::MouseEvent& event) {
435 // Mouse should not activate us in dangerous mode.
436 if (IsShowingWarningDialog())
437 return true;
439 if (!starting_drag_) {
440 starting_drag_ = true;
441 drag_start_point_ = event.location();
443 if (dragging_) {
444 if (download()->GetState() == DownloadItem::COMPLETE) {
445 IconManager* im = g_browser_process->icon_manager();
446 gfx::Image* icon = im->LookupIconFromFilepath(
447 download()->GetTargetFilePath(), IconLoader::SMALL);
448 views::Widget* widget = GetWidget();
449 DragDownloadItem(
450 download(), icon, widget ? widget->GetNativeView() : NULL);
452 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
453 dragging_ = true;
455 return true;
458 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
459 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
462 void DownloadItemView::OnMouseCaptureLost() {
463 // Mouse should not activate us in dangerous mode.
464 if (mode_ == DANGEROUS_MODE)
465 return;
467 if (dragging_) {
468 // Starting a drag results in a MouseCaptureLost.
469 dragging_ = false;
470 starting_drag_ = false;
472 SetState(NORMAL, NORMAL);
475 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
476 // Mouse should not activate us in dangerous mode.
477 if (mode_ == DANGEROUS_MODE)
478 return;
480 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
481 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
484 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
485 // Mouse should not activate us in dangerous mode.
486 if (mode_ == DANGEROUS_MODE)
487 return;
489 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
492 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
493 // Key press should not activate us in dangerous mode.
494 if (IsShowingWarningDialog())
495 return true;
497 if (event.key_code() == ui::VKEY_SPACE ||
498 event.key_code() == ui::VKEY_RETURN) {
499 // OpenDownload may delete this, so don't add any code after this line.
500 OpenDownload();
501 return true;
503 return false;
506 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
507 base::string16* tooltip) const {
508 if (IsShowingWarningDialog()) {
509 tooltip->clear();
510 return false;
513 tooltip->assign(tooltip_text_);
515 return true;
518 void DownloadItemView::GetAccessibleState(ui::AXViewState* state) {
519 state->name = accessible_name_;
520 state->role = ui::AX_ROLE_BUTTON;
521 if (model_.IsDangerous())
522 state->AddStateFlag(ui::AX_STATE_DISABLED);
523 else
524 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
527 void DownloadItemView::OnThemeChanged() {
528 UpdateColorsFromTheme();
531 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
532 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
533 HandlePressEvent(*event, true);
534 event->SetHandled();
535 return;
538 if (event->type() == ui::ET_GESTURE_TAP) {
539 HandleClickEvent(*event, true);
540 event->SetHandled();
541 return;
544 SetState(NORMAL, NORMAL);
545 views::View::OnGestureEvent(event);
548 void DownloadItemView::ShowContextMenuForView(View* source,
549 const gfx::Point& point,
550 ui::MenuSourceType source_type) {
551 // |point| is in screen coordinates. So convert it to local coordinates first.
552 gfx::Point local_point = point;
553 ConvertPointFromScreen(this, &local_point);
554 ShowContextMenuImpl(local_point, source_type);
557 void DownloadItemView::ButtonPressed(views::Button* sender,
558 const ui::Event& event) {
559 base::TimeDelta warning_duration;
560 if (!time_download_warning_shown_.is_null())
561 warning_duration = base::Time::Now() - time_download_warning_shown_;
563 if (save_button_ && sender == save_button_) {
564 // The user has confirmed a dangerous download. We'd record how quickly the
565 // user did this to detect whether we're being clickjacked.
566 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
567 // ExperienceSampling: User chose to proceed with a dangerous download.
568 if (sampling_event_.get()) {
569 sampling_event_->CreateUserDecisionEvent(
570 ExperienceSamplingEvent::kProceed);
571 sampling_event_.reset(NULL);
573 // This will change the state and notify us.
574 download()->ValidateDangerousDownload();
575 return;
578 // WARNING: all end states after this point delete |this|.
579 DCHECK_EQ(discard_button_, sender);
580 if (model_.IsMalicious()) {
581 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
582 // ExperienceSampling: User chose to dismiss the dangerous download.
583 if (sampling_event_.get()) {
584 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
585 sampling_event_.reset(NULL);
587 shelf_->RemoveDownloadView(this);
588 return;
590 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration);
591 if (model_.ShouldAllowDownloadFeedback() &&
592 !shelf_->browser()->profile()->IsOffTheRecord()) {
593 if (!shelf_->browser()->profile()->GetPrefs()->HasPrefPath(
594 prefs::kSafeBrowsingExtendedReportingEnabled)) {
595 // Show dialog, because the dialog hasn't been shown before.
596 DownloadFeedbackDialogView::Show(
597 shelf_->get_parent()->GetNativeWindow(),
598 shelf_->browser()->profile(),
599 shelf_->GetNavigator(),
600 base::Bind(
601 &DownloadItemView::PossiblySubmitDownloadToFeedbackService,
602 weak_ptr_factory_.GetWeakPtr()));
603 } else {
604 PossiblySubmitDownloadToFeedbackService(
605 shelf_->browser()->profile()->GetPrefs()->GetBoolean(
606 prefs::kSafeBrowsingExtendedReportingEnabled));
608 return;
610 download()->Remove();
613 void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) {
614 // We don't care if what animation (body button/drop button/complete),
615 // is calling back, as they all have to go through the same paint call.
616 SchedulePaint();
619 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
620 OnPaintBackground(canvas);
621 if (HasFocus())
622 canvas->DrawFocusRect(GetLocalBounds());
625 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
626 // and MALICIOUS_MODE).
628 // NORMAL_MODE: We are displaying an in-progress or completed download.
629 // .-------------------------------+-.
630 // | [icon] Filename |v|
631 // | [ ] Status | |
632 // `-------------------------------+-'
633 // | | \_ Drop down button. Invokes menu. Responds
634 // | | to mouse. (NORMAL, HOT or PUSHED).
635 // | \_ Icon is overlaid on top of in-progress animation.
636 // \_ Both the body and the drop down button respond to mouse hover and can be
637 // pushed (NORMAL, HOT or PUSHED).
639 // DANGEROUS_MODE: The file could be potentially dangerous.
640 // .-------------------------------------------------------.
641 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
642 // | [ ] [destroy your computer..] [ ] [ ] |
643 // `-------------------------------------------------------'
644 // | | | | \_ No drop down button.
645 // | | | \_ Buttons are views::LabelButtons.
646 // | | \_ Text is in a label (dangerous_download_label_)
647 // | \_ Warning icon. No progress animation.
648 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
650 // MALICIOUS_MODE: The file is known malware.
651 // .---------------------------------------------+-.
652 // | [ - ] [This file is malicious.] [ Discard ] |v|
653 // | [ ] [ ] [ ] | |-.
654 // `---------------------------------------------+-' |
655 // | | | | Drop down button. Responds to
656 // | | | | mouse.(NORMAL, HOT or PUSHED)
657 // | | | \_ Button is a views::LabelButton.
658 // | | \_ Text is in a label (dangerous_download_label_)
659 // | \_ Warning icon. No progress animation.
660 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
662 void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) {
663 BodyImageSet* body_image_set = NULL;
664 switch (mode_) {
665 case NORMAL_MODE:
666 if (body_state_ == PUSHED)
667 body_image_set = &pushed_body_image_set_;
668 else // NORMAL or HOT
669 body_image_set = &normal_body_image_set_;
670 break;
671 case DANGEROUS_MODE:
672 body_image_set = &dangerous_mode_body_image_set_;
673 break;
674 case MALICIOUS_MODE:
675 body_image_set = &malicious_mode_body_image_set_;
676 break;
677 default:
678 NOTREACHED();
681 DropDownImageSet* drop_down_image_set = NULL;
682 switch (mode_) {
683 case NORMAL_MODE:
684 case MALICIOUS_MODE:
685 if (drop_down_state_ == PUSHED)
686 drop_down_image_set = &pushed_drop_down_image_set_;
687 else // NORMAL or HOT
688 drop_down_image_set = &normal_drop_down_image_set_;
689 break;
690 case DANGEROUS_MODE:
691 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
692 // drop_down_image_set == NULL.
693 break;
694 default:
695 NOTREACHED();
698 int center_width = width() - kLeftPadding -
699 body_image_set->left->width() -
700 body_image_set->right->width() -
701 (drop_down_image_set ?
702 normal_drop_down_image_set_.center->width() :
705 // May be caused by animation.
706 if (center_width <= 0)
707 return;
709 // Draw status before button image to effectively lighten text. No status for
710 // warning dialogs.
711 if (!IsShowingWarningDialog()) {
712 if (!status_text_.empty()) {
713 int mirrored_x = GetMirroredXWithWidthInView(
714 DownloadShelf::kSmallProgressIconSize, kTextWidth);
715 // Add font_list_.height() to compensate for title, which is drawn later.
716 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() +
717 kVerticalTextPadding;
718 SkColor file_name_color = GetThemeProvider()->GetColor(
719 ThemeProperties::COLOR_BOOKMARK_TEXT);
720 // If text is light-on-dark, lightening it alone will do nothing.
721 // Therefore we mute luminance a wee bit before drawing in this case.
722 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
723 file_name_color = SkColorSetRGB(
724 static_cast<int>(kDownloadItemLuminanceMod *
725 SkColorGetR(file_name_color)),
726 static_cast<int>(kDownloadItemLuminanceMod *
727 SkColorGetG(file_name_color)),
728 static_cast<int>(kDownloadItemLuminanceMod *
729 SkColorGetB(file_name_color)));
730 canvas->DrawStringRect(status_text_, font_list_, file_name_color,
731 gfx::Rect(mirrored_x, y, kTextWidth,
732 font_list_.GetHeight()));
736 // Paint the background images.
737 int x = kLeftPadding;
738 canvas->Save();
739 if (base::i18n::IsRTL()) {
740 // Since we do not have the mirrored images for
741 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
742 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
743 // for RTL UI, we flip the canvas to draw those images mirrored.
744 // Consequently, we do not need to mirror the x-axis of those images.
745 canvas->Translate(gfx::Vector2d(width(), 0));
746 canvas->Scale(-1, 1);
748 PaintImages(canvas,
749 body_image_set->top_left, body_image_set->left,
750 body_image_set->bottom_left,
751 x, box_y_, box_height_, body_image_set->top_left->width());
752 x += body_image_set->top_left->width();
753 PaintImages(canvas,
754 body_image_set->top, body_image_set->center,
755 body_image_set->bottom,
756 x, box_y_, box_height_, center_width);
757 x += center_width;
758 PaintImages(canvas,
759 body_image_set->top_right, body_image_set->right,
760 body_image_set->bottom_right,
761 x, box_y_, box_height_, body_image_set->top_right->width());
763 // Overlay our body hot state. Warning dialogs don't display body a hot state.
764 if (!IsShowingWarningDialog() &&
765 body_hover_animation_->GetCurrentValue() > 0) {
766 canvas->SaveLayerAlpha(
767 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
769 int x = kLeftPadding;
770 PaintImages(canvas,
771 hot_body_image_set_.top_left, hot_body_image_set_.left,
772 hot_body_image_set_.bottom_left,
773 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
774 x += body_image_set->top_left->width();
775 PaintImages(canvas,
776 hot_body_image_set_.top, hot_body_image_set_.center,
777 hot_body_image_set_.bottom,
778 x, box_y_, box_height_, center_width);
779 x += center_width;
780 PaintImages(canvas,
781 hot_body_image_set_.top_right, hot_body_image_set_.right,
782 hot_body_image_set_.bottom_right,
783 x, box_y_, box_height_,
784 hot_body_image_set_.top_right->width());
785 canvas->Restore();
788 x += body_image_set->top_right->width();
790 // Paint the drop-down.
791 if (drop_down_image_set) {
792 PaintImages(canvas,
793 drop_down_image_set->top, drop_down_image_set->center,
794 drop_down_image_set->bottom,
795 x, box_y_, box_height_, drop_down_image_set->top->width());
797 // Overlay our drop-down hot state.
798 if (drop_hover_animation_->GetCurrentValue() > 0) {
799 canvas->SaveLayerAlpha(
800 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
802 PaintImages(canvas,
803 drop_down_image_set->top, drop_down_image_set->center,
804 drop_down_image_set->bottom,
805 x, box_y_, box_height_, drop_down_image_set->top->width());
807 canvas->Restore();
811 // Restore the canvas to avoid file name etc. text are drawn flipped.
812 // Consequently, the x-axis of following canvas->DrawXXX() method should be
813 // mirrored so the text and images are down in the right positions.
814 canvas->Restore();
816 // Print the text, left aligned and always print the file extension.
817 // Last value of x was the end of the right image, just before the button.
818 // Note that in dangerous mode we use a label (as the text is multi-line).
819 if (!IsShowingWarningDialog()) {
820 base::string16 filename;
821 if (!disabled_while_opening_) {
822 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(),
823 font_list_, kTextWidth);
824 } else {
825 // First, Calculate the download status opening string width.
826 base::string16 status_string =
827 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
828 base::string16());
829 int status_string_width = gfx::GetStringWidth(status_string, font_list_);
830 // Then, elide the file name.
831 base::string16 filename_string =
832 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_,
833 kTextWidth - status_string_width);
834 // Last, concat the whole string.
835 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
836 filename_string);
839 int mirrored_x = GetMirroredXWithWidthInView(
840 DownloadShelf::kSmallProgressIconSize, kTextWidth);
841 SkColor file_name_color = GetThemeProvider()->GetColor(
842 ThemeProperties::COLOR_BOOKMARK_TEXT);
843 int y =
844 box_y_ + (status_text_.empty() ?
845 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
847 // Draw the file's name.
848 canvas->DrawStringRect(
849 filename, font_list_,
850 enabled() ? file_name_color : kFileNameDisabledColor,
851 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight()));
854 // Load the icon.
855 IconManager* im = g_browser_process->icon_manager();
856 gfx::Image* image = im->LookupIconFromFilepath(
857 download()->GetTargetFilePath(), IconLoader::SMALL);
858 const gfx::ImageSkia* icon = NULL;
859 if (IsShowingWarningDialog())
860 icon = warning_icon_;
861 else if (image)
862 icon = image->ToImageSkia();
864 // We count on the fact that the icon manager will cache the icons and if one
865 // is available, it will be cached here. We *don't* want to request the icon
866 // to be loaded here, since this will also get called if the icon can't be
867 // loaded, in which case LookupIcon will always be NULL. The loading will be
868 // triggered only when we think the status might change.
869 if (icon) {
870 if (!IsShowingWarningDialog()) {
871 DownloadItem::DownloadState state = download()->GetState();
872 DownloadShelf::BoundsAdjusterCallback rtl_mirror =
873 base::Bind(&RTLMirrorXForView, base::Unretained(this));
874 if (state == DownloadItem::IN_PROGRESS) {
875 DownloadShelf::PaintDownloadProgress(canvas,
876 rtl_mirror,
879 progress_angle_,
880 model_.PercentComplete(),
881 DownloadShelf::SMALL);
882 } else if (complete_animation_.get() &&
883 complete_animation_->is_animating()) {
884 if (state == DownloadItem::INTERRUPTED) {
885 DownloadShelf::PaintDownloadInterrupted(
886 canvas,
887 rtl_mirror,
890 complete_animation_->GetCurrentValue(),
891 DownloadShelf::SMALL);
892 } else {
893 DCHECK_EQ(DownloadItem::COMPLETE, state);
894 DownloadShelf::PaintDownloadComplete(
895 canvas,
896 rtl_mirror,
899 complete_animation_->GetCurrentValue(),
900 DownloadShelf::SMALL);
905 // Draw the icon image.
906 int icon_x, icon_y;
908 if (IsShowingWarningDialog()) {
909 icon_x = kLeftPadding + body_image_set->top_left->width();
910 icon_y = (height() - icon->height()) / 2;
911 } else {
912 icon_x = DownloadShelf::kSmallProgressIconOffset;
913 icon_y = DownloadShelf::kSmallProgressIconOffset;
915 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
916 if (enabled()) {
917 canvas->DrawImageInt(*icon, icon_x, icon_y);
918 } else {
919 // Use an alpha to make the image look disabled.
920 SkPaint paint;
921 paint.setAlpha(120);
922 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
927 void DownloadItemView::OnFocus() {
928 View::OnFocus();
929 // We render differently when focused.
930 SchedulePaint();
933 void DownloadItemView::OnBlur() {
934 View::OnBlur();
935 // We render differently when focused.
936 SchedulePaint();
939 void DownloadItemView::OpenDownload() {
940 DCHECK(!IsShowingWarningDialog());
941 // We're interested in how long it takes users to open downloads. If they
942 // open downloads super quickly, we should be concerned about clickjacking.
943 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
944 base::Time::Now() - creation_time_);
946 UpdateAccessibleName();
948 // Calling download()->OpenDownload may delete this, so this must be
949 // the last thing we do.
950 download()->OpenDownload();
953 bool DownloadItemView::SubmitDownloadToFeedbackService() {
954 #if defined(FULL_SAFE_BROWSING)
955 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
956 if (!sb_service)
957 return false;
958 safe_browsing::DownloadProtectionService* download_protection_service =
959 sb_service->download_protection_service();
960 if (!download_protection_service)
961 return false;
962 download_protection_service->feedback_service()->BeginFeedbackForDownload(
963 download());
964 // WARNING: we are deleted at this point. Don't access 'this'.
965 return true;
966 #else
967 NOTREACHED();
968 return false;
969 #endif
972 void DownloadItemView::PossiblySubmitDownloadToFeedbackService(bool enabled) {
973 if (!enabled || !SubmitDownloadToFeedbackService())
974 download()->Remove();
975 // WARNING: 'this' is deleted at this point. Don't access 'this'.
978 void DownloadItemView::LoadIcon() {
979 IconManager* im = g_browser_process->icon_manager();
980 last_download_item_path_ = download()->GetTargetFilePath();
981 im->LoadIcon(last_download_item_path_,
982 IconLoader::SMALL,
983 base::Bind(&DownloadItemView::OnExtractIconComplete,
984 base::Unretained(this)),
985 &cancelable_task_tracker_);
988 void DownloadItemView::LoadIconIfItemPathChanged() {
989 base::FilePath current_download_path = download()->GetTargetFilePath();
990 if (last_download_item_path_ == current_download_path)
991 return;
993 LoadIcon();
996 void DownloadItemView::UpdateColorsFromTheme() {
997 if (dangerous_download_label_ && GetThemeProvider()) {
998 dangerous_download_label_->SetEnabledColor(
999 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
1003 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
1004 ui::MenuSourceType source_type) {
1005 gfx::Point point = p;
1006 gfx::Size size;
1008 // Similar hack as in MenuButton.
1009 // We're about to show the menu from a mouse press. By showing from the
1010 // mouse press event we block RootView in mouse dispatching. This also
1011 // appears to cause RootView to get a mouse pressed BEFORE the mouse
1012 // release is seen, which means RootView sends us another mouse press no
1013 // matter where the user pressed. To force RootView to recalculate the
1014 // mouse target during the mouse press we explicitly set the mouse handler
1015 // to NULL.
1016 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
1017 SetMouseHandler(NULL);
1019 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
1020 // to drop down arrow button.
1021 if (source_type != ui::MENU_SOURCE_MOUSE &&
1022 source_type != ui::MENU_SOURCE_TOUCH) {
1023 drop_down_pressed_ = true;
1024 SetState(NORMAL, PUSHED);
1025 point.SetPoint(drop_down_x_left_, box_y_);
1026 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
1028 // Post a task to release the button. When we call the Run method on the menu
1029 // below, it runs an inner message loop that might cause us to be deleted.
1030 // Posting a task with a WeakPtr lets us safely handle the button release.
1031 base::MessageLoop::current()->PostNonNestableTask(
1032 FROM_HERE,
1033 base::Bind(&DownloadItemView::ReleaseDropDown,
1034 weak_ptr_factory_.GetWeakPtr()));
1035 views::View::ConvertPointToScreen(this, &point);
1037 if (!context_menu_.get())
1038 context_menu_.reset(new DownloadShelfContextMenuView(download()));
1040 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
1041 gfx::Rect(point, size), source_type);
1042 // We could be deleted now.
1045 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event,
1046 bool active_event) {
1047 // The event should not activate us in dangerous mode.
1048 if (mode_ == DANGEROUS_MODE)
1049 return;
1051 // Stop any completion animation.
1052 if (complete_animation_.get() && complete_animation_->is_animating())
1053 complete_animation_->End();
1055 if (active_event) {
1056 if (InDropDownButtonXCoordinateRange(event.x())) {
1057 if (context_menu_.get()) {
1058 // Ignore two close clicks. This typically happens when the user clicks
1059 // the button to close the menu.
1060 base::TimeDelta delta =
1061 base::TimeTicks::Now() - context_menu_->close_time();
1062 if (delta.InMilliseconds() < views::kMinimumMsBetweenButtonClicks)
1063 return;
1065 drop_down_pressed_ = true;
1066 SetState(NORMAL, PUSHED);
1067 // We are setting is_mouse_gesture to false when calling ShowContextMenu
1068 // so that the positioning of the context menu will be similar to a
1069 // keyboard invocation. I.e. we want the menu to always be positioned
1070 // next to the drop down button instead of the next to the pointer.
1071 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD);
1072 // Once called, it is possible that *this was deleted (e.g.: due to
1073 // invoking the 'Discard' action.)
1074 } else if (!IsShowingWarningDialog()) {
1075 SetState(PUSHED, NORMAL);
1080 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event,
1081 bool active_event) {
1082 // Mouse should not activate us in dangerous mode.
1083 if (mode_ == DANGEROUS_MODE)
1084 return;
1086 SetState(NORMAL, NORMAL);
1088 if (!active_event ||
1089 InDropDownButtonXCoordinateRange(event.x()) ||
1090 IsShowingWarningDialog()) {
1091 return;
1094 // OpenDownload may delete this, so don't add any code after this line.
1095 OpenDownload();
1098 // Load an icon for the file type we're downloading, and animate any in progress
1099 // download state.
1100 void DownloadItemView::PaintImages(gfx::Canvas* canvas,
1101 const gfx::ImageSkia* top_image,
1102 const gfx::ImageSkia* center_image,
1103 const gfx::ImageSkia* bottom_image,
1104 int x, int y, int height, int width) {
1105 int middle_height = height - top_image->height() - bottom_image->height();
1106 // Draw the top.
1107 canvas->DrawImageInt(*top_image,
1108 0, 0, top_image->width(), top_image->height(),
1109 x, y, width, top_image->height(), false);
1110 y += top_image->height();
1111 // Draw the center.
1112 canvas->DrawImageInt(*center_image,
1113 0, 0, center_image->width(), center_image->height(),
1114 x, y, width, middle_height, false);
1115 y += middle_height;
1116 // Draw the bottom.
1117 canvas->DrawImageInt(*bottom_image,
1118 0, 0, bottom_image->width(), bottom_image->height(),
1119 x, y, width, bottom_image->height(), false);
1122 void DownloadItemView::SetState(State new_body_state, State new_drop_state) {
1123 // If we are showing a warning dialog, we don't change body state.
1124 if (IsShowingWarningDialog()) {
1125 new_body_state = NORMAL;
1127 // Current body_state_ should always be NORMAL for warning dialogs.
1128 DCHECK_EQ(NORMAL, body_state_);
1129 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1130 DCHECK_NE(DANGEROUS_MODE, mode_);
1132 // Avoid extra SchedulePaint()s if the state is going to be the same.
1133 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state)
1134 return;
1136 AnimateStateTransition(body_state_, new_body_state,
1137 body_hover_animation_.get());
1138 AnimateStateTransition(drop_down_state_, new_drop_state,
1139 drop_hover_animation_.get());
1140 body_state_ = new_body_state;
1141 drop_down_state_ = new_drop_state;
1142 SchedulePaint();
1145 void DownloadItemView::ClearWarningDialog() {
1146 DCHECK(download()->GetDangerType() ==
1147 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1148 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1150 mode_ = NORMAL_MODE;
1151 body_state_ = NORMAL;
1152 drop_down_state_ = NORMAL;
1154 // ExperienceSampling: User proceeded through the warning.
1155 if (sampling_event_.get()) {
1156 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
1157 sampling_event_.reset(NULL);
1159 // Remove the views used by the warning dialog.
1160 if (save_button_) {
1161 RemoveChildView(save_button_);
1162 delete save_button_;
1163 save_button_ = NULL;
1165 RemoveChildView(discard_button_);
1166 delete discard_button_;
1167 discard_button_ = NULL;
1168 RemoveChildView(dangerous_download_label_);
1169 delete dangerous_download_label_;
1170 dangerous_download_label_ = NULL;
1171 dangerous_download_label_sized_ = false;
1172 cached_button_size_.SetSize(0,0);
1174 // Set the accessible name back to the status and filename instead of the
1175 // download warning.
1176 UpdateAccessibleName();
1177 UpdateDropDownButtonPosition();
1179 // We need to load the icon now that the download has the real path.
1180 LoadIcon();
1182 // Force the shelf to layout again as our size has changed.
1183 shelf_->Layout();
1184 shelf_->SchedulePaint();
1186 TooltipTextChanged();
1189 void DownloadItemView::ShowWarningDialog() {
1190 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
1191 time_download_warning_shown_ = base::Time::Now();
1192 content::DownloadDangerType danger_type = download()->GetDangerType();
1193 RecordDangerousDownloadWarningShown(danger_type);
1194 #if defined(FULL_SAFE_BROWSING)
1195 if (model_.ShouldAllowDownloadFeedback()) {
1196 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1197 danger_type);
1199 #endif
1200 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
1202 // ExperienceSampling: Dangerous or malicious download warning is being shown
1203 // to the user, so we start a new SamplingEvent and track it.
1204 std::string event_name = model_.MightBeMalicious()
1205 ? ExperienceSamplingEvent::kMaliciousDownload
1206 : ExperienceSamplingEvent::kDangerousDownload;
1207 sampling_event_.reset(
1208 new ExperienceSamplingEvent(event_name,
1209 download()->GetURL(),
1210 download()->GetReferrerUrl(),
1211 download()->GetBrowserContext()));
1213 body_state_ = NORMAL;
1214 drop_down_state_ = NORMAL;
1215 if (mode_ == DANGEROUS_MODE) {
1216 save_button_ = new views::LabelButton(
1217 this, model_.GetWarningConfirmButtonText());
1218 save_button_->SetStyle(views::Button::STYLE_BUTTON);
1219 AddChildView(save_button_);
1221 int discard_button_message = model_.IsMalicious() ?
1222 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD;
1223 discard_button_ = new views::LabelButton(
1224 this, l10n_util::GetStringUTF16(discard_button_message));
1225 discard_button_->SetStyle(views::Button::STYLE_BUTTON);
1226 AddChildView(discard_button_);
1228 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1229 switch (danger_type) {
1230 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
1231 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
1232 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
1233 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
1234 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
1235 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING);
1236 break;
1238 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
1239 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
1240 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
1241 case content::DOWNLOAD_DANGER_TYPE_MAX:
1242 NOTREACHED();
1243 // fallthrough
1245 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1246 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
1248 base::string16 dangerous_label =
1249 model_.GetWarningText(font_list_, kTextWidth);
1250 dangerous_download_label_ = new views::Label(dangerous_label);
1251 dangerous_download_label_->SetMultiLine(true);
1252 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1253 dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
1254 AddChildView(dangerous_download_label_);
1255 SizeLabelToMinWidth();
1256 UpdateDropDownButtonPosition();
1257 TooltipTextChanged();
1260 gfx::Size DownloadItemView::GetButtonSize() const {
1261 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
1262 gfx::Size size;
1264 // We cache the size when successfully retrieved, not for performance reasons
1265 // but because if this DownloadItemView is being animated while the tab is
1266 // not showing, the native buttons are not parented and their preferred size
1267 // is 0, messing-up the layout.
1268 if (cached_button_size_.width() != 0)
1269 return cached_button_size_;
1271 if (save_button_)
1272 size = save_button_->GetMinimumSize();
1273 gfx::Size discard_size = discard_button_->GetMinimumSize();
1275 size.SetSize(std::max(size.width(), discard_size.width()),
1276 std::max(size.height(), discard_size.height()));
1278 if (size.width() != 0)
1279 cached_button_size_ = size;
1281 return size;
1284 // This method computes the minimum width of the label for displaying its text
1285 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1286 // configuration with minimum width.
1287 void DownloadItemView::SizeLabelToMinWidth() {
1288 if (dangerous_download_label_sized_)
1289 return;
1291 base::string16 label_text = dangerous_download_label_->text();
1292 base::TrimWhitespace(label_text, base::TRIM_ALL, &label_text);
1293 DCHECK_EQ(base::string16::npos, label_text.find('\n'));
1295 // Make the label big so that GetPreferredSize() is not constrained by the
1296 // current width.
1297 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1299 // Use a const string from here. BreakIterator requies that text.data() not
1300 // change during its lifetime.
1301 const base::string16 original_text(label_text);
1302 // Using BREAK_WORD can work in most cases, but it can also break
1303 // lines where it should not. Using BREAK_LINE is safer although
1304 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1305 base::i18n::BreakIterator iter(original_text,
1306 base::i18n::BreakIterator::BREAK_LINE);
1307 bool status = iter.Init();
1308 DCHECK(status);
1310 base::string16 prev_text = original_text;
1311 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1312 int min_width = size.width();
1314 // Go through the string and try each line break (starting with no line break)
1315 // searching for the optimal line break position. Stop if we find one that
1316 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1317 // a short string (e.g.: "This file is malicious") from being broken up
1318 // unnecessarily.
1319 while (iter.Advance() && min_width > kDangerousTextWidth) {
1320 size_t pos = iter.pos();
1321 if (pos >= original_text.length())
1322 break;
1323 base::string16 current_text = original_text;
1324 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1325 // return false and inserting a new line after a surrogate pair
1326 // is perfectly ok.
1327 base::char16 line_end_char = current_text[pos - 1];
1328 if (u_isUWhiteSpace(line_end_char))
1329 current_text.replace(pos - 1, 1, 1, base::char16('\n'));
1330 else
1331 current_text.insert(pos, 1, base::char16('\n'));
1332 dangerous_download_label_->SetText(current_text);
1333 size = dangerous_download_label_->GetPreferredSize();
1335 // If the width is growing again, it means we passed the optimal width spot.
1336 if (size.width() > min_width) {
1337 dangerous_download_label_->SetText(prev_text);
1338 break;
1339 } else {
1340 min_width = size.width();
1342 prev_text = current_text;
1345 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1346 dangerous_download_label_sized_ = true;
1349 void DownloadItemView::Reenable() {
1350 disabled_while_opening_ = false;
1351 SetEnabled(true); // Triggers a repaint.
1354 void DownloadItemView::ReleaseDropDown() {
1355 drop_down_pressed_ = false;
1356 SetState(NORMAL, NORMAL);
1359 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1360 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1361 return true;
1362 return false;
1365 void DownloadItemView::UpdateAccessibleName() {
1366 base::string16 new_name;
1367 if (IsShowingWarningDialog()) {
1368 new_name = dangerous_download_label_->text();
1369 } else {
1370 new_name = status_text_ + base::char16(' ') +
1371 download()->GetFileNameToReportUser().LossyDisplayName();
1374 // If the name has changed, notify assistive technology that the name
1375 // has changed so they can announce it immediately.
1376 if (new_name != accessible_name_) {
1377 accessible_name_ = new_name;
1378 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true);
1382 void DownloadItemView::UpdateDropDownButtonPosition() {
1383 gfx::Size size = GetPreferredSize();
1384 if (base::i18n::IsRTL()) {
1385 // Drop down button is glued to the left of the download shelf.
1386 drop_down_x_left_ = 0;
1387 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
1388 } else {
1389 // Drop down button is glued to the right of the download shelf.
1390 drop_down_x_left_ =
1391 size.width() - normal_drop_down_image_set_.top->width();
1392 drop_down_x_right_ = size.width();
1396 void DownloadItemView::AnimateStateTransition(State from, State to,
1397 gfx::SlideAnimation* animation) {
1398 if (from == NORMAL && to == HOT) {
1399 animation->Show();
1400 } else if (from == HOT && to == NORMAL) {
1401 animation->Hide();
1402 } else if (from != to) {
1403 animation->Reset((to == HOT) ? 1.0 : 0.0);