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