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