1 // Copyright (c) 2011 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 <vector>
8
9 #include "base/callback.h"
10 #include "base/file_path.h"
11 #include "base/i18n/break_iterator.h"
12 #include "base/i18n/rtl.h"
13 #include "base/metrics/histogram.h"
14 #include "base/string_util.h"
15 #include "base/sys_string_conversions.h"
16 #include "base/utf_string_conversions.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/download/download_item_model.h"
19 #include "chrome/browser/download/download_util.h"
20 #include "chrome/browser/themes/theme_service.h"
21 #include "chrome/browser/ui/views/download/download_shelf_view.h"
22 #include "grit/generated_resources.h"
23 #include "grit/theme_resources.h"
24 #include "ui/base/accessibility/accessible_view_state.h"
25 #include "ui/base/animation/slide_animation.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/base/text/text_elider.h"
29 #include "ui/gfx/canvas_skia.h"
30 #include "ui/gfx/color_utils.h"
31 #include "ui/gfx/image.h"
32 #include "unicode/uchar.h"
33 #include "views/controls/button/native_button.h"
34 #include "views/controls/menu/menu_2.h"
35 #include "views/widget/root_view.h"
36 #include "views/widget/widget.h"
37
38 using base::TimeDelta;
39
40 // TODO(paulg): These may need to be adjusted when download progress
41 // animation is added, and also possibly to take into account
42 // different screen resolutions.
43 static const int kTextWidth = 140; // Pixels
44 static const int kDangerousTextWidth = 200; // Pixels
45 static const int kHorizontalTextPadding = 2; // Pixels
46 static const int kVerticalPadding = 3; // Pixels
47 static const int kVerticalTextSpacer = 2; // Pixels
48 static const int kVerticalTextPadding = 2; // Pixels
49
50 // The maximum number of characters we show in a file name when displaying the
51 // dangerous download message.
52 static const int kFileNameMaxLength = 20;
53
54 // We add some padding before the left image so that the progress animation icon
55 // hides the corners of the left image.
56 static const int kLeftPadding = 0; // Pixels.
57
58 // The space between the Save and Discard buttons when prompting for a dangerous
59 // download.
60 static const int kButtonPadding = 5; // Pixels.
61
62 // The space on the left and right side of the dangerous download label.
63 static const int kLabelPadding = 4; // Pixels.
64
65 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
66
67 // How long the 'download complete' animation should last for.
68 static const int kCompleteAnimationDurationMs = 2500;
69
70 // How long the 'download interrupted' animation should last for.
71 static const int kInterruptedAnimationDurationMs = 2500;
72
73 // How long we keep the item disabled after the user clicked it to open the
74 // downloaded item.
75 static const int kDisabledOnOpenDuration = 3000;
76
77 // Darken light-on-dark download status text by 20% before drawing, thus
78 // creating a "muted" version of title text for both dark-on-light and
79 // light-on-dark themes.
80 static const double kDownloadItemLuminanceMod = 0.8;
81
82 // DownloadShelfContextMenuWin -------------------------------------------------
83
84 class DownloadShelfContextMenuWin : public DownloadShelfContextMenu {
85 public:
DownloadShelfContextMenuWin(BaseDownloadItemModel * model)86 explicit DownloadShelfContextMenuWin(BaseDownloadItemModel* model)
87 : DownloadShelfContextMenu(model) {
88 DCHECK(model);
89 }
90
Run(const gfx::Point & point)91 void Run(const gfx::Point& point) {
92 if (download_->IsComplete())
93 menu_.reset(new views::Menu2(GetFinishedMenuModel()));
94 else
95 menu_.reset(new views::Menu2(GetInProgressMenuModel()));
96
97 // The menu's alignment is determined based on the UI layout.
98 views::Menu2::Alignment alignment;
99 if (base::i18n::IsRTL())
100 alignment = views::Menu2::ALIGN_TOPRIGHT;
101 else
102 alignment = views::Menu2::ALIGN_TOPLEFT;
103 menu_->RunMenuAt(point, alignment);
104 }
105
106 // This method runs when the caller has been deleted and we should not attempt
107 // to access |download_|.
Stop()108 void Stop() {
109 download_ = NULL;
110 }
111
112 private:
113 scoped_ptr<views::Menu2> menu_;
114 };
115
116 // DownloadItemView ------------------------------------------------------------
117
DownloadItemView(DownloadItem * download,DownloadShelfView * parent,BaseDownloadItemModel * model)118 DownloadItemView::DownloadItemView(DownloadItem* download,
119 DownloadShelfView* parent,
120 BaseDownloadItemModel* model)
121 : warning_icon_(NULL),
122 download_(download),
123 parent_(parent),
124 status_text_(UTF16ToWide(
125 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING))),
126 show_status_text_(true),
127 body_state_(NORMAL),
128 drop_down_state_(NORMAL),
129 progress_angle_(download_util::kStartAngleDegrees),
130 drop_down_pressed_(false),
131 dragging_(false),
132 starting_drag_(false),
133 model_(model),
134 save_button_(NULL),
135 discard_button_(NULL),
136 dangerous_download_label_(NULL),
137 dangerous_download_label_sized_(false),
138 disabled_while_opening_(false),
139 creation_time_(base::Time::Now()),
140 ALLOW_THIS_IN_INITIALIZER_LIST(reenable_method_factory_(this)),
141 deleted_(NULL) {
142 DCHECK(download_);
143 download_->AddObserver(this);
144
145 ResourceBundle &rb = ResourceBundle::GetSharedInstance();
146
147 BodyImageSet normal_body_image_set = {
148 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
149 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
150 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
151 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
152 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
153 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
154 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
155 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
156 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
157 };
158 normal_body_image_set_ = normal_body_image_set;
159
160 DropDownImageSet normal_drop_down_image_set = {
161 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
162 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
163 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
164 };
165 normal_drop_down_image_set_ = normal_drop_down_image_set;
166
167 BodyImageSet hot_body_image_set = {
168 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
169 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
170 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
171 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
172 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
173 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
174 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
175 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
176 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
177 };
178 hot_body_image_set_ = hot_body_image_set;
179
180 DropDownImageSet hot_drop_down_image_set = {
181 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
182 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
183 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
184 };
185 hot_drop_down_image_set_ = hot_drop_down_image_set;
186
187 BodyImageSet pushed_body_image_set = {
188 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
189 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
190 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
191 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
192 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
193 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
194 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
195 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
196 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
197 };
198 pushed_body_image_set_ = pushed_body_image_set;
199
200 DropDownImageSet pushed_drop_down_image_set = {
201 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
202 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
203 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
204 };
205 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
206
207 BodyImageSet dangerous_mode_body_image_set = {
208 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
209 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
210 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
211 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
212 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
213 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
214 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
215 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
216 rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
217 };
218 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
219
220 LoadIcon();
221 tooltip_text_ =
222 UTF16ToWide(download_->GetFileNameToReportUser().LossyDisplayName());
223
224 font_ = ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
225 box_height_ = std::max<int>(2 * kVerticalPadding + font_.GetHeight() +
226 kVerticalTextPadding + font_.GetHeight(),
227 2 * kVerticalPadding +
228 normal_body_image_set_.top_left->height() +
229 normal_body_image_set_.bottom_left->height());
230
231 if (download_util::kSmallProgressIconSize > box_height_)
232 box_y_ = (download_util::kSmallProgressIconSize - box_height_) / 2;
233 else
234 box_y_ = kVerticalPadding;
235
236 gfx::Size size = GetPreferredSize();
237 if (base::i18n::IsRTL()) {
238 // Drop down button is glued to the left of the download shelf.
239 drop_down_x_left_ = 0;
240 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
241 } else {
242 // Drop down button is glued to the right of the download shelf.
243 drop_down_x_left_ =
244 size.width() - normal_drop_down_image_set_.top->width();
245 drop_down_x_right_ = size.width();
246 }
247
248 body_hover_animation_.reset(new ui::SlideAnimation(this));
249 drop_hover_animation_.reset(new ui::SlideAnimation(this));
250
251 if (download->safety_state() == DownloadItem::DANGEROUS) {
252 tooltip_text_.clear();
253 body_state_ = DANGEROUS;
254 drop_down_state_ = DANGEROUS;
255 save_button_ = new views::NativeButton(this,
256 UTF16ToWide(l10n_util::GetStringUTF16(
257 download->is_extension_install() ?
258 IDS_CONTINUE_EXTENSION_DOWNLOAD : IDS_SAVE_DOWNLOAD)));
259 save_button_->set_ignore_minimum_size(true);
260 discard_button_ = new views::NativeButton(
261 this, UTF16ToWide(l10n_util::GetStringUTF16(IDS_DISCARD_DOWNLOAD)));
262 discard_button_->set_ignore_minimum_size(true);
263 AddChildView(save_button_);
264 AddChildView(discard_button_);
265
266 // Ensure the file name is not too long.
267
268 // Extract the file extension (if any).
269 FilePath filename(download->target_name());
270 #if defined(OS_LINUX)
271 string16 extension = WideToUTF16(base::SysNativeMBToWide(
272 filename.Extension()));
273 #else
274 string16 extension = filename.Extension();
275 #endif
276
277 // Remove leading '.'
278 if (extension.length() > 0)
279 extension = extension.substr(1);
280 #if defined(OS_LINUX)
281 string16 rootname = WideToUTF16(base::SysNativeMBToWide(
282 filename.RemoveExtension().value()));
283 #else
284 string16 rootname = filename.RemoveExtension().value();
285 #endif
286
287 // Elide giant extensions (this shouldn't currently be hit, but might
288 // in future, should we ever notice unsafe giant extensions).
289 if (extension.length() > kFileNameMaxLength / 2)
290 ui::ElideString(extension, kFileNameMaxLength / 2, &extension);
291
292 // The dangerous download label text and icon are different
293 // under different cases.
294 string16 dangerous_label;
295 if (download->danger_type() == DownloadItem::DANGEROUS_URL) {
296 // Safebrowsing shows the download URL leads to malicious file.
297 warning_icon_ = rb.GetBitmapNamed(IDR_SAFEBROWSING_WARNING);
298 dangerous_label =
299 l10n_util::GetStringUTF16(IDS_PROMPT_UNSAFE_DOWNLOAD_URL);
300 } else {
301 // The download file has dangerous file type (e.g.: an executable).
302 DCHECK(download->danger_type() == DownloadItem::DANGEROUS_FILE);
303 warning_icon_ = rb.GetBitmapNamed(IDR_WARNING);
304 if (download->is_extension_install()) {
305 dangerous_label =
306 l10n_util::GetStringUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
307 } else {
308 ui::ElideString(rootname,
309 kFileNameMaxLength - extension.length(),
310 &rootname);
311 string16 filename = rootname + ASCIIToUTF16(".") + extension;
312 filename = base::i18n::GetDisplayStringInLTRDirectionality(filename);
313 dangerous_label =
314 l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD, filename);
315 }
316 }
317
318 dangerous_download_label_ = new views::Label(UTF16ToWide(dangerous_label));
319 dangerous_download_label_->SetMultiLine(true);
320 dangerous_download_label_->SetHorizontalAlignment(
321 views::Label::ALIGN_LEFT);
322 AddChildView(dangerous_download_label_);
323 SizeLabelToMinWidth();
324 }
325
326 UpdateAccessibleName();
327 set_accessibility_focusable(true);
328
329 // Set up our animation.
330 StartDownloadProgress();
331 }
332
~DownloadItemView()333 DownloadItemView::~DownloadItemView() {
334 if (context_menu_.get()) {
335 context_menu_->Stop();
336 }
337 icon_consumer_.CancelAllRequests();
338 StopDownloadProgress();
339 download_->RemoveObserver(this);
340 if (deleted_)
341 *deleted_ = true;
342 }
343
344 // Progress animation handlers.
345
UpdateDownloadProgress()346 void DownloadItemView::UpdateDownloadProgress() {
347 progress_angle_ = (progress_angle_ +
348 download_util::kUnknownIncrementDegrees) %
349 download_util::kMaxDegrees;
350 SchedulePaint();
351 }
352
StartDownloadProgress()353 void DownloadItemView::StartDownloadProgress() {
354 if (progress_timer_.IsRunning())
355 return;
356 progress_timer_.Start(
357 TimeDelta::FromMilliseconds(download_util::kProgressRateMs), this,
358 &DownloadItemView::UpdateDownloadProgress);
359 }
360
StopDownloadProgress()361 void DownloadItemView::StopDownloadProgress() {
362 progress_timer_.Stop();
363 }
364
OnExtractIconComplete(IconManager::Handle handle,gfx::Image * icon_bitmap)365 void DownloadItemView::OnExtractIconComplete(IconManager::Handle handle,
366 gfx::Image* icon_bitmap) {
367 if (icon_bitmap)
368 parent()->SchedulePaint();
369 }
370
371 // DownloadObserver interface.
372
373 // Update the progress graphic on the icon and our text status label
374 // to reflect our current bytes downloaded, time remaining.
OnDownloadUpdated(DownloadItem * download)375 void DownloadItemView::OnDownloadUpdated(DownloadItem* download) {
376 DCHECK(download == download_);
377
378 if (body_state_ == DANGEROUS &&
379 download->safety_state() == DownloadItem::DANGEROUS_BUT_VALIDATED) {
380 // We have been approved.
381 ClearDangerousMode();
382 }
383
384 string16 status_text = model_->GetStatusText();
385 switch (download_->state()) {
386 case DownloadItem::IN_PROGRESS:
387 download_->is_paused() ? StopDownloadProgress() : StartDownloadProgress();
388 break;
389 case DownloadItem::INTERRUPTED:
390 StopDownloadProgress();
391 complete_animation_.reset(new ui::SlideAnimation(this));
392 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
393 complete_animation_->SetTweenType(ui::Tween::LINEAR);
394 complete_animation_->Show();
395 if (status_text.empty())
396 show_status_text_ = false;
397 SchedulePaint();
398 LoadIcon();
399 break;
400 case DownloadItem::COMPLETE:
401 if (download_->auto_opened()) {
402 parent_->RemoveDownloadView(this); // This will delete us!
403 return;
404 }
405 StopDownloadProgress();
406 complete_animation_.reset(new ui::SlideAnimation(this));
407 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
408 complete_animation_->SetTweenType(ui::Tween::LINEAR);
409 complete_animation_->Show();
410 if (status_text.empty())
411 show_status_text_ = false;
412 SchedulePaint();
413 LoadIcon();
414 break;
415 case DownloadItem::CANCELLED:
416 StopDownloadProgress();
417 LoadIcon();
418 break;
419 case DownloadItem::REMOVING:
420 parent_->RemoveDownloadView(this); // This will delete us!
421 return;
422 default:
423 NOTREACHED();
424 }
425
426 status_text_ = UTF16ToWideHack(status_text);
427 UpdateAccessibleName();
428
429 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
430 // are spaces between each DownloadItemView that the parent is responsible
431 // for painting.
432 parent()->SchedulePaint();
433 }
434
OnDownloadOpened(DownloadItem * download)435 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
436 disabled_while_opening_ = true;
437 SetEnabled(false);
438 MessageLoop::current()->PostDelayedTask(
439 FROM_HERE,
440 reenable_method_factory_.NewRunnableMethod(&DownloadItemView::Reenable),
441 kDisabledOnOpenDuration);
442
443 // Notify our parent.
444 parent_->OpenedDownload(this);
445 }
446
447 // View overrides
448
449 // In dangerous mode we have to layout our buttons.
Layout()450 void DownloadItemView::Layout() {
451 if (IsDangerousMode()) {
452 dangerous_download_label_->SetColor(
453 GetThemeProvider()->GetColor(ThemeService::COLOR_BOOKMARK_TEXT));
454
455 int x = kLeftPadding + dangerous_mode_body_image_set_.top_left->width() +
456 warning_icon_->width() + kLabelPadding;
457 int y = (height() - dangerous_download_label_->height()) / 2;
458 dangerous_download_label_->SetBounds(x, y,
459 dangerous_download_label_->width(),
460 dangerous_download_label_->height());
461 gfx::Size button_size = GetButtonSize();
462 x += dangerous_download_label_->width() + kLabelPadding;
463 y = (height() - button_size.height()) / 2;
464 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
465 x += button_size.width() + kButtonPadding;
466 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
467 }
468 }
469
GetPreferredSize()470 gfx::Size DownloadItemView::GetPreferredSize() {
471 int width, height;
472
473 // First, we set the height to the height of two rows or text plus margins.
474 height = 2 * kVerticalPadding + 2 * font_.GetHeight() + kVerticalTextPadding;
475 // Then we increase the size if the progress icon doesn't fit.
476 height = std::max<int>(height, download_util::kSmallProgressIconSize);
477
478 if (IsDangerousMode()) {
479 width = kLeftPadding + dangerous_mode_body_image_set_.top_left->width();
480 width += warning_icon_->width() + kLabelPadding;
481 width += dangerous_download_label_->width() + kLabelPadding;
482 gfx::Size button_size = GetButtonSize();
483 // Make sure the button fits.
484 height = std::max<int>(height, 2 * kVerticalPadding + button_size.height());
485 // Then we make sure the warning icon fits.
486 height = std::max<int>(height, 2 * kVerticalPadding +
487 warning_icon_->height());
488 width += button_size.width() * 2 + kButtonPadding;
489 width += dangerous_mode_body_image_set_.top_right->width();
490 } else {
491 width = kLeftPadding + normal_body_image_set_.top_left->width();
492 width += download_util::kSmallProgressIconSize;
493 width += kTextWidth;
494 width += normal_body_image_set_.top_right->width();
495 width += normal_drop_down_image_set_.top->width();
496 }
497 return gfx::Size(width, height);
498 }
499
500 // Handle a mouse click and open the context menu if the mouse is
501 // over the drop-down region.
OnMousePressed(const views::MouseEvent & event)502 bool DownloadItemView::OnMousePressed(const views::MouseEvent& event) {
503 // Mouse should not activate us in dangerous mode.
504 if (IsDangerousMode())
505 return true;
506
507 // Stop any completion animation.
508 if (complete_animation_.get() && complete_animation_->is_animating())
509 complete_animation_->End();
510
511 gfx::Point menu_location(event.location());
512 if (event.IsOnlyLeftMouseButton()) {
513 if (!InDropDownButtonXCoordinateRange(event.x())) {
514 SetState(PUSHED, NORMAL);
515 return true;
516 }
517
518 // Anchor the menu below the dropmarker.
519 menu_location.SetPoint(base::i18n::IsRTL() ?
520 drop_down_x_right_ : drop_down_x_left_,
521 height());
522 drop_down_pressed_ = true;
523 SetState(NORMAL, PUSHED);
524 }
525 ShowContextMenu(menu_location, true);
526 return true;
527 }
528
529 // Handle drag (file copy) operations.
OnMouseDragged(const views::MouseEvent & event)530 bool DownloadItemView::OnMouseDragged(const views::MouseEvent& event) {
531 // Mouse should not activate us in dangerous mode.
532 if (IsDangerousMode())
533 return true;
534
535 if (!starting_drag_) {
536 starting_drag_ = true;
537 drag_start_point_ = event.location();
538 }
539 if (dragging_) {
540 if (download_->IsComplete()) {
541 IconManager* im = g_browser_process->icon_manager();
542 gfx::Image* icon = im->LookupIcon(download_->GetUserVerifiedFilePath(),
543 IconLoader::SMALL);
544 if (icon) {
545 views::Widget* widget = GetWidget();
546 download_util::DragDownload(download_, icon,
547 widget ? widget->GetNativeView() : NULL);
548 }
549 }
550 } else if (ExceededDragThreshold(
551 event.location().x() - drag_start_point_.x(),
552 event.location().y() - drag_start_point_.y())) {
553 dragging_ = true;
554 }
555 return true;
556 }
557
OnMouseReleased(const views::MouseEvent & event)558 void DownloadItemView::OnMouseReleased(const views::MouseEvent& event) {
559 // Mouse should not activate us in dangerous mode.
560 if (IsDangerousMode())
561 return;
562
563 if (event.IsOnlyLeftMouseButton() &&
564 !InDropDownButtonXCoordinateRange(event.x())) {
565 OpenDownload();
566 }
567
568 SetState(NORMAL, NORMAL);
569 }
570
OnMouseCaptureLost()571 void DownloadItemView::OnMouseCaptureLost() {
572 // Mouse should not activate us in dangerous mode.
573 if (IsDangerousMode())
574 return;
575
576 if (dragging_) {
577 // Starting a drag results in a MouseCaptureLost.
578 dragging_ = false;
579 starting_drag_ = false;
580 } else {
581 SetState(NORMAL, NORMAL);
582 }
583 }
584
OnMouseMoved(const views::MouseEvent & event)585 void DownloadItemView::OnMouseMoved(const views::MouseEvent& event) {
586 // Mouse should not activate us in dangerous mode.
587 if (IsDangerousMode())
588 return;
589
590 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
591 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
592 if (on_body) {
593 body_hover_animation_->Show();
594 drop_hover_animation_->Hide();
595 } else {
596 body_hover_animation_->Hide();
597 drop_hover_animation_->Show();
598 }
599 }
600
OnMouseExited(const views::MouseEvent & event)601 void DownloadItemView::OnMouseExited(const views::MouseEvent& event) {
602 // Mouse should not activate us in dangerous mode.
603 if (IsDangerousMode())
604 return;
605
606 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
607 body_hover_animation_->Hide();
608 drop_hover_animation_->Hide();
609 }
610
OnKeyPressed(const views::KeyEvent & event)611 bool DownloadItemView::OnKeyPressed(const views::KeyEvent& event) {
612 // Key press should not activate us in dangerous mode.
613 if (IsDangerousMode())
614 return true;
615
616 if (event.key_code() == ui::VKEY_SPACE ||
617 event.key_code() == ui::VKEY_RETURN) {
618 OpenDownload();
619 return true;
620 }
621 return false;
622 }
623
GetTooltipText(const gfx::Point & p,std::wstring * tooltip)624 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
625 std::wstring* tooltip) {
626 if (tooltip_text_.empty())
627 return false;
628
629 tooltip->assign(tooltip_text_);
630 return true;
631 }
632
ShowContextMenu(const gfx::Point & p,bool is_mouse_gesture)633 void DownloadItemView::ShowContextMenu(const gfx::Point& p,
634 bool is_mouse_gesture) {
635 gfx::Point point = p;
636
637 // Similar hack as in MenuButton.
638 // We're about to show the menu from a mouse press. By showing from the
639 // mouse press event we block RootView in mouse dispatching. This also
640 // appears to cause RootView to get a mouse pressed BEFORE the mouse
641 // release is seen, which means RootView sends us another mouse press no
642 // matter where the user pressed. To force RootView to recalculate the
643 // mouse target during the mouse press we explicitly set the mouse handler
644 // to NULL.
645 GetRootView()->SetMouseHandler(NULL);
646
647 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
648 // to drop down arrow button.
649 if (!is_mouse_gesture) {
650 drop_down_pressed_ = true;
651 SetState(NORMAL, PUSHED);
652
653 point.set_y(height());
654 if (base::i18n::IsRTL())
655 point.set_x(drop_down_x_right_);
656 else
657 point.set_x(drop_down_x_left_);
658 }
659
660 views::View::ConvertPointToScreen(this, &point);
661
662 if (!context_menu_.get())
663 context_menu_.reset(new DownloadShelfContextMenuWin(model_.get()));
664 // When we call the Run method on the menu, it runs an inner message loop
665 // that might causes us to be deleted.
666 bool deleted = false;
667 deleted_ = &deleted;
668 context_menu_->Run(point);
669 if (deleted)
670 return; // We have been deleted! Don't access 'this'.
671 deleted_ = NULL;
672
673 // If the menu action was to remove the download, this view will also be
674 // invalid so we must not access 'this' in this case.
675 if (context_menu_->download()) {
676 drop_down_pressed_ = false;
677 // Showing the menu blocks. Here we revert the state.
678 SetState(NORMAL, NORMAL);
679 }
680 }
681
GetAccessibleState(ui::AccessibleViewState * state)682 void DownloadItemView::GetAccessibleState(ui::AccessibleViewState* state) {
683 state->name = accessible_name_;
684 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
685 if (download_->safety_state() == DownloadItem::DANGEROUS) {
686 state->state = ui::AccessibilityTypes::STATE_UNAVAILABLE;
687 } else {
688 state->state = ui::AccessibilityTypes::STATE_HASPOPUP;
689 }
690 }
691
ButtonPressed(views::Button * sender,const views::Event & event)692 void DownloadItemView::ButtonPressed(
693 views::Button* sender, const views::Event& event) {
694 if (sender == discard_button_) {
695 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download",
696 base::Time::Now() - creation_time_);
697 if (download_->IsPartialDownload())
698 download_->Cancel(true);
699 download_->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD);
700 // WARNING: we are deleted at this point. Don't access 'this'.
701 } else if (sender == save_button_) {
702 // The user has confirmed a dangerous download. We'd record how quickly the
703 // user did this to detect whether we're being clickjacked.
704 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
705 base::Time::Now() - creation_time_);
706 // This will change the state and notify us.
707 download_->DangerousDownloadValidated();
708 }
709 }
710
AnimationProgressed(const ui::Animation * animation)711 void DownloadItemView::AnimationProgressed(const ui::Animation* animation) {
712 // We don't care if what animation (body button/drop button/complete),
713 // is calling back, as they all have to go through the same paint call.
714 SchedulePaint();
715 }
716
OnPaint(gfx::Canvas * canvas)717 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
718 BodyImageSet* body_image_set = NULL;
719 switch (body_state_) {
720 case NORMAL:
721 case HOT:
722 body_image_set = &normal_body_image_set_;
723 break;
724 case PUSHED:
725 body_image_set = &pushed_body_image_set_;
726 break;
727 case DANGEROUS:
728 body_image_set = &dangerous_mode_body_image_set_;
729 break;
730 default:
731 NOTREACHED();
732 }
733 DropDownImageSet* drop_down_image_set = NULL;
734 switch (drop_down_state_) {
735 case NORMAL:
736 case HOT:
737 drop_down_image_set = &normal_drop_down_image_set_;
738 break;
739 case PUSHED:
740 drop_down_image_set = &pushed_drop_down_image_set_;
741 break;
742 case DANGEROUS:
743 drop_down_image_set = NULL; // No drop-down in dangerous mode.
744 break;
745 default:
746 NOTREACHED();
747 }
748
749 int center_width = width() - kLeftPadding -
750 body_image_set->left->width() -
751 body_image_set->right->width() -
752 (drop_down_image_set ?
753 normal_drop_down_image_set_.center->width() :
754 0);
755
756 // May be caused by animation.
757 if (center_width <= 0)
758 return;
759
760 // Draw status before button image to effectively lighten text.
761 if (!IsDangerousMode()) {
762 if (show_status_text_) {
763 int mirrored_x = GetMirroredXWithWidthInView(
764 download_util::kSmallProgressIconSize, kTextWidth);
765 // Add font_.height() to compensate for title, which is drawn later.
766 int y = box_y_ + kVerticalPadding + font_.GetHeight() +
767 kVerticalTextPadding;
768 SkColor file_name_color = GetThemeProvider()->GetColor(
769 ThemeService::COLOR_BOOKMARK_TEXT);
770 // If text is light-on-dark, lightening it alone will do nothing.
771 // Therefore we mute luminance a wee bit before drawing in this case.
772 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
773 file_name_color = SkColorSetRGB(
774 static_cast<int>(kDownloadItemLuminanceMod *
775 SkColorGetR(file_name_color)),
776 static_cast<int>(kDownloadItemLuminanceMod *
777 SkColorGetG(file_name_color)),
778 static_cast<int>(kDownloadItemLuminanceMod *
779 SkColorGetB(file_name_color)));
780 canvas->DrawStringInt(WideToUTF16Hack(status_text_), font_,
781 file_name_color, mirrored_x, y, kTextWidth,
782 font_.GetHeight());
783 }
784 }
785
786 // Paint the background images.
787 int x = kLeftPadding;
788 canvas->Save();
789 if (base::i18n::IsRTL()) {
790 // Since we do not have the mirrored images for
791 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
792 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
793 // for RTL UI, we flip the canvas to draw those images mirrored.
794 // Consequently, we do not need to mirror the x-axis of those images.
795 canvas->TranslateInt(width(), 0);
796 canvas->ScaleInt(-1, 1);
797 }
798 PaintBitmaps(canvas,
799 body_image_set->top_left, body_image_set->left,
800 body_image_set->bottom_left,
801 x, box_y_, box_height_, body_image_set->top_left->width());
802 x += body_image_set->top_left->width();
803 PaintBitmaps(canvas,
804 body_image_set->top, body_image_set->center,
805 body_image_set->bottom,
806 x, box_y_, box_height_, center_width);
807 x += center_width;
808 PaintBitmaps(canvas,
809 body_image_set->top_right, body_image_set->right,
810 body_image_set->bottom_right,
811 x, box_y_, box_height_, body_image_set->top_right->width());
812
813 // Overlay our body hot state.
814 if (body_hover_animation_->GetCurrentValue() > 0) {
815 canvas->SaveLayerAlpha(
816 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
817 canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode);
818
819 int x = kLeftPadding;
820 PaintBitmaps(canvas,
821 hot_body_image_set_.top_left, hot_body_image_set_.left,
822 hot_body_image_set_.bottom_left,
823 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
824 x += body_image_set->top_left->width();
825 PaintBitmaps(canvas,
826 hot_body_image_set_.top, hot_body_image_set_.center,
827 hot_body_image_set_.bottom,
828 x, box_y_, box_height_, center_width);
829 x += center_width;
830 PaintBitmaps(canvas,
831 hot_body_image_set_.top_right, hot_body_image_set_.right,
832 hot_body_image_set_.bottom_right,
833 x, box_y_, box_height_,
834 hot_body_image_set_.top_right->width());
835 canvas->Restore();
836 }
837
838 x += body_image_set->top_right->width();
839
840 // Paint the drop-down.
841 if (drop_down_image_set) {
842 PaintBitmaps(canvas,
843 drop_down_image_set->top, drop_down_image_set->center,
844 drop_down_image_set->bottom,
845 x, box_y_, box_height_, drop_down_image_set->top->width());
846
847 // Overlay our drop-down hot state.
848 if (drop_hover_animation_->GetCurrentValue() > 0) {
849 canvas->SaveLayerAlpha(
850 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
851 canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255,
852 SkXfermode::kClear_Mode);
853
854 PaintBitmaps(canvas,
855 drop_down_image_set->top, drop_down_image_set->center,
856 drop_down_image_set->bottom,
857 x, box_y_, box_height_, drop_down_image_set->top->width());
858
859 canvas->Restore();
860 }
861 }
862
863 // Restore the canvas to avoid file name etc. text are drawn flipped.
864 // Consequently, the x-axis of following canvas->DrawXXX() method should be
865 // mirrored so the text and images are down in the right positions.
866 canvas->Restore();
867
868 // Print the text, left aligned and always print the file extension.
869 // Last value of x was the end of the right image, just before the button.
870 // Note that in dangerous mode we use a label (as the text is multi-line).
871 if (!IsDangerousMode()) {
872 string16 filename;
873 if (!disabled_while_opening_) {
874 filename = ui::ElideFilename(download_->GetFileNameToReportUser(),
875 font_, kTextWidth);
876 } else {
877 // First, Calculate the download status opening string width.
878 string16 status_string =
879 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, string16());
880 int status_string_width = font_.GetStringWidth(status_string);
881 // Then, elide the file name.
882 string16 filename_string =
883 ui::ElideFilename(download_->GetFileNameToReportUser(), font_,
884 kTextWidth - status_string_width);
885 // Last, concat the whole string.
886 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
887 filename_string);
888 }
889
890 int mirrored_x = GetMirroredXWithWidthInView(
891 download_util::kSmallProgressIconSize, kTextWidth);
892 SkColor file_name_color = GetThemeProvider()->GetColor(
893 ThemeService::COLOR_BOOKMARK_TEXT);
894 int y =
895 box_y_ + (show_status_text_ ? kVerticalPadding :
896 (box_height_ - font_.GetHeight()) / 2);
897
898 // Draw the file's name.
899 canvas->DrawStringInt(filename, font_,
900 IsEnabled() ? file_name_color :
901 kFileNameDisabledColor,
902 mirrored_x, y, kTextWidth, font_.GetHeight());
903 }
904
905 // Load the icon.
906 IconManager* im = g_browser_process->icon_manager();
907 gfx::Image* image = im->LookupIcon(download_->GetUserVerifiedFilePath(),
908 IconLoader::SMALL);
909 const SkBitmap* icon = NULL;
910 if (IsDangerousMode())
911 icon = warning_icon_;
912 else if (image)
913 icon = *image;
914
915 // We count on the fact that the icon manager will cache the icons and if one
916 // is available, it will be cached here. We *don't* want to request the icon
917 // to be loaded here, since this will also get called if the icon can't be
918 // loaded, in which case LookupIcon will always be NULL. The loading will be
919 // triggered only when we think the status might change.
920 if (icon) {
921 if (!IsDangerousMode()) {
922 if (download_->IsInProgress()) {
923 download_util::PaintDownloadProgress(canvas, this, 0, 0,
924 progress_angle_,
925 download_->PercentComplete(),
926 download_util::SMALL);
927 } else if (download_->IsComplete() &&
928 complete_animation_.get() &&
929 complete_animation_->is_animating()) {
930 if (download_->IsInterrupted()) {
931 download_util::PaintDownloadInterrupted(canvas, this, 0, 0,
932 complete_animation_->GetCurrentValue(),
933 download_util::SMALL);
934 } else {
935 download_util::PaintDownloadComplete(canvas, this, 0, 0,
936 complete_animation_->GetCurrentValue(),
937 download_util::SMALL);
938 }
939 }
940 }
941
942 // Draw the icon image.
943 int mirrored_x = GetMirroredXWithWidthInView(
944 download_util::kSmallProgressIconOffset, icon->width());
945 if (IsEnabled()) {
946 canvas->DrawBitmapInt(*icon, mirrored_x,
947 download_util::kSmallProgressIconOffset);
948 } else {
949 // Use an alpha to make the image look disabled.
950 SkPaint paint;
951 paint.setAlpha(120);
952 canvas->DrawBitmapInt(*icon, mirrored_x,
953 download_util::kSmallProgressIconOffset, paint);
954 }
955 }
956 }
957
OpenDownload()958 void DownloadItemView::OpenDownload() {
959 // We're interested in how long it takes users to open downloads. If they
960 // open downloads super quickly, we should be concerned about clickjacking.
961 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
962 base::Time::Now() - creation_time_);
963 download_->OpenDownload();
964 UpdateAccessibleName();
965 }
966
LoadIcon()967 void DownloadItemView::LoadIcon() {
968 IconManager* im = g_browser_process->icon_manager();
969 im->LoadIcon(download_->GetUserVerifiedFilePath(),
970 IconLoader::SMALL, &icon_consumer_,
971 NewCallback(this, &DownloadItemView::OnExtractIconComplete));
972 }
973
974 // Load an icon for the file type we're downloading, and animate any in progress
975 // download state.
PaintBitmaps(gfx::Canvas * canvas,const SkBitmap * top_bitmap,const SkBitmap * center_bitmap,const SkBitmap * bottom_bitmap,int x,int y,int height,int width)976 void DownloadItemView::PaintBitmaps(gfx::Canvas* canvas,
977 const SkBitmap* top_bitmap,
978 const SkBitmap* center_bitmap,
979 const SkBitmap* bottom_bitmap,
980 int x, int y, int height, int width) {
981 int middle_height = height - top_bitmap->height() - bottom_bitmap->height();
982 // Draw the top.
983 canvas->DrawBitmapInt(*top_bitmap,
984 0, 0, top_bitmap->width(), top_bitmap->height(),
985 x, y, width, top_bitmap->height(), false);
986 y += top_bitmap->height();
987 // Draw the center.
988 canvas->DrawBitmapInt(*center_bitmap,
989 0, 0, center_bitmap->width(), center_bitmap->height(),
990 x, y, width, middle_height, false);
991 y += middle_height;
992 // Draw the bottom.
993 canvas->DrawBitmapInt(*bottom_bitmap,
994 0, 0, bottom_bitmap->width(), bottom_bitmap->height(),
995 x, y, width, bottom_bitmap->height(), false);
996 }
997
SetState(State body_state,State drop_down_state)998 void DownloadItemView::SetState(State body_state, State drop_down_state) {
999 if (body_state_ == body_state && drop_down_state_ == drop_down_state)
1000 return;
1001
1002 body_state_ = body_state;
1003 drop_down_state_ = drop_down_state;
1004 SchedulePaint();
1005 }
1006
ClearDangerousMode()1007 void DownloadItemView::ClearDangerousMode() {
1008 DCHECK(download_->safety_state() == DownloadItem::DANGEROUS_BUT_VALIDATED &&
1009 body_state_ == DANGEROUS && drop_down_state_ == DANGEROUS);
1010
1011 body_state_ = NORMAL;
1012 drop_down_state_ = NORMAL;
1013
1014 // Remove the views used by the dangerous mode.
1015 RemoveChildView(save_button_);
1016 delete save_button_;
1017 save_button_ = NULL;
1018 RemoveChildView(discard_button_);
1019 delete discard_button_;
1020 discard_button_ = NULL;
1021 RemoveChildView(dangerous_download_label_);
1022 delete dangerous_download_label_;
1023 dangerous_download_label_ = NULL;
1024
1025 // Set the accessible name back to the status and filename instead of the
1026 // download warning.
1027 UpdateAccessibleName();
1028
1029 // We need to load the icon now that the download_ has the real path.
1030 LoadIcon();
1031 tooltip_text_ =
1032 UTF16ToWide(download_->GetFileNameToReportUser().LossyDisplayName());
1033
1034 // Force the shelf to layout again as our size has changed.
1035 parent_->Layout();
1036 parent_->SchedulePaint();
1037 }
1038
GetButtonSize()1039 gfx::Size DownloadItemView::GetButtonSize() {
1040 DCHECK(save_button_ && discard_button_);
1041 gfx::Size size;
1042
1043 // We cache the size when successfully retrieved, not for performance reasons
1044 // but because if this DownloadItemView is being animated while the tab is
1045 // not showing, the native buttons are not parented and their preferred size
1046 // is 0, messing-up the layout.
1047 if (cached_button_size_.width() != 0)
1048 return cached_button_size_;
1049
1050 size = save_button_->GetMinimumSize();
1051 gfx::Size discard_size = discard_button_->GetMinimumSize();
1052
1053 size.SetSize(std::max(size.width(), discard_size.width()),
1054 std::max(size.height(), discard_size.height()));
1055
1056 if (size.width() != 0)
1057 cached_button_size_ = size;
1058
1059 return size;
1060 }
1061
1062 // This method computes the minimum width of the label for displaying its text
1063 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1064 // configuration with minimum width.
SizeLabelToMinWidth()1065 void DownloadItemView::SizeLabelToMinWidth() {
1066 if (dangerous_download_label_sized_)
1067 return;
1068
1069 std::wstring text = dangerous_download_label_->GetText();
1070 TrimWhitespace(text, TRIM_ALL, &text);
1071 DCHECK_EQ(std::wstring::npos, text.find(L"\n"));
1072
1073 // Make the label big so that GetPreferredSize() is not constrained by the
1074 // current width.
1075 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1076
1077 gfx::Size size;
1078 int min_width = -1;
1079 string16 text16 = WideToUTF16(text);
1080 // Using BREAK_WORD can work in most cases, but it can also break
1081 // lines where it should not. Using BREAK_LINE is safer although
1082 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1083 base::BreakIterator iter(&text16, base::BreakIterator::BREAK_LINE);
1084 bool status = iter.Init();
1085 DCHECK(status);
1086
1087 string16 current_text = text16;
1088 string16 prev_text = text16;
1089 while (iter.Advance()) {
1090 size_t pos = iter.pos();
1091 if (pos >= text16.length())
1092 break;
1093 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1094 // return false and inserting a new line after a surrogate pair
1095 // is perfectly ok.
1096 char16 line_end_char = text16[pos - 1];
1097 if (u_isUWhiteSpace(line_end_char))
1098 current_text.replace(pos - 1, 1, 1, char16('\n'));
1099 else
1100 current_text.insert(pos, 1, char16('\n'));
1101 dangerous_download_label_->SetText(UTF16ToWide(current_text));
1102 size = dangerous_download_label_->GetPreferredSize();
1103
1104 if (min_width == -1)
1105 min_width = size.width();
1106
1107 // If the width is growing again, it means we passed the optimal width spot.
1108 if (size.width() > min_width) {
1109 dangerous_download_label_->SetText(UTF16ToWide(prev_text));
1110 break;
1111 } else {
1112 min_width = size.width();
1113 }
1114
1115 // Restore the string.
1116 prev_text = current_text;
1117 current_text = text16;
1118 }
1119
1120 // If we have a line with no line breaking opportunity (which is very
1121 // unlikely), we won't cut it.
1122 if (min_width == -1)
1123 size = dangerous_download_label_->GetPreferredSize();
1124
1125 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1126 dangerous_download_label_sized_ = true;
1127 }
1128
Reenable()1129 void DownloadItemView::Reenable() {
1130 disabled_while_opening_ = false;
1131 SetEnabled(true); // Triggers a repaint.
1132 }
1133
InDropDownButtonXCoordinateRange(int x)1134 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1135 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1136 return true;
1137 return false;
1138 }
1139
UpdateAccessibleName()1140 void DownloadItemView::UpdateAccessibleName() {
1141 string16 new_name;
1142 if (download_->safety_state() == DownloadItem::DANGEROUS) {
1143 new_name = WideToUTF16Hack(dangerous_download_label_->GetText());
1144 } else {
1145 new_name = WideToUTF16Hack(status_text_) + char16(' ') +
1146 download_->GetFileNameToReportUser().LossyDisplayName();
1147 }
1148
1149 // If the name has changed, notify assistive technology that the name
1150 // has changed so they can announce it immediately.
1151 if (new_name != accessible_name_) {
1152 accessible_name_ = new_name;
1153 if (GetWidget()) {
1154 GetWidget()->NotifyAccessibilityEvent(
1155 this, ui::AccessibilityTypes::EVENT_NAME_CHANGED, true);
1156 }
1157 }
1158 }
1159