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