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