download_item_view.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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::kSafeBrowsingExtendedReportingEnabled)) { 560 // Show dialog, because the dialog hasn't been shown before. 561 DownloadFeedbackDialogView::Show( 562 shelf_->get_parent()->GetNativeWindow(), 563 shelf_->browser()->profile(), 564 shelf_->GetNavigator(), 565 base::Bind( 566 &DownloadItemView::PossiblySubmitDownloadToFeedbackService, 567 weak_ptr_factory_.GetWeakPtr())); 568 } else { 569 PossiblySubmitDownloadToFeedbackService( 570 shelf_->browser()->profile()->GetPrefs()->GetBoolean( 571 prefs::kSafeBrowsingExtendedReportingEnabled)); 572 } 573 return; 574 } 575 download()->Remove(); 576} 577 578void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) { 579 // We don't care if what animation (body button/drop button/complete), 580 // is calling back, as they all have to go through the same paint call. 581 SchedulePaint(); 582} 583 584void DownloadItemView::OnPaint(gfx::Canvas* canvas) { 585 OnPaintBackground(canvas); 586 if (HasFocus()) 587 canvas->DrawFocusRect(GetLocalBounds()); 588} 589 590// The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE 591// and MALICIOUS_MODE). 592// 593// NORMAL_MODE: We are displaying an in-progress or completed download. 594// .-------------------------------+-. 595// | [icon] Filename |v| 596// | [ ] Status | | 597// `-------------------------------+-' 598// | | \_ Drop down button. Invokes menu. Responds 599// | | to mouse. (NORMAL, HOT or PUSHED). 600// | \_ Icon is overlaid on top of in-progress animation. 601// \_ Both the body and the drop down button respond to mouse hover and can be 602// pushed (NORMAL, HOT or PUSHED). 603// 604// DANGEROUS_MODE: The file could be potentially dangerous. 605// .-------------------------------------------------------. 606// | [ ! ] [This type of file can ] [ Keep ] [ Discard ] | 607// | [ ] [destroy your computer..] [ ] [ ] | 608// `-------------------------------------------------------' 609// | | | | \_ No drop down button. 610// | | | \_ Buttons are views::LabelButtons. 611// | | \_ Text is in a label (dangerous_download_label_) 612// | \_ Warning icon. No progress animation. 613// \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only) 614// 615// MALICIOUS_MODE: The file is known malware. 616// .---------------------------------------------+-. 617// | [ - ] [This file is malicious.] [ Discard ] |v| 618// | [ ] [ ] [ ] | |-. 619// `---------------------------------------------+-' | 620// | | | | Drop down button. Responds to 621// | | | | mouse.(NORMAL, HOT or PUSHED) 622// | | | \_ Button is a views::LabelButton. 623// | | \_ Text is in a label (dangerous_download_label_) 624// | \_ Warning icon. No progress animation. 625// \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only) 626// 627void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) { 628 BodyImageSet* body_image_set = NULL; 629 switch (mode_) { 630 case NORMAL_MODE: 631 if (body_state_ == PUSHED) 632 body_image_set = &pushed_body_image_set_; 633 else // NORMAL or HOT 634 body_image_set = &normal_body_image_set_; 635 break; 636 case DANGEROUS_MODE: 637 body_image_set = &dangerous_mode_body_image_set_; 638 break; 639 case MALICIOUS_MODE: 640 body_image_set = &malicious_mode_body_image_set_; 641 break; 642 default: 643 NOTREACHED(); 644 } 645 646 DropDownImageSet* drop_down_image_set = NULL; 647 switch (mode_) { 648 case NORMAL_MODE: 649 case MALICIOUS_MODE: 650 if (drop_down_state_ == PUSHED) 651 drop_down_image_set = &pushed_drop_down_image_set_; 652 else // NORMAL or HOT 653 drop_down_image_set = &normal_drop_down_image_set_; 654 break; 655 case DANGEROUS_MODE: 656 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let 657 // drop_down_image_set == NULL. 658 break; 659 default: 660 NOTREACHED(); 661 } 662 663 int center_width = width() - kLeftPadding - 664 body_image_set->left->width() - 665 body_image_set->right->width() - 666 (drop_down_image_set ? 667 normal_drop_down_image_set_.center->width() : 668 0); 669 670 // May be caused by animation. 671 if (center_width <= 0) 672 return; 673 674 // Draw status before button image to effectively lighten text. No status for 675 // warning dialogs. 676 if (!IsShowingWarningDialog()) { 677 if (!status_text_.empty()) { 678 int mirrored_x = GetMirroredXWithWidthInView( 679 DownloadShelf::kSmallProgressIconSize, kTextWidth); 680 // Add font_list_.height() to compensate for title, which is drawn later. 681 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() + 682 kVerticalTextPadding; 683 SkColor file_name_color = GetThemeProvider()->GetColor( 684 ThemeProperties::COLOR_BOOKMARK_TEXT); 685 // If text is light-on-dark, lightening it alone will do nothing. 686 // Therefore we mute luminance a wee bit before drawing in this case. 687 if (color_utils::RelativeLuminance(file_name_color) > 0.5) 688 file_name_color = SkColorSetRGB( 689 static_cast<int>(kDownloadItemLuminanceMod * 690 SkColorGetR(file_name_color)), 691 static_cast<int>(kDownloadItemLuminanceMod * 692 SkColorGetG(file_name_color)), 693 static_cast<int>(kDownloadItemLuminanceMod * 694 SkColorGetB(file_name_color))); 695 canvas->DrawStringRect(status_text_, font_list_, file_name_color, 696 gfx::Rect(mirrored_x, y, kTextWidth, 697 font_list_.GetHeight())); 698 } 699 } 700 701 // Paint the background images. 702 int x = kLeftPadding; 703 canvas->Save(); 704 if (base::i18n::IsRTL()) { 705 // Since we do not have the mirrored images for 706 // (hot_)body_image_set->top_left, (hot_)body_image_set->left, 707 // (hot_)body_image_set->bottom_left, and drop_down_image_set, 708 // for RTL UI, we flip the canvas to draw those images mirrored. 709 // Consequently, we do not need to mirror the x-axis of those images. 710 canvas->Translate(gfx::Vector2d(width(), 0)); 711 canvas->Scale(-1, 1); 712 } 713 PaintImages(canvas, 714 body_image_set->top_left, body_image_set->left, 715 body_image_set->bottom_left, 716 x, box_y_, box_height_, body_image_set->top_left->width()); 717 x += body_image_set->top_left->width(); 718 PaintImages(canvas, 719 body_image_set->top, body_image_set->center, 720 body_image_set->bottom, 721 x, box_y_, box_height_, center_width); 722 x += center_width; 723 PaintImages(canvas, 724 body_image_set->top_right, body_image_set->right, 725 body_image_set->bottom_right, 726 x, box_y_, box_height_, body_image_set->top_right->width()); 727 728 // Overlay our body hot state. Warning dialogs don't display body a hot state. 729 if (!IsShowingWarningDialog() && 730 body_hover_animation_->GetCurrentValue() > 0) { 731 canvas->SaveLayerAlpha( 732 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255)); 733 734 int x = kLeftPadding; 735 PaintImages(canvas, 736 hot_body_image_set_.top_left, hot_body_image_set_.left, 737 hot_body_image_set_.bottom_left, 738 x, box_y_, box_height_, hot_body_image_set_.top_left->width()); 739 x += body_image_set->top_left->width(); 740 PaintImages(canvas, 741 hot_body_image_set_.top, hot_body_image_set_.center, 742 hot_body_image_set_.bottom, 743 x, box_y_, box_height_, center_width); 744 x += center_width; 745 PaintImages(canvas, 746 hot_body_image_set_.top_right, hot_body_image_set_.right, 747 hot_body_image_set_.bottom_right, 748 x, box_y_, box_height_, 749 hot_body_image_set_.top_right->width()); 750 canvas->Restore(); 751 } 752 753 x += body_image_set->top_right->width(); 754 755 // Paint the drop-down. 756 if (drop_down_image_set) { 757 PaintImages(canvas, 758 drop_down_image_set->top, drop_down_image_set->center, 759 drop_down_image_set->bottom, 760 x, box_y_, box_height_, drop_down_image_set->top->width()); 761 762 // Overlay our drop-down hot state. 763 if (drop_hover_animation_->GetCurrentValue() > 0) { 764 canvas->SaveLayerAlpha( 765 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255)); 766 767 PaintImages(canvas, 768 drop_down_image_set->top, drop_down_image_set->center, 769 drop_down_image_set->bottom, 770 x, box_y_, box_height_, drop_down_image_set->top->width()); 771 772 canvas->Restore(); 773 } 774 } 775 776 // Restore the canvas to avoid file name etc. text are drawn flipped. 777 // Consequently, the x-axis of following canvas->DrawXXX() method should be 778 // mirrored so the text and images are down in the right positions. 779 canvas->Restore(); 780 781 // Print the text, left aligned and always print the file extension. 782 // Last value of x was the end of the right image, just before the button. 783 // Note that in dangerous mode we use a label (as the text is multi-line). 784 if (!IsShowingWarningDialog()) { 785 base::string16 filename; 786 if (!disabled_while_opening_) { 787 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(), 788 font_list_, kTextWidth); 789 } else { 790 // First, Calculate the download status opening string width. 791 base::string16 status_string = 792 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, 793 base::string16()); 794 int status_string_width = gfx::GetStringWidth(status_string, font_list_); 795 // Then, elide the file name. 796 base::string16 filename_string = 797 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_, 798 kTextWidth - status_string_width); 799 // Last, concat the whole string. 800 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, 801 filename_string); 802 } 803 804 int mirrored_x = GetMirroredXWithWidthInView( 805 DownloadShelf::kSmallProgressIconSize, kTextWidth); 806 SkColor file_name_color = GetThemeProvider()->GetColor( 807 ThemeProperties::COLOR_BOOKMARK_TEXT); 808 int y = 809 box_y_ + (status_text_.empty() ? 810 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding); 811 812 // Draw the file's name. 813 canvas->DrawStringRect( 814 filename, font_list_, 815 enabled() ? file_name_color : kFileNameDisabledColor, 816 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight())); 817 } 818 819 // Load the icon. 820 IconManager* im = g_browser_process->icon_manager(); 821 gfx::Image* image = im->LookupIconFromFilepath( 822 download()->GetTargetFilePath(), IconLoader::SMALL); 823 const gfx::ImageSkia* icon = NULL; 824 if (IsShowingWarningDialog()) 825 icon = warning_icon_; 826 else if (image) 827 icon = image->ToImageSkia(); 828 829 // We count on the fact that the icon manager will cache the icons and if one 830 // is available, it will be cached here. We *don't* want to request the icon 831 // to be loaded here, since this will also get called if the icon can't be 832 // loaded, in which case LookupIcon will always be NULL. The loading will be 833 // triggered only when we think the status might change. 834 if (icon) { 835 if (!IsShowingWarningDialog()) { 836 DownloadItem::DownloadState state = download()->GetState(); 837 if (state == DownloadItem::IN_PROGRESS) { 838 DownloadShelf::PaintDownloadProgress(canvas, 839 this, 840 0, 841 0, 842 progress_angle_, 843 model_.PercentComplete(), 844 DownloadShelf::SMALL); 845 } else if (complete_animation_.get() && 846 complete_animation_->is_animating()) { 847 if (state == DownloadItem::INTERRUPTED) { 848 DownloadShelf::PaintDownloadInterrupted( 849 canvas, 850 this, 851 0, 852 0, 853 complete_animation_->GetCurrentValue(), 854 DownloadShelf::SMALL); 855 } else { 856 DCHECK_EQ(DownloadItem::COMPLETE, state); 857 DownloadShelf::PaintDownloadComplete( 858 canvas, 859 this, 860 0, 861 0, 862 complete_animation_->GetCurrentValue(), 863 DownloadShelf::SMALL); 864 } 865 } 866 } 867 868 // Draw the icon image. 869 int icon_x, icon_y; 870 871 if (IsShowingWarningDialog()) { 872 icon_x = kLeftPadding + body_image_set->top_left->width(); 873 icon_y = (height() - icon->height()) / 2; 874 } else { 875 icon_x = DownloadShelf::kSmallProgressIconOffset; 876 icon_y = DownloadShelf::kSmallProgressIconOffset; 877 } 878 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width()); 879 if (enabled()) { 880 canvas->DrawImageInt(*icon, icon_x, icon_y); 881 } else { 882 // Use an alpha to make the image look disabled. 883 SkPaint paint; 884 paint.setAlpha(120); 885 canvas->DrawImageInt(*icon, icon_x, icon_y, paint); 886 } 887 } 888} 889 890void DownloadItemView::OnFocus() { 891 View::OnFocus(); 892 // We render differently when focused. 893 SchedulePaint(); 894} 895 896void DownloadItemView::OnBlur() { 897 View::OnBlur(); 898 // We render differently when focused. 899 SchedulePaint(); 900} 901 902void DownloadItemView::OpenDownload() { 903 DCHECK(!IsShowingWarningDialog()); 904 // We're interested in how long it takes users to open downloads. If they 905 // open downloads super quickly, we should be concerned about clickjacking. 906 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download", 907 base::Time::Now() - creation_time_); 908 909 UpdateAccessibleName(); 910 911 // Calling download()->OpenDownload may delete this, so this must be 912 // the last thing we do. 913 download()->OpenDownload(); 914} 915 916bool DownloadItemView::SubmitDownloadToFeedbackService() { 917#if defined(FULL_SAFE_BROWSING) 918 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service(); 919 if (!sb_service) 920 return false; 921 safe_browsing::DownloadProtectionService* download_protection_service = 922 sb_service->download_protection_service(); 923 if (!download_protection_service) 924 return false; 925 download_protection_service->feedback_service()->BeginFeedbackForDownload( 926 download()); 927 // WARNING: we are deleted at this point. Don't access 'this'. 928 return true; 929#else 930 NOTREACHED(); 931 return false; 932#endif 933} 934 935void DownloadItemView::PossiblySubmitDownloadToFeedbackService(bool enabled) { 936 if (!enabled || !SubmitDownloadToFeedbackService()) 937 download()->Remove(); 938 // WARNING: 'this' is deleted at this point. Don't access 'this'. 939} 940 941void DownloadItemView::LoadIcon() { 942 IconManager* im = g_browser_process->icon_manager(); 943 last_download_item_path_ = download()->GetTargetFilePath(); 944 im->LoadIcon(last_download_item_path_, 945 IconLoader::SMALL, 946 base::Bind(&DownloadItemView::OnExtractIconComplete, 947 base::Unretained(this)), 948 &cancelable_task_tracker_); 949} 950 951void DownloadItemView::LoadIconIfItemPathChanged() { 952 base::FilePath current_download_path = download()->GetTargetFilePath(); 953 if (last_download_item_path_ == current_download_path) 954 return; 955 956 LoadIcon(); 957} 958 959void DownloadItemView::UpdateColorsFromTheme() { 960 if (dangerous_download_label_ && GetThemeProvider()) { 961 dangerous_download_label_->SetEnabledColor( 962 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT)); 963 } 964} 965 966void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p, 967 ui::MenuSourceType source_type) { 968 gfx::Point point = p; 969 gfx::Size size; 970 971 // Similar hack as in MenuButton. 972 // We're about to show the menu from a mouse press. By showing from the 973 // mouse press event we block RootView in mouse dispatching. This also 974 // appears to cause RootView to get a mouse pressed BEFORE the mouse 975 // release is seen, which means RootView sends us another mouse press no 976 // matter where the user pressed. To force RootView to recalculate the 977 // mouse target during the mouse press we explicitly set the mouse handler 978 // to NULL. 979 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())-> 980 SetMouseHandler(NULL); 981 982 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned 983 // to drop down arrow button. 984 if (source_type != ui::MENU_SOURCE_MOUSE && 985 source_type != ui::MENU_SOURCE_TOUCH) { 986 drop_down_pressed_ = true; 987 SetState(NORMAL, PUSHED); 988 point.SetPoint(drop_down_x_left_, box_y_); 989 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_); 990 } 991 // Post a task to release the button. When we call the Run method on the menu 992 // below, it runs an inner message loop that might cause us to be deleted. 993 // Posting a task with a WeakPtr lets us safely handle the button release. 994 base::MessageLoop::current()->PostNonNestableTask( 995 FROM_HERE, 996 base::Bind(&DownloadItemView::ReleaseDropDown, 997 weak_ptr_factory_.GetWeakPtr())); 998 views::View::ConvertPointToScreen(this, &point); 999 1000 if (!context_menu_.get()) { 1001 context_menu_.reset( 1002 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator())); 1003 } 1004 context_menu_->Run(GetWidget()->GetTopLevelWidget(), 1005 gfx::Rect(point, size), source_type); 1006 // We could be deleted now. 1007} 1008 1009void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event, 1010 bool active_event) { 1011 // The event should not activate us in dangerous mode. 1012 if (mode_ == DANGEROUS_MODE) 1013 return; 1014 1015 // Stop any completion animation. 1016 if (complete_animation_.get() && complete_animation_->is_animating()) 1017 complete_animation_->End(); 1018 1019 if (active_event) { 1020 if (InDropDownButtonXCoordinateRange(event.x())) { 1021 if (context_menu_.get()) { 1022 // Ignore two close clicks. This typically happens when the user clicks 1023 // the button to close the menu. 1024 base::TimeDelta delta = 1025 base::TimeTicks::Now() - context_menu_->close_time(); 1026 if (delta.InMilliseconds() < views::kMinimumMsBetweenButtonClicks) 1027 return; 1028 } 1029 drop_down_pressed_ = true; 1030 SetState(NORMAL, PUSHED); 1031 // We are setting is_mouse_gesture to false when calling ShowContextMenu 1032 // so that the positioning of the context menu will be similar to a 1033 // keyboard invocation. I.e. we want the menu to always be positioned 1034 // next to the drop down button instead of the next to the pointer. 1035 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD); 1036 // Once called, it is possible that *this was deleted (e.g.: due to 1037 // invoking the 'Discard' action.) 1038 } else if (!IsShowingWarningDialog()) { 1039 SetState(PUSHED, NORMAL); 1040 } 1041 } 1042} 1043 1044void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event, 1045 bool active_event) { 1046 // Mouse should not activate us in dangerous mode. 1047 if (mode_ == DANGEROUS_MODE) 1048 return; 1049 1050 SetState(NORMAL, NORMAL); 1051 1052 if (!active_event || 1053 InDropDownButtonXCoordinateRange(event.x()) || 1054 IsShowingWarningDialog()) { 1055 return; 1056 } 1057 1058 // OpenDownload may delete this, so don't add any code after this line. 1059 OpenDownload(); 1060} 1061 1062// Load an icon for the file type we're downloading, and animate any in progress 1063// download state. 1064void DownloadItemView::PaintImages(gfx::Canvas* canvas, 1065 const gfx::ImageSkia* top_image, 1066 const gfx::ImageSkia* center_image, 1067 const gfx::ImageSkia* bottom_image, 1068 int x, int y, int height, int width) { 1069 int middle_height = height - top_image->height() - bottom_image->height(); 1070 // Draw the top. 1071 canvas->DrawImageInt(*top_image, 1072 0, 0, top_image->width(), top_image->height(), 1073 x, y, width, top_image->height(), false); 1074 y += top_image->height(); 1075 // Draw the center. 1076 canvas->DrawImageInt(*center_image, 1077 0, 0, center_image->width(), center_image->height(), 1078 x, y, width, middle_height, false); 1079 y += middle_height; 1080 // Draw the bottom. 1081 canvas->DrawImageInt(*bottom_image, 1082 0, 0, bottom_image->width(), bottom_image->height(), 1083 x, y, width, bottom_image->height(), false); 1084} 1085 1086void DownloadItemView::SetState(State new_body_state, State new_drop_state) { 1087 // If we are showing a warning dialog, we don't change body state. 1088 if (IsShowingWarningDialog()) { 1089 new_body_state = NORMAL; 1090 1091 // Current body_state_ should always be NORMAL for warning dialogs. 1092 DCHECK_EQ(NORMAL, body_state_); 1093 // We shouldn't be calling SetState if we are in DANGEROUS_MODE. 1094 DCHECK_NE(DANGEROUS_MODE, mode_); 1095 } 1096 // Avoid extra SchedulePaint()s if the state is going to be the same. 1097 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state) 1098 return; 1099 1100 AnimateStateTransition(body_state_, new_body_state, 1101 body_hover_animation_.get()); 1102 AnimateStateTransition(drop_down_state_, new_drop_state, 1103 drop_hover_animation_.get()); 1104 body_state_ = new_body_state; 1105 drop_down_state_ = new_drop_state; 1106 SchedulePaint(); 1107} 1108 1109void DownloadItemView::ClearWarningDialog() { 1110 DCHECK(download()->GetDangerType() == 1111 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED); 1112 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE); 1113 1114 mode_ = NORMAL_MODE; 1115 body_state_ = NORMAL; 1116 drop_down_state_ = NORMAL; 1117 1118 // Remove the views used by the warning dialog. 1119 if (save_button_) { 1120 RemoveChildView(save_button_); 1121 delete save_button_; 1122 save_button_ = NULL; 1123 } 1124 RemoveChildView(discard_button_); 1125 delete discard_button_; 1126 discard_button_ = NULL; 1127 RemoveChildView(dangerous_download_label_); 1128 delete dangerous_download_label_; 1129 dangerous_download_label_ = NULL; 1130 dangerous_download_label_sized_ = false; 1131 cached_button_size_.SetSize(0,0); 1132 1133 // Set the accessible name back to the status and filename instead of the 1134 // download warning. 1135 UpdateAccessibleName(); 1136 UpdateDropDownButtonPosition(); 1137 1138 // We need to load the icon now that the download has the real path. 1139 LoadIcon(); 1140 1141 // Force the shelf to layout again as our size has changed. 1142 shelf_->Layout(); 1143 shelf_->SchedulePaint(); 1144 1145 TooltipTextChanged(); 1146} 1147 1148void DownloadItemView::ShowWarningDialog() { 1149 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE); 1150 time_download_warning_shown_ = base::Time::Now(); 1151 content::DownloadDangerType danger_type = download()->GetDangerType(); 1152 RecordDangerousDownloadWarningShown(danger_type); 1153#if defined(FULL_SAFE_BROWSING) 1154 if (model_.ShouldAllowDownloadFeedback()) { 1155 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown( 1156 danger_type); 1157 } 1158#endif 1159 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE; 1160 1161 body_state_ = NORMAL; 1162 drop_down_state_ = NORMAL; 1163 if (mode_ == DANGEROUS_MODE) { 1164 save_button_ = new views::LabelButton( 1165 this, model_.GetWarningConfirmButtonText()); 1166 save_button_->SetStyle(views::Button::STYLE_BUTTON); 1167 AddChildView(save_button_); 1168 } 1169 int discard_button_message = model_.IsMalicious() ? 1170 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD; 1171 discard_button_ = new views::LabelButton( 1172 this, l10n_util::GetStringUTF16(discard_button_message)); 1173 discard_button_->SetStyle(views::Button::STYLE_BUTTON); 1174 AddChildView(discard_button_); 1175 1176 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1177 switch (danger_type) { 1178 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: 1179 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: 1180 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: 1181 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: 1182 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: 1183 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING); 1184 break; 1185 1186 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: 1187 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: 1188 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: 1189 case content::DOWNLOAD_DANGER_TYPE_MAX: 1190 NOTREACHED(); 1191 // fallthrough 1192 1193 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: 1194 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING); 1195 } 1196 base::string16 dangerous_label = 1197 model_.GetWarningText(font_list_, kTextWidth); 1198 dangerous_download_label_ = new views::Label(dangerous_label); 1199 dangerous_download_label_->SetMultiLine(true); 1200 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1201 dangerous_download_label_->SetAutoColorReadabilityEnabled(false); 1202 AddChildView(dangerous_download_label_); 1203 SizeLabelToMinWidth(); 1204 UpdateDropDownButtonPosition(); 1205 TooltipTextChanged(); 1206} 1207 1208gfx::Size DownloadItemView::GetButtonSize() const { 1209 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_)); 1210 gfx::Size size; 1211 1212 // We cache the size when successfully retrieved, not for performance reasons 1213 // but because if this DownloadItemView is being animated while the tab is 1214 // not showing, the native buttons are not parented and their preferred size 1215 // is 0, messing-up the layout. 1216 if (cached_button_size_.width() != 0) 1217 return cached_button_size_; 1218 1219 if (save_button_) 1220 size = save_button_->GetMinimumSize(); 1221 gfx::Size discard_size = discard_button_->GetMinimumSize(); 1222 1223 size.SetSize(std::max(size.width(), discard_size.width()), 1224 std::max(size.height(), discard_size.height())); 1225 1226 if (size.width() != 0) 1227 cached_button_size_ = size; 1228 1229 return size; 1230} 1231 1232// This method computes the minimum width of the label for displaying its text 1233// on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the 1234// configuration with minimum width. 1235void DownloadItemView::SizeLabelToMinWidth() { 1236 if (dangerous_download_label_sized_) 1237 return; 1238 1239 base::string16 label_text = dangerous_download_label_->text(); 1240 base::TrimWhitespace(label_text, base::TRIM_ALL, &label_text); 1241 DCHECK_EQ(base::string16::npos, label_text.find('\n')); 1242 1243 // Make the label big so that GetPreferredSize() is not constrained by the 1244 // current width. 1245 dangerous_download_label_->SetBounds(0, 0, 1000, 1000); 1246 1247 // Use a const string from here. BreakIterator requies that text.data() not 1248 // change during its lifetime. 1249 const base::string16 original_text(label_text); 1250 // Using BREAK_WORD can work in most cases, but it can also break 1251 // lines where it should not. Using BREAK_LINE is safer although 1252 // slower for Chinese/Japanese. This is not perf-critical at all, though. 1253 base::i18n::BreakIterator iter(original_text, 1254 base::i18n::BreakIterator::BREAK_LINE); 1255 bool status = iter.Init(); 1256 DCHECK(status); 1257 1258 base::string16 prev_text = original_text; 1259 gfx::Size size = dangerous_download_label_->GetPreferredSize(); 1260 int min_width = size.width(); 1261 1262 // Go through the string and try each line break (starting with no line break) 1263 // searching for the optimal line break position. Stop if we find one that 1264 // yields one that is less than kDangerousTextWidth wide. This is to prevent 1265 // a short string (e.g.: "This file is malicious") from being broken up 1266 // unnecessarily. 1267 while (iter.Advance() && min_width > kDangerousTextWidth) { 1268 size_t pos = iter.pos(); 1269 if (pos >= original_text.length()) 1270 break; 1271 base::string16 current_text = original_text; 1272 // This can be a low surrogate codepoint, but u_isUWhiteSpace will 1273 // return false and inserting a new line after a surrogate pair 1274 // is perfectly ok. 1275 base::char16 line_end_char = current_text[pos - 1]; 1276 if (u_isUWhiteSpace(line_end_char)) 1277 current_text.replace(pos - 1, 1, 1, base::char16('\n')); 1278 else 1279 current_text.insert(pos, 1, base::char16('\n')); 1280 dangerous_download_label_->SetText(current_text); 1281 size = dangerous_download_label_->GetPreferredSize(); 1282 1283 // If the width is growing again, it means we passed the optimal width spot. 1284 if (size.width() > min_width) { 1285 dangerous_download_label_->SetText(prev_text); 1286 break; 1287 } else { 1288 min_width = size.width(); 1289 } 1290 prev_text = current_text; 1291 } 1292 1293 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height()); 1294 dangerous_download_label_sized_ = true; 1295} 1296 1297void DownloadItemView::Reenable() { 1298 disabled_while_opening_ = false; 1299 SetEnabled(true); // Triggers a repaint. 1300} 1301 1302void DownloadItemView::ReleaseDropDown() { 1303 drop_down_pressed_ = false; 1304 SetState(NORMAL, NORMAL); 1305} 1306 1307bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) { 1308 if (x > drop_down_x_left_ && x < drop_down_x_right_) 1309 return true; 1310 return false; 1311} 1312 1313void DownloadItemView::UpdateAccessibleName() { 1314 base::string16 new_name; 1315 if (IsShowingWarningDialog()) { 1316 new_name = dangerous_download_label_->text(); 1317 } else { 1318 new_name = status_text_ + base::char16(' ') + 1319 download()->GetFileNameToReportUser().LossyDisplayName(); 1320 } 1321 1322 // If the name has changed, notify assistive technology that the name 1323 // has changed so they can announce it immediately. 1324 if (new_name != accessible_name_) { 1325 accessible_name_ = new_name; 1326 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true); 1327 } 1328} 1329 1330void DownloadItemView::UpdateDropDownButtonPosition() { 1331 gfx::Size size = GetPreferredSize(); 1332 if (base::i18n::IsRTL()) { 1333 // Drop down button is glued to the left of the download shelf. 1334 drop_down_x_left_ = 0; 1335 drop_down_x_right_ = normal_drop_down_image_set_.top->width(); 1336 } else { 1337 // Drop down button is glued to the right of the download shelf. 1338 drop_down_x_left_ = 1339 size.width() - normal_drop_down_image_set_.top->width(); 1340 drop_down_x_right_ = size.width(); 1341 } 1342} 1343 1344void DownloadItemView::AnimateStateTransition(State from, State to, 1345 gfx::SlideAnimation* animation) { 1346 if (from == NORMAL && to == HOT) { 1347 animation->Show(); 1348 } else if (from == HOT && to == NORMAL) { 1349 animation->Hide(); 1350 } else if (from != to) { 1351 animation->Reset((to == HOT) ? 1.0 : 0.0); 1352 } 1353} 1354