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