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