| OLD | NEW |
| (Empty) |
| 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_md.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/bind.h" | |
| 13 #include "base/callback.h" | |
| 14 #include "base/files/file_path.h" | |
| 15 #include "base/i18n/break_iterator.h" | |
| 16 #include "base/i18n/rtl.h" | |
| 17 #include "base/location.h" | |
| 18 #include "base/macros.h" | |
| 19 #include "base/memory/ptr_util.h" | |
| 20 #include "base/metrics/histogram_macros.h" | |
| 21 #include "base/single_thread_task_runner.h" | |
| 22 #include "base/strings/string_util.h" | |
| 23 #include "base/strings/stringprintf.h" | |
| 24 #include "base/strings/sys_string_conversions.h" | |
| 25 #include "base/strings/utf_string_conversions.h" | |
| 26 #include "base/threading/thread_task_runner_handle.h" | |
| 27 #include "chrome/browser/browser_process.h" | |
| 28 #include "chrome/browser/download/chrome_download_manager_delegate.h" | |
| 29 #include "chrome/browser/download/download_item_model.h" | |
| 30 #include "chrome/browser/download/download_stats.h" | |
| 31 #include "chrome/browser/download/drag_download_item.h" | |
| 32 #include "chrome/browser/extensions/api/experience_sampling_private/experience_s
ampling.h" | |
| 33 #include "chrome/browser/profiles/profile.h" | |
| 34 #include "chrome/browser/safe_browsing/download_feedback_service.h" | |
| 35 #include "chrome/browser/safe_browsing/download_protection_service.h" | |
| 36 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | |
| 37 #include "chrome/browser/themes/theme_properties.h" | |
| 38 #include "chrome/browser/ui/views/download/download_feedback_dialog_view.h" | |
| 39 #include "chrome/browser/ui/views/download/download_shelf_context_menu_view.h" | |
| 40 #include "chrome/browser/ui/views/download/download_shelf_view.h" | |
| 41 #include "chrome/browser/ui/views/frame/browser_view.h" | |
| 42 #include "chrome/common/pref_names.h" | |
| 43 #include "chrome/grit/generated_resources.h" | |
| 44 #include "components/prefs/pref_service.h" | |
| 45 #include "content/public/browser/download_danger_type.h" | |
| 46 #include "third_party/icu/source/common/unicode/uchar.h" | |
| 47 #include "ui/accessibility/ax_view_state.h" | |
| 48 #include "ui/base/l10n/l10n_util.h" | |
| 49 #include "ui/base/material_design/material_design_controller.h" | |
| 50 #include "ui/base/resource/resource_bundle.h" | |
| 51 #include "ui/base/theme_provider.h" | |
| 52 #include "ui/events/event.h" | |
| 53 #include "ui/gfx/animation/slide_animation.h" | |
| 54 #include "ui/gfx/canvas.h" | |
| 55 #include "ui/gfx/color_palette.h" | |
| 56 #include "ui/gfx/color_utils.h" | |
| 57 #include "ui/gfx/image/image.h" | |
| 58 #include "ui/gfx/paint_vector_icon.h" | |
| 59 #include "ui/gfx/text_elider.h" | |
| 60 #include "ui/gfx/text_utils.h" | |
| 61 #include "ui/gfx/vector_icons_public.h" | |
| 62 #include "ui/views/animation/flood_fill_ink_drop_ripple.h" | |
| 63 #include "ui/views/animation/ink_drop_highlight.h" | |
| 64 #include "ui/views/border.h" | |
| 65 #include "ui/views/controls/button/image_button.h" | |
| 66 #include "ui/views/controls/button/md_text_button.h" | |
| 67 #include "ui/views/controls/button/vector_icon_button.h" | |
| 68 #include "ui/views/controls/focusable_border.h" | |
| 69 #include "ui/views/controls/label.h" | |
| 70 #include "ui/views/mouse_constants.h" | |
| 71 #include "ui/views/widget/root_view.h" | |
| 72 #include "ui/views/widget/widget.h" | |
| 73 | |
| 74 using content::DownloadItem; | |
| 75 using extensions::ExperienceSamplingEvent; | |
| 76 | |
| 77 namespace { | |
| 78 | |
| 79 // All values in dp. | |
| 80 const int kTextWidth = 140; | |
| 81 const int kDangerousTextWidth = 200; | |
| 82 | |
| 83 // The normal height of the item which may be exceeded if text is large. | |
| 84 const int kDefaultHeight = 48; | |
| 85 | |
| 86 // The vertical distance between the item's visual upper bound (as delineated by | |
| 87 // the separator on the right) and the edge of the shelf. | |
| 88 const int kTopBottomPadding = 6; | |
| 89 | |
| 90 // The minimum vertical padding above and below contents of the download item. | |
| 91 // This is only used when the text size is large. | |
| 92 const int kMinimumVerticalPadding = 2 + kTopBottomPadding; | |
| 93 | |
| 94 // Vertical padding between filename and status text. | |
| 95 const int kVerticalTextPadding = 1; | |
| 96 | |
| 97 const int kTooltipMaxWidth = 800; | |
| 98 | |
| 99 // Padding before the icon and at end of the item. | |
| 100 const int kStartPadding = 12; | |
| 101 const int kEndPadding = 6; | |
| 102 | |
| 103 // Horizontal padding between progress indicator and filename/status text. | |
| 104 const int kProgressTextPadding = 8; | |
| 105 | |
| 106 // The space between the Save and Discard buttons when prompting for a dangerous | |
| 107 // download. | |
| 108 const int kButtonPadding = 5; | |
| 109 | |
| 110 // The touchable space around the dropdown button's icon. | |
| 111 const int kDropdownBorderWidth = 10; | |
| 112 | |
| 113 // The space on the right side of the dangerous download label. | |
| 114 const int kLabelPadding = 8; | |
| 115 | |
| 116 // Height/width of the warning icon, also in dp. | |
| 117 const int kWarningIconSize = 24; | |
| 118 | |
| 119 // How long the 'download complete' animation should last for. | |
| 120 const int kCompleteAnimationDurationMs = 2500; | |
| 121 | |
| 122 // How long the 'download interrupted' animation should last for. | |
| 123 const int kInterruptedAnimationDurationMs = 2500; | |
| 124 | |
| 125 // How long we keep the item disabled after the user clicked it to open the | |
| 126 // downloaded item. | |
| 127 const int kDisabledOnOpenDuration = 3000; | |
| 128 | |
| 129 // The separator is drawn as a border. It's one dp wide. | |
| 130 class SeparatorBorder : public views::FocusableBorder { | |
| 131 public: | |
| 132 explicit SeparatorBorder(SkColor color) : color_(color) {} | |
| 133 ~SeparatorBorder() override {} | |
| 134 | |
| 135 void Paint(const views::View& view, gfx::Canvas* canvas) override { | |
| 136 if (view.HasFocus()) | |
| 137 return FocusableBorder::Paint(view, canvas); | |
| 138 | |
| 139 int end_x = base::i18n::IsRTL() ? 0 : view.width() - 1; | |
| 140 canvas->DrawLine(gfx::Point(end_x, kTopBottomPadding), | |
| 141 gfx::Point(end_x, view.height() - kTopBottomPadding), | |
| 142 color_); | |
| 143 } | |
| 144 | |
| 145 gfx::Insets GetInsets() const override { return gfx::Insets(0, 0, 0, 1); } | |
| 146 | |
| 147 gfx::Size GetMinimumSize() const override { | |
| 148 return gfx::Size(1, 2 * kTopBottomPadding + 1); | |
| 149 } | |
| 150 | |
| 151 private: | |
| 152 SkColor color_; | |
| 153 | |
| 154 DISALLOW_COPY_AND_ASSIGN(SeparatorBorder); | |
| 155 }; | |
| 156 | |
| 157 } // namespace | |
| 158 | |
| 159 // Allows the DownloadItemViewMd to control the InkDrop on the drop down button. | |
| 160 class DownloadItemViewMd::DropDownButton : public views::VectorIconButton { | |
| 161 public: | |
| 162 explicit DropDownButton(views::VectorIconButtonDelegate* delegate) | |
| 163 : views::VectorIconButton(delegate) {} | |
| 164 ~DropDownButton() override {} | |
| 165 | |
| 166 // Promoted visibility to public. | |
| 167 void AnimateInkDrop(views::InkDropState state) { | |
| 168 // TODO(bruthig): Plumb in the proper Event. | |
| 169 views::VectorIconButton::AnimateInkDrop(state, nullptr /* event */); | |
| 170 } | |
| 171 | |
| 172 private: | |
| 173 DISALLOW_COPY_AND_ASSIGN(DropDownButton); | |
| 174 }; | |
| 175 | |
| 176 DownloadItemViewMd::DownloadItemViewMd(DownloadItem* download_item, | |
| 177 DownloadShelfView* parent) | |
| 178 : shelf_(parent), | |
| 179 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)), | |
| 180 dropdown_state_(NORMAL), | |
| 181 mode_(NORMAL_MODE), | |
| 182 dragging_(false), | |
| 183 starting_drag_(false), | |
| 184 model_(download_item), | |
| 185 save_button_(nullptr), | |
| 186 discard_button_(nullptr), | |
| 187 dropdown_button_(new DropDownButton(this)), | |
| 188 dangerous_download_label_(nullptr), | |
| 189 dangerous_download_label_sized_(false), | |
| 190 disabled_while_opening_(false), | |
| 191 creation_time_(base::Time::Now()), | |
| 192 time_download_warning_shown_(base::Time()), | |
| 193 weak_ptr_factory_(this) { | |
| 194 SetInkDropMode(InkDropMode::ON); | |
| 195 DCHECK(download()); | |
| 196 DCHECK(ui::MaterialDesignController::IsModeMaterial()); | |
| 197 download()->AddObserver(this); | |
| 198 set_context_menu_controller(this); | |
| 199 | |
| 200 dropdown_button_->SetBorder( | |
| 201 views::Border::CreateEmptyBorder(gfx::Insets(kDropdownBorderWidth))); | |
| 202 dropdown_button_->set_ink_drop_size(gfx::Size(32, 32)); | |
| 203 AddChildView(dropdown_button_); | |
| 204 | |
| 205 LoadIcon(); | |
| 206 | |
| 207 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 208 font_list_ = | |
| 209 rb.GetFontList(ui::ResourceBundle::BaseFont).DeriveWithSizeDelta(1); | |
| 210 status_font_list_ = | |
| 211 rb.GetFontList(ui::ResourceBundle::BaseFont).DeriveWithSizeDelta(-2); | |
| 212 | |
| 213 SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); | |
| 214 | |
| 215 OnDownloadUpdated(download()); | |
| 216 | |
| 217 SetDropdownState(NORMAL); | |
| 218 UpdateColorsFromTheme(); | |
| 219 } | |
| 220 | |
| 221 DownloadItemViewMd::~DownloadItemViewMd() { | |
| 222 StopDownloadProgress(); | |
| 223 download()->RemoveObserver(this); | |
| 224 | |
| 225 // ExperienceSampling: If the user took no action to remove the warning | |
| 226 // before it disappeared, then the user effectively dismissed the download | |
| 227 // without keeping it. | |
| 228 if (sampling_event_.get()) | |
| 229 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kIgnore); | |
| 230 } | |
| 231 | |
| 232 // Progress animation handlers. | |
| 233 | |
| 234 void DownloadItemViewMd::StartDownloadProgress() { | |
| 235 if (progress_timer_.IsRunning()) | |
| 236 return; | |
| 237 progress_start_time_ = base::TimeTicks::Now(); | |
| 238 progress_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds( | |
| 239 DownloadShelf::kProgressRateMs), | |
| 240 base::Bind(&DownloadItemViewMd::ProgressTimerFired, | |
| 241 base::Unretained(this))); | |
| 242 } | |
| 243 | |
| 244 void DownloadItemViewMd::StopDownloadProgress() { | |
| 245 if (!progress_timer_.IsRunning()) | |
| 246 return; | |
| 247 previous_progress_elapsed_ += base::TimeTicks::Now() - progress_start_time_; | |
| 248 progress_start_time_ = base::TimeTicks(); | |
| 249 progress_timer_.Stop(); | |
| 250 } | |
| 251 | |
| 252 // static | |
| 253 SkColor DownloadItemViewMd::GetTextColorForThemeProvider( | |
| 254 const ui::ThemeProvider* theme) { | |
| 255 return theme ? theme->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT) | |
| 256 : gfx::kPlaceholderColor; | |
| 257 } | |
| 258 | |
| 259 void DownloadItemViewMd::OnExtractIconComplete(gfx::Image* icon_bitmap) { | |
| 260 if (icon_bitmap) | |
| 261 shelf_->SchedulePaint(); | |
| 262 } | |
| 263 | |
| 264 // DownloadObserver interface. | |
| 265 | |
| 266 // Update the progress graphic on the icon and our text status label | |
| 267 // to reflect our current bytes downloaded, time remaining. | |
| 268 void DownloadItemViewMd::OnDownloadUpdated(DownloadItem* download_item) { | |
| 269 DCHECK_EQ(download(), download_item); | |
| 270 | |
| 271 if (!model_.ShouldShowInShelf()) { | |
| 272 shelf_->RemoveDownloadView(this); // This will delete us! | |
| 273 return; | |
| 274 } | |
| 275 | |
| 276 if (IsShowingWarningDialog() != model_.IsDangerous()) { | |
| 277 ToggleWarningDialog(); | |
| 278 } else { | |
| 279 switch (download()->GetState()) { | |
| 280 case DownloadItem::IN_PROGRESS: | |
| 281 download()->IsPaused() ? StopDownloadProgress() | |
| 282 : StartDownloadProgress(); | |
| 283 LoadIconIfItemPathChanged(); | |
| 284 break; | |
| 285 case DownloadItem::INTERRUPTED: | |
| 286 StopDownloadProgress(); | |
| 287 complete_animation_.reset(new gfx::SlideAnimation(this)); | |
| 288 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs); | |
| 289 complete_animation_->SetTweenType(gfx::Tween::LINEAR); | |
| 290 complete_animation_->Show(); | |
| 291 LoadIcon(); | |
| 292 break; | |
| 293 case DownloadItem::COMPLETE: | |
| 294 if (model_.ShouldRemoveFromShelfWhenComplete()) { | |
| 295 shelf_->RemoveDownloadView(this); // This will delete us! | |
| 296 return; | |
| 297 } | |
| 298 StopDownloadProgress(); | |
| 299 complete_animation_.reset(new gfx::SlideAnimation(this)); | |
| 300 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs); | |
| 301 complete_animation_->SetTweenType(gfx::Tween::LINEAR); | |
| 302 complete_animation_->Show(); | |
| 303 LoadIcon(); | |
| 304 break; | |
| 305 case DownloadItem::CANCELLED: | |
| 306 StopDownloadProgress(); | |
| 307 if (complete_animation_) | |
| 308 complete_animation_->Stop(); | |
| 309 LoadIcon(); | |
| 310 break; | |
| 311 default: | |
| 312 NOTREACHED(); | |
| 313 } | |
| 314 status_text_ = model_.GetStatusText(); | |
| 315 SchedulePaint(); | |
| 316 } | |
| 317 | |
| 318 base::string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth); | |
| 319 if (new_tip != tooltip_text_) { | |
| 320 tooltip_text_ = new_tip; | |
| 321 TooltipTextChanged(); | |
| 322 } | |
| 323 | |
| 324 UpdateAccessibleName(); | |
| 325 } | |
| 326 | |
| 327 void DownloadItemViewMd::OnDownloadDestroyed(DownloadItem* download) { | |
| 328 shelf_->RemoveDownloadView(this); // This will delete us! | |
| 329 } | |
| 330 | |
| 331 void DownloadItemViewMd::OnDownloadOpened(DownloadItem* download) { | |
| 332 disabled_while_opening_ = true; | |
| 333 SetEnabled(false); | |
| 334 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
| 335 FROM_HERE, | |
| 336 base::Bind(&DownloadItemViewMd::Reenable, weak_ptr_factory_.GetWeakPtr()), | |
| 337 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration)); | |
| 338 | |
| 339 // Notify our parent. | |
| 340 shelf_->OpenedDownload(); | |
| 341 } | |
| 342 | |
| 343 // View overrides | |
| 344 | |
| 345 // In dangerous mode we have to layout our buttons. | |
| 346 void DownloadItemViewMd::Layout() { | |
| 347 UpdateColorsFromTheme(); | |
| 348 | |
| 349 if (IsShowingWarningDialog()) { | |
| 350 gfx::Point child_origin( | |
| 351 kStartPadding + kWarningIconSize + kStartPadding, | |
| 352 (height() - dangerous_download_label_->height()) / 2); | |
| 353 dangerous_download_label_->SetPosition(child_origin); | |
| 354 | |
| 355 child_origin.Offset(dangerous_download_label_->width() + kLabelPadding, 0); | |
| 356 gfx::Size button_size = GetButtonSize(); | |
| 357 child_origin.set_y((height() - button_size.height()) / 2); | |
| 358 if (save_button_) { | |
| 359 save_button_->SetBoundsRect(gfx::Rect(child_origin, button_size)); | |
| 360 child_origin.Offset(button_size.width() + kButtonPadding, 0); | |
| 361 } | |
| 362 discard_button_->SetBoundsRect(gfx::Rect(child_origin, button_size)); | |
| 363 } | |
| 364 | |
| 365 if (mode_ != DANGEROUS_MODE) { | |
| 366 dropdown_button_->SizeToPreferredSize(); | |
| 367 dropdown_button_->SetPosition( | |
| 368 gfx::Point(width() - dropdown_button_->width() - kEndPadding, | |
| 369 (height() - dropdown_button_->height()) / 2)); | |
| 370 } | |
| 371 } | |
| 372 | |
| 373 gfx::Size DownloadItemViewMd::GetPreferredSize() const { | |
| 374 int width = 0; | |
| 375 // We set the height to the height of two rows or text plus margins. | |
| 376 int child_height = font_list_.GetBaseline() + kVerticalTextPadding + | |
| 377 status_font_list_.GetHeight(); | |
| 378 | |
| 379 if (IsShowingWarningDialog()) { | |
| 380 // Width. | |
| 381 width = kStartPadding + kWarningIconSize + kStartPadding + | |
| 382 dangerous_download_label_->width() + kLabelPadding; | |
| 383 gfx::Size button_size = GetButtonSize(); | |
| 384 if (save_button_) | |
| 385 width += button_size.width() + kButtonPadding; | |
| 386 width += button_size.width() + kEndPadding; | |
| 387 | |
| 388 // Height: make sure the button fits and the warning icon fits. | |
| 389 child_height = | |
| 390 std::max({child_height, button_size.height(), kWarningIconSize}); | |
| 391 } else { | |
| 392 width = kStartPadding + DownloadShelf::kProgressIndicatorSize + | |
| 393 kProgressTextPadding + kTextWidth + kEndPadding; | |
| 394 } | |
| 395 | |
| 396 if (mode_ != DANGEROUS_MODE) | |
| 397 width += dropdown_button_->GetPreferredSize().width(); | |
| 398 | |
| 399 return gfx::Size(width, std::max(kDefaultHeight, | |
| 400 2 * kMinimumVerticalPadding + child_height)); | |
| 401 } | |
| 402 | |
| 403 bool DownloadItemViewMd::OnMousePressed(const ui::MouseEvent& event) { | |
| 404 HandlePressEvent(event, event.IsOnlyLeftMouseButton()); | |
| 405 return true; | |
| 406 } | |
| 407 | |
| 408 // Handle drag (file copy) operations. | |
| 409 bool DownloadItemViewMd::OnMouseDragged(const ui::MouseEvent& event) { | |
| 410 // Mouse should not activate us in dangerous mode. | |
| 411 if (IsShowingWarningDialog()) | |
| 412 return true; | |
| 413 | |
| 414 if (!starting_drag_) { | |
| 415 starting_drag_ = true; | |
| 416 drag_start_point_ = event.location(); | |
| 417 AnimateInkDrop(views::InkDropState::HIDDEN, &event); | |
| 418 } | |
| 419 if (dragging_) { | |
| 420 if (download()->GetState() == DownloadItem::COMPLETE) { | |
| 421 IconManager* im = g_browser_process->icon_manager(); | |
| 422 gfx::Image* icon = im->LookupIconFromFilepath( | |
| 423 download()->GetTargetFilePath(), IconLoader::SMALL); | |
| 424 views::Widget* widget = GetWidget(); | |
| 425 DragDownloadItem(download(), icon, | |
| 426 widget ? widget->GetNativeView() : NULL); | |
| 427 } | |
| 428 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) { | |
| 429 dragging_ = true; | |
| 430 } | |
| 431 return true; | |
| 432 } | |
| 433 | |
| 434 void DownloadItemViewMd::OnMouseReleased(const ui::MouseEvent& event) { | |
| 435 HandleClickEvent(event, event.IsOnlyLeftMouseButton()); | |
| 436 } | |
| 437 | |
| 438 void DownloadItemViewMd::OnMouseCaptureLost() { | |
| 439 // Mouse should not activate us in dangerous mode. | |
| 440 if (mode_ != NORMAL_MODE) | |
| 441 return; | |
| 442 | |
| 443 if (dragging_) { | |
| 444 // Starting a drag results in a MouseCaptureLost. | |
| 445 dragging_ = false; | |
| 446 starting_drag_ = false; | |
| 447 } | |
| 448 } | |
| 449 | |
| 450 bool DownloadItemViewMd::OnKeyPressed(const ui::KeyEvent& event) { | |
| 451 // Key press should not activate us in dangerous mode. | |
| 452 if (IsShowingWarningDialog()) | |
| 453 return true; | |
| 454 | |
| 455 if (event.key_code() == ui::VKEY_SPACE || | |
| 456 event.key_code() == ui::VKEY_RETURN) { | |
| 457 AnimateInkDrop(views::InkDropState::ACTION_TRIGGERED, nullptr /* &event */); | |
| 458 // OpenDownload may delete this, so don't add any code after this line. | |
| 459 OpenDownload(); | |
| 460 return true; | |
| 461 } | |
| 462 return false; | |
| 463 } | |
| 464 | |
| 465 bool DownloadItemViewMd::GetTooltipText(const gfx::Point& p, | |
| 466 base::string16* tooltip) const { | |
| 467 if (IsShowingWarningDialog()) { | |
| 468 tooltip->clear(); | |
| 469 return false; | |
| 470 } | |
| 471 | |
| 472 tooltip->assign(tooltip_text_); | |
| 473 | |
| 474 return true; | |
| 475 } | |
| 476 | |
| 477 void DownloadItemViewMd::GetAccessibleState(ui::AXViewState* state) { | |
| 478 state->name = accessible_name_; | |
| 479 state->role = ui::AX_ROLE_BUTTON; | |
| 480 if (model_.IsDangerous()) | |
| 481 state->AddStateFlag(ui::AX_STATE_DISABLED); | |
| 482 else | |
| 483 state->AddStateFlag(ui::AX_STATE_HASPOPUP); | |
| 484 } | |
| 485 | |
| 486 void DownloadItemViewMd::OnThemeChanged() { | |
| 487 UpdateColorsFromTheme(); | |
| 488 SchedulePaint(); | |
| 489 } | |
| 490 | |
| 491 void DownloadItemViewMd::AddInkDropLayer(ui::Layer* ink_drop_layer) { | |
| 492 InkDropHostView::AddInkDropLayer(ink_drop_layer); | |
| 493 // The layer that's added to host the ink drop layer must mask to bounds | |
| 494 // so the hover effect is clipped while animating open. | |
| 495 layer()->SetMasksToBounds(true); | |
| 496 } | |
| 497 | |
| 498 std::unique_ptr<views::InkDropRipple> DownloadItemViewMd::CreateInkDropRipple() | |
| 499 const { | |
| 500 return base::MakeUnique<views::FloodFillInkDropRipple>( | |
| 501 GetLocalBounds(), GetInkDropCenterBasedOnLastEvent(), | |
| 502 color_utils::DeriveDefaultIconColor(GetTextColor()), | |
| 503 ink_drop_visible_opacity()); | |
| 504 } | |
| 505 | |
| 506 std::unique_ptr<views::InkDropHighlight> | |
| 507 DownloadItemViewMd::CreateInkDropHighlight() const { | |
| 508 if (IsShowingWarningDialog()) | |
| 509 return nullptr; | |
| 510 | |
| 511 gfx::Size size = GetPreferredSize(); | |
| 512 return base::MakeUnique<views::InkDropHighlight>( | |
| 513 size, kInkDropSmallCornerRadius, | |
| 514 gfx::RectF(gfx::SizeF(size)).CenterPoint(), | |
| 515 color_utils::DeriveDefaultIconColor(GetTextColor())); | |
| 516 } | |
| 517 | |
| 518 void DownloadItemViewMd::OnGestureEvent(ui::GestureEvent* event) { | |
| 519 if (event->type() == ui::ET_GESTURE_TAP_DOWN) { | |
| 520 HandlePressEvent(*event, true); | |
| 521 event->SetHandled(); | |
| 522 return; | |
| 523 } | |
| 524 | |
| 525 if (event->type() == ui::ET_GESTURE_TAP) { | |
| 526 HandleClickEvent(*event, true); | |
| 527 event->SetHandled(); | |
| 528 return; | |
| 529 } | |
| 530 | |
| 531 views::View::OnGestureEvent(event); | |
| 532 } | |
| 533 | |
| 534 void DownloadItemViewMd::ShowContextMenuForView( | |
| 535 View* source, | |
| 536 const gfx::Point& point, | |
| 537 ui::MenuSourceType source_type) { | |
| 538 ShowContextMenuImpl(gfx::Rect(point, gfx::Size()), source_type); | |
| 539 } | |
| 540 | |
| 541 void DownloadItemViewMd::ButtonPressed(views::Button* sender, | |
| 542 const ui::Event& event) { | |
| 543 if (sender == dropdown_button_) { | |
| 544 // TODO(estade): this is copied from ToolbarActionView but should be shared | |
| 545 // one way or another. | |
| 546 ui::MenuSourceType type = ui::MENU_SOURCE_NONE; | |
| 547 if (event.IsMouseEvent()) | |
| 548 type = ui::MENU_SOURCE_MOUSE; | |
| 549 else if (event.IsKeyEvent()) | |
| 550 type = ui::MENU_SOURCE_KEYBOARD; | |
| 551 else if (event.IsGestureEvent()) | |
| 552 type = ui::MENU_SOURCE_TOUCH; | |
| 553 SetDropdownState(PUSHED); | |
| 554 ShowContextMenuImpl(dropdown_button_->GetBoundsInScreen(), type); | |
| 555 return; | |
| 556 } | |
| 557 | |
| 558 base::TimeDelta warning_duration; | |
| 559 if (!time_download_warning_shown_.is_null()) | |
| 560 warning_duration = base::Time::Now() - time_download_warning_shown_; | |
| 561 | |
| 562 if (save_button_ && sender == save_button_) { | |
| 563 // The user has confirmed a dangerous download. We'd record how quickly the | |
| 564 // user did this to detect whether we're being clickjacked. | |
| 565 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration); | |
| 566 // ExperienceSampling: User chose to proceed with a dangerous download. | |
| 567 if (sampling_event_.get()) { | |
| 568 sampling_event_->CreateUserDecisionEvent( | |
| 569 ExperienceSamplingEvent::kProceed); | |
| 570 sampling_event_.reset(NULL); | |
| 571 } | |
| 572 // This will change the state and notify us. | |
| 573 download()->ValidateDangerousDownload(); | |
| 574 return; | |
| 575 } | |
| 576 | |
| 577 // WARNING: all end states after this point delete |this|. | |
| 578 DCHECK_EQ(discard_button_, sender); | |
| 579 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration); | |
| 580 if (!model_.IsMalicious() && model_.ShouldAllowDownloadFeedback() && | |
| 581 !shelf_->browser()->profile()->IsOffTheRecord()) { | |
| 582 if (!shelf_->browser()->profile()->GetPrefs()->HasPrefPath( | |
| 583 prefs::kSafeBrowsingExtendedReportingEnabled)) { | |
| 584 // Show dialog, because the dialog hasn't been shown before. | |
| 585 DownloadFeedbackDialogView::Show( | |
| 586 shelf_->get_parent()->GetNativeWindow(), shelf_->browser()->profile(), | |
| 587 shelf_->GetNavigator(), | |
| 588 base::Bind( | |
| 589 &DownloadItemViewMd::PossiblySubmitDownloadToFeedbackService, | |
| 590 weak_ptr_factory_.GetWeakPtr())); | |
| 591 } else { | |
| 592 PossiblySubmitDownloadToFeedbackService( | |
| 593 shelf_->browser()->profile()->GetPrefs()->GetBoolean( | |
| 594 prefs::kSafeBrowsingExtendedReportingEnabled)); | |
| 595 } | |
| 596 return; | |
| 597 } | |
| 598 download()->Remove(); | |
| 599 } | |
| 600 | |
| 601 SkColor DownloadItemViewMd::GetVectorIconBaseColor() const { | |
| 602 return GetTextColor(); | |
| 603 } | |
| 604 | |
| 605 void DownloadItemViewMd::AnimationProgressed(const gfx::Animation* animation) { | |
| 606 // We don't care if what animation (body button/drop button/complete), | |
| 607 // is calling back, as they all have to go through the same paint call. | |
| 608 SchedulePaint(); | |
| 609 } | |
| 610 | |
| 611 void DownloadItemViewMd::OnPaint(gfx::Canvas* canvas) { | |
| 612 // Make sure to draw |this| opaquely. Since the toolbar color can be partially | |
| 613 // transparent, start with a black backdrop (which is the default initialized | |
| 614 // color for opaque canvases). | |
| 615 canvas->DrawColor(SK_ColorBLACK); | |
| 616 canvas->DrawColor( | |
| 617 GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR)); | |
| 618 | |
| 619 DrawStatusText(canvas); | |
| 620 DrawFilename(canvas); | |
| 621 DrawIcon(canvas); | |
| 622 OnPaintBorder(canvas); | |
| 623 } | |
| 624 | |
| 625 int DownloadItemViewMd::GetYForFilenameText() const { | |
| 626 int text_height = font_list_.GetBaseline(); | |
| 627 if (!status_text_.empty()) | |
| 628 text_height += kVerticalTextPadding + status_font_list_.GetBaseline(); | |
| 629 return (height() - text_height) / 2; | |
| 630 } | |
| 631 | |
| 632 void DownloadItemViewMd::DrawStatusText(gfx::Canvas* canvas) { | |
| 633 if (status_text_.empty() || IsShowingWarningDialog()) | |
| 634 return; | |
| 635 | |
| 636 int mirrored_x = GetMirroredXWithWidthInView( | |
| 637 kStartPadding + DownloadShelf::kProgressIndicatorSize + | |
| 638 kProgressTextPadding, | |
| 639 kTextWidth); | |
| 640 int y = | |
| 641 GetYForFilenameText() + font_list_.GetBaseline() + kVerticalTextPadding; | |
| 642 canvas->DrawStringRect( | |
| 643 status_text_, status_font_list_, GetDimmedTextColor(), | |
| 644 gfx::Rect(mirrored_x, y, kTextWidth, status_font_list_.GetHeight())); | |
| 645 } | |
| 646 | |
| 647 void DownloadItemViewMd::DrawFilename(gfx::Canvas* canvas) { | |
| 648 if (IsShowingWarningDialog()) | |
| 649 return; | |
| 650 | |
| 651 // Print the text, left aligned and always print the file extension. | |
| 652 // Last value of x was the end of the right image, just before the button. | |
| 653 // Note that in dangerous mode we use a label (as the text is multi-line). | |
| 654 base::string16 filename; | |
| 655 if (!disabled_while_opening_) { | |
| 656 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(), | |
| 657 font_list_, kTextWidth); | |
| 658 } else { | |
| 659 // First, Calculate the download status opening string width. | |
| 660 base::string16 status_string = l10n_util::GetStringFUTF16( | |
| 661 IDS_DOWNLOAD_STATUS_OPENING, base::string16()); | |
| 662 int status_string_width = gfx::GetStringWidth(status_string, font_list_); | |
| 663 // Then, elide the file name. | |
| 664 base::string16 filename_string = | |
| 665 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_, | |
| 666 kTextWidth - status_string_width); | |
| 667 // Last, concat the whole string. | |
| 668 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, | |
| 669 filename_string); | |
| 670 } | |
| 671 | |
| 672 int mirrored_x = GetMirroredXWithWidthInView( | |
| 673 kStartPadding + DownloadShelf::kProgressIndicatorSize + | |
| 674 kProgressTextPadding, | |
| 675 kTextWidth); | |
| 676 canvas->DrawStringRect(filename, font_list_, | |
| 677 enabled() ? GetTextColor() : GetDimmedTextColor(), | |
| 678 gfx::Rect(mirrored_x, GetYForFilenameText(), | |
| 679 kTextWidth, font_list_.GetHeight())); | |
| 680 } | |
| 681 | |
| 682 void DownloadItemViewMd::DrawIcon(gfx::Canvas* canvas) { | |
| 683 if (IsShowingWarningDialog()) { | |
| 684 int icon_x = base::i18n::IsRTL() | |
| 685 ? width() - kWarningIconSize - kStartPadding | |
| 686 : kStartPadding; | |
| 687 int icon_y = (height() - kWarningIconSize) / 2; | |
| 688 canvas->DrawImageInt(GetWarningIcon(), icon_x, icon_y); | |
| 689 return; | |
| 690 } | |
| 691 | |
| 692 // Paint download progress. | |
| 693 DownloadItem::DownloadState state = download()->GetState(); | |
| 694 canvas->Save(); | |
| 695 int progress_x = | |
| 696 base::i18n::IsRTL() | |
| 697 ? width() - kStartPadding - DownloadShelf::kProgressIndicatorSize | |
| 698 : kStartPadding; | |
| 699 int progress_y = (height() - DownloadShelf::kProgressIndicatorSize) / 2; | |
| 700 canvas->Translate(gfx::Vector2d(progress_x, progress_y)); | |
| 701 | |
| 702 if (state == DownloadItem::IN_PROGRESS) { | |
| 703 base::TimeDelta progress_time = previous_progress_elapsed_; | |
| 704 if (!download()->IsPaused()) | |
| 705 progress_time += base::TimeTicks::Now() - progress_start_time_; | |
| 706 DownloadShelf::PaintDownloadProgress( | |
| 707 canvas, *GetThemeProvider(), progress_time, model_.PercentComplete()); | |
| 708 } else if (complete_animation_.get() && complete_animation_->is_animating()) { | |
| 709 if (state == DownloadItem::INTERRUPTED) { | |
| 710 DownloadShelf::PaintDownloadInterrupted( | |
| 711 canvas, *GetThemeProvider(), complete_animation_->GetCurrentValue()); | |
| 712 } else { | |
| 713 DCHECK_EQ(DownloadItem::COMPLETE, state); | |
| 714 DownloadShelf::PaintDownloadComplete( | |
| 715 canvas, *GetThemeProvider(), complete_animation_->GetCurrentValue()); | |
| 716 } | |
| 717 } | |
| 718 canvas->Restore(); | |
| 719 | |
| 720 // Fetch the already-loaded icon. | |
| 721 IconManager* im = g_browser_process->icon_manager(); | |
| 722 gfx::Image* icon = im->LookupIconFromFilepath(download()->GetTargetFilePath(), | |
| 723 IconLoader::SMALL); | |
| 724 if (!icon) | |
| 725 return; | |
| 726 | |
| 727 // Draw the icon image. | |
| 728 int icon_x = progress_x + DownloadShelf::kFiletypeIconOffset; | |
| 729 int icon_y = progress_y + DownloadShelf::kFiletypeIconOffset; | |
| 730 SkPaint paint; | |
| 731 // Use an alpha to make the image look disabled. | |
| 732 if (!enabled()) | |
| 733 paint.setAlpha(120); | |
| 734 canvas->DrawImageInt(*icon->ToImageSkia(), icon_x, icon_y, paint); | |
| 735 } | |
| 736 | |
| 737 void DownloadItemViewMd::OnFocus() { | |
| 738 View::OnFocus(); | |
| 739 // We render differently when focused. | |
| 740 SchedulePaint(); | |
| 741 } | |
| 742 | |
| 743 void DownloadItemViewMd::OnBlur() { | |
| 744 View::OnBlur(); | |
| 745 // We render differently when focused. | |
| 746 SchedulePaint(); | |
| 747 } | |
| 748 | |
| 749 void DownloadItemViewMd::OpenDownload() { | |
| 750 DCHECK(!IsShowingWarningDialog()); | |
| 751 // We're interested in how long it takes users to open downloads. If they | |
| 752 // open downloads super quickly, we should be concerned about clickjacking. | |
| 753 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download", | |
| 754 base::Time::Now() - creation_time_); | |
| 755 | |
| 756 UpdateAccessibleName(); | |
| 757 | |
| 758 // Calling download()->OpenDownload may delete this, so this must be | |
| 759 // the last thing we do. | |
| 760 download()->OpenDownload(); | |
| 761 } | |
| 762 | |
| 763 bool DownloadItemViewMd::SubmitDownloadToFeedbackService() { | |
| 764 #if defined(FULL_SAFE_BROWSING) | |
| 765 safe_browsing::SafeBrowsingService* sb_service = | |
| 766 g_browser_process->safe_browsing_service(); | |
| 767 if (!sb_service) | |
| 768 return false; | |
| 769 safe_browsing::DownloadProtectionService* download_protection_service = | |
| 770 sb_service->download_protection_service(); | |
| 771 if (!download_protection_service) | |
| 772 return false; | |
| 773 download_protection_service->feedback_service()->BeginFeedbackForDownload( | |
| 774 download()); | |
| 775 // WARNING: we are deleted at this point. Don't access 'this'. | |
| 776 return true; | |
| 777 #else | |
| 778 NOTREACHED(); | |
| 779 return false; | |
| 780 #endif | |
| 781 } | |
| 782 | |
| 783 void DownloadItemViewMd::PossiblySubmitDownloadToFeedbackService(bool enabled) { | |
| 784 if (!enabled || !SubmitDownloadToFeedbackService()) | |
| 785 download()->Remove(); | |
| 786 // WARNING: 'this' is deleted at this point. Don't access 'this'. | |
| 787 } | |
| 788 | |
| 789 void DownloadItemViewMd::LoadIcon() { | |
| 790 IconManager* im = g_browser_process->icon_manager(); | |
| 791 last_download_item_path_ = download()->GetTargetFilePath(); | |
| 792 im->LoadIcon(last_download_item_path_, IconLoader::SMALL, | |
| 793 base::Bind(&DownloadItemViewMd::OnExtractIconComplete, | |
| 794 base::Unretained(this)), | |
| 795 &cancelable_task_tracker_); | |
| 796 } | |
| 797 | |
| 798 void DownloadItemViewMd::LoadIconIfItemPathChanged() { | |
| 799 base::FilePath current_download_path = download()->GetTargetFilePath(); | |
| 800 if (last_download_item_path_ == current_download_path) | |
| 801 return; | |
| 802 | |
| 803 LoadIcon(); | |
| 804 } | |
| 805 | |
| 806 void DownloadItemViewMd::UpdateColorsFromTheme() { | |
| 807 if (!GetThemeProvider()) | |
| 808 return; | |
| 809 | |
| 810 SetBorder(base::MakeUnique<SeparatorBorder>(GetThemeProvider()->GetColor( | |
| 811 ThemeProperties::COLOR_TOOLBAR_VERTICAL_SEPARATOR))); | |
| 812 | |
| 813 SkColor text_color = GetTextColor(); | |
| 814 if (dangerous_download_label_) | |
| 815 dangerous_download_label_->SetEnabledColor(text_color); | |
| 816 if (save_button_) | |
| 817 save_button_->SetEnabledTextColors(text_color); | |
| 818 if (discard_button_) | |
| 819 discard_button_->SetEnabledTextColors(text_color); | |
| 820 } | |
| 821 | |
| 822 void DownloadItemViewMd::ShowContextMenuImpl(const gfx::Rect& rect, | |
| 823 ui::MenuSourceType source_type) { | |
| 824 // Similar hack as in MenuButton. | |
| 825 // We're about to show the menu from a mouse press. By showing from the | |
| 826 // mouse press event we block RootView in mouse dispatching. This also | |
| 827 // appears to cause RootView to get a mouse pressed BEFORE the mouse | |
| 828 // release is seen, which means RootView sends us another mouse press no | |
| 829 // matter where the user pressed. To force RootView to recalculate the | |
| 830 // mouse target during the mouse press we explicitly set the mouse handler | |
| 831 // to NULL. | |
| 832 static_cast<views::internal::RootView*>(GetWidget()->GetRootView()) | |
| 833 ->SetMouseHandler(NULL); | |
| 834 | |
| 835 if (!context_menu_.get()) | |
| 836 context_menu_.reset(new DownloadShelfContextMenuView(download())); | |
| 837 context_menu_->Run(GetWidget()->GetTopLevelWidget(), rect, source_type, | |
| 838 base::Bind(&DownloadItemViewMd::ReleaseDropdown, | |
| 839 weak_ptr_factory_.GetWeakPtr())); | |
| 840 } | |
| 841 | |
| 842 void DownloadItemViewMd::HandlePressEvent(const ui::LocatedEvent& event, | |
| 843 bool active_event) { | |
| 844 // The event should not activate us in dangerous/malicious mode. | |
| 845 if (IsShowingWarningDialog()) | |
| 846 return; | |
| 847 | |
| 848 // Stop any completion animation. | |
| 849 if (complete_animation_.get() && complete_animation_->is_animating()) | |
| 850 complete_animation_->End(); | |
| 851 | |
| 852 // Don't show the ripple for right clicks. | |
| 853 if (!active_event) | |
| 854 return; | |
| 855 | |
| 856 AnimateInkDrop(views::InkDropState::ACTION_PENDING, &event); | |
| 857 } | |
| 858 | |
| 859 void DownloadItemViewMd::HandleClickEvent(const ui::LocatedEvent& event, | |
| 860 bool active_event) { | |
| 861 // The event should not activate us in dangerous/malicious mode. | |
| 862 if (!active_event || IsShowingWarningDialog()) | |
| 863 return; | |
| 864 | |
| 865 AnimateInkDrop(views::InkDropState::ACTION_TRIGGERED, &event); | |
| 866 | |
| 867 // OpenDownload may delete this, so don't add any code after this line. | |
| 868 OpenDownload(); | |
| 869 } | |
| 870 | |
| 871 void DownloadItemViewMd::SetDropdownState(State new_state) { | |
| 872 // Avoid extra SchedulePaint()s if the state is going to be the same and | |
| 873 // |dropdown_button_| has already been initialized. | |
| 874 if (dropdown_state_ == new_state && | |
| 875 !dropdown_button_->GetImage(views::CustomButton::STATE_NORMAL).isNull()) | |
| 876 return; | |
| 877 | |
| 878 dropdown_button_->SetIcon(new_state == PUSHED ? gfx::VectorIconId::FIND_NEXT | |
| 879 : gfx::VectorIconId::FIND_PREV); | |
| 880 if (new_state != dropdown_state_) { | |
| 881 dropdown_button_->AnimateInkDrop(new_state == PUSHED | |
| 882 ? views::InkDropState::ACTIVATED | |
| 883 : views::InkDropState::DEACTIVATED); | |
| 884 } | |
| 885 dropdown_button_->OnThemeChanged(); | |
| 886 dropdown_state_ = new_state; | |
| 887 SchedulePaint(); | |
| 888 } | |
| 889 | |
| 890 void DownloadItemViewMd::ToggleWarningDialog() { | |
| 891 if (model_.IsDangerous()) | |
| 892 ShowWarningDialog(); | |
| 893 else | |
| 894 ClearWarningDialog(); | |
| 895 | |
| 896 // We need to load the icon now that the download has the real path. | |
| 897 LoadIcon(); | |
| 898 | |
| 899 // Force the shelf to layout again as our size has changed. | |
| 900 shelf_->Layout(); | |
| 901 shelf_->SchedulePaint(); | |
| 902 } | |
| 903 | |
| 904 void DownloadItemViewMd::ClearWarningDialog() { | |
| 905 DCHECK(download()->GetDangerType() == | |
| 906 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED); | |
| 907 DCHECK(IsShowingWarningDialog()); | |
| 908 | |
| 909 mode_ = NORMAL_MODE; | |
| 910 dropdown_state_ = NORMAL; | |
| 911 | |
| 912 // ExperienceSampling: User proceeded through the warning. | |
| 913 if (sampling_event_.get()) { | |
| 914 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed); | |
| 915 sampling_event_.reset(NULL); | |
| 916 } | |
| 917 // Remove the views used by the warning dialog. | |
| 918 if (save_button_) { | |
| 919 RemoveChildView(save_button_); | |
| 920 delete save_button_; | |
| 921 save_button_ = NULL; | |
| 922 } | |
| 923 RemoveChildView(discard_button_); | |
| 924 delete discard_button_; | |
| 925 discard_button_ = NULL; | |
| 926 RemoveChildView(dangerous_download_label_); | |
| 927 delete dangerous_download_label_; | |
| 928 dangerous_download_label_ = NULL; | |
| 929 dangerous_download_label_sized_ = false; | |
| 930 | |
| 931 // We need to load the icon now that the download has the real path. | |
| 932 LoadIcon(); | |
| 933 | |
| 934 dropdown_button_->SetVisible(true); | |
| 935 } | |
| 936 | |
| 937 void DownloadItemViewMd::ShowWarningDialog() { | |
| 938 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE); | |
| 939 time_download_warning_shown_ = base::Time::Now(); | |
| 940 content::DownloadDangerType danger_type = download()->GetDangerType(); | |
| 941 RecordDangerousDownloadWarningShown(danger_type); | |
| 942 #if defined(FULL_SAFE_BROWSING) | |
| 943 if (model_.ShouldAllowDownloadFeedback()) { | |
| 944 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown( | |
| 945 danger_type); | |
| 946 } | |
| 947 #endif | |
| 948 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE; | |
| 949 | |
| 950 // ExperienceSampling: Dangerous or malicious download warning is being shown | |
| 951 // to the user, so we start a new SamplingEvent and track it. | |
| 952 std::string event_name = model_.MightBeMalicious() | |
| 953 ? ExperienceSamplingEvent::kMaliciousDownload | |
| 954 : ExperienceSamplingEvent::kDangerousDownload; | |
| 955 sampling_event_.reset(new ExperienceSamplingEvent( | |
| 956 event_name, download()->GetURL(), download()->GetReferrerUrl(), | |
| 957 download()->GetBrowserContext())); | |
| 958 | |
| 959 dropdown_state_ = NORMAL; | |
| 960 if (mode_ == DANGEROUS_MODE) { | |
| 961 save_button_ = | |
| 962 views::MdTextButton::Create(this, model_.GetWarningConfirmButtonText()); | |
| 963 AddChildView(save_button_); | |
| 964 } | |
| 965 discard_button_ = views::MdTextButton::Create( | |
| 966 this, l10n_util::GetStringUTF16(IDS_DISCARD_DOWNLOAD)); | |
| 967 AddChildView(discard_button_); | |
| 968 | |
| 969 base::string16 dangerous_label = | |
| 970 model_.GetWarningText(font_list_, kTextWidth); | |
| 971 dangerous_download_label_ = new views::Label(dangerous_label); | |
| 972 dangerous_download_label_->SetMultiLine(true); | |
| 973 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 974 dangerous_download_label_->SetAutoColorReadabilityEnabled(false); | |
| 975 AddChildView(dangerous_download_label_); | |
| 976 SizeLabelToMinWidth(); | |
| 977 | |
| 978 dropdown_button_->SetVisible(mode_ == MALICIOUS_MODE); | |
| 979 } | |
| 980 | |
| 981 gfx::ImageSkia DownloadItemViewMd::GetWarningIcon() { | |
| 982 switch (download()->GetDangerType()) { | |
| 983 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: | |
| 984 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: | |
| 985 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: | |
| 986 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: | |
| 987 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: | |
| 988 return gfx::CreateVectorIcon(gfx::VectorIconId::REMOVE_CIRCLE, | |
| 989 kWarningIconSize, | |
| 990 gfx::kGoogleRed700); | |
| 991 | |
| 992 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: | |
| 993 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: | |
| 994 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: | |
| 995 case content::DOWNLOAD_DANGER_TYPE_MAX: | |
| 996 NOTREACHED(); | |
| 997 break; | |
| 998 | |
| 999 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: | |
| 1000 return gfx::CreateVectorIcon(gfx::VectorIconId::WARNING, | |
| 1001 kWarningIconSize, | |
| 1002 gfx::kGoogleYellow700); | |
| 1003 } | |
| 1004 return gfx::ImageSkia(); | |
| 1005 } | |
| 1006 | |
| 1007 gfx::Size DownloadItemViewMd::GetButtonSize() const { | |
| 1008 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_)); | |
| 1009 gfx::Size size = discard_button_->GetPreferredSize(); | |
| 1010 if (save_button_) | |
| 1011 size.SetToMax(save_button_->GetPreferredSize()); | |
| 1012 return size; | |
| 1013 } | |
| 1014 | |
| 1015 // This method computes the minimum width of the label for displaying its text | |
| 1016 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the | |
| 1017 // configuration with minimum width. | |
| 1018 void DownloadItemViewMd::SizeLabelToMinWidth() { | |
| 1019 if (dangerous_download_label_sized_) | |
| 1020 return; | |
| 1021 | |
| 1022 base::string16 label_text = dangerous_download_label_->text(); | |
| 1023 base::TrimWhitespace(label_text, base::TRIM_ALL, &label_text); | |
| 1024 DCHECK_EQ(base::string16::npos, label_text.find('\n')); | |
| 1025 | |
| 1026 // Make the label big so that GetPreferredSize() is not constrained by the | |
| 1027 // current width. | |
| 1028 dangerous_download_label_->SetBounds(0, 0, 1000, 1000); | |
| 1029 | |
| 1030 // Use a const string from here. BreakIterator requies that text.data() not | |
| 1031 // change during its lifetime. | |
| 1032 const base::string16 original_text(label_text); | |
| 1033 // Using BREAK_WORD can work in most cases, but it can also break | |
| 1034 // lines where it should not. Using BREAK_LINE is safer although | |
| 1035 // slower for Chinese/Japanese. This is not perf-critical at all, though. | |
| 1036 base::i18n::BreakIterator iter(original_text, | |
| 1037 base::i18n::BreakIterator::BREAK_LINE); | |
| 1038 bool status = iter.Init(); | |
| 1039 DCHECK(status); | |
| 1040 | |
| 1041 base::string16 prev_text = original_text; | |
| 1042 gfx::Size size = dangerous_download_label_->GetPreferredSize(); | |
| 1043 int min_width = size.width(); | |
| 1044 | |
| 1045 // Go through the string and try each line break (starting with no line break) | |
| 1046 // searching for the optimal line break position. Stop if we find one that | |
| 1047 // yields one that is less than kDangerousTextWidth wide. This is to prevent | |
| 1048 // a short string (e.g.: "This file is malicious") from being broken up | |
| 1049 // unnecessarily. | |
| 1050 while (iter.Advance() && min_width > kDangerousTextWidth) { | |
| 1051 size_t pos = iter.pos(); | |
| 1052 if (pos >= original_text.length()) | |
| 1053 break; | |
| 1054 base::string16 current_text = original_text; | |
| 1055 // This can be a low surrogate codepoint, but u_isUWhiteSpace will | |
| 1056 // return false and inserting a new line after a surrogate pair | |
| 1057 // is perfectly ok. | |
| 1058 base::char16 line_end_char = current_text[pos - 1]; | |
| 1059 if (u_isUWhiteSpace(line_end_char)) | |
| 1060 current_text.replace(pos - 1, 1, 1, base::char16('\n')); | |
| 1061 else | |
| 1062 current_text.insert(pos, 1, base::char16('\n')); | |
| 1063 dangerous_download_label_->SetText(current_text); | |
| 1064 size = dangerous_download_label_->GetPreferredSize(); | |
| 1065 | |
| 1066 // If the width is growing again, it means we passed the optimal width spot. | |
| 1067 if (size.width() > min_width) { | |
| 1068 dangerous_download_label_->SetText(prev_text); | |
| 1069 break; | |
| 1070 } else { | |
| 1071 min_width = size.width(); | |
| 1072 } | |
| 1073 prev_text = current_text; | |
| 1074 } | |
| 1075 | |
| 1076 dangerous_download_label_->SetSize(size); | |
| 1077 dangerous_download_label_sized_ = true; | |
| 1078 } | |
| 1079 | |
| 1080 void DownloadItemViewMd::Reenable() { | |
| 1081 disabled_while_opening_ = false; | |
| 1082 SetEnabled(true); // Triggers a repaint. | |
| 1083 } | |
| 1084 | |
| 1085 void DownloadItemViewMd::ReleaseDropdown() { | |
| 1086 SetDropdownState(NORMAL); | |
| 1087 } | |
| 1088 | |
| 1089 void DownloadItemViewMd::UpdateAccessibleName() { | |
| 1090 base::string16 new_name; | |
| 1091 if (IsShowingWarningDialog()) { | |
| 1092 new_name = dangerous_download_label_->text(); | |
| 1093 } else { | |
| 1094 new_name = status_text_ + base::char16(' ') + | |
| 1095 download()->GetFileNameToReportUser().LossyDisplayName(); | |
| 1096 } | |
| 1097 | |
| 1098 // If the name has changed, notify assistive technology that the name | |
| 1099 // has changed so they can announce it immediately. | |
| 1100 if (new_name != accessible_name_) { | |
| 1101 accessible_name_ = new_name; | |
| 1102 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true); | |
| 1103 } | |
| 1104 } | |
| 1105 | |
| 1106 void DownloadItemViewMd::AnimateStateTransition( | |
| 1107 State from, | |
| 1108 State to, | |
| 1109 gfx::SlideAnimation* animation) { | |
| 1110 if (from == NORMAL && to == HOT) { | |
| 1111 animation->Show(); | |
| 1112 } else if (from == HOT && to == NORMAL) { | |
| 1113 animation->Hide(); | |
| 1114 } else if (from != to) { | |
| 1115 animation->Reset((to == HOT) ? 1.0 : 0.0); | |
| 1116 } | |
| 1117 } | |
| 1118 | |
| 1119 void DownloadItemViewMd::ProgressTimerFired() { | |
| 1120 // Only repaint for the indeterminate size case. Otherwise, we'll repaint only | |
| 1121 // when there's an update notified via OnDownloadUpdated(). | |
| 1122 if (model_.PercentComplete() < 0) | |
| 1123 SchedulePaint(); | |
| 1124 } | |
| 1125 | |
| 1126 SkColor DownloadItemViewMd::GetTextColor() const { | |
| 1127 return GetTextColorForThemeProvider(GetThemeProvider()); | |
| 1128 } | |
| 1129 | |
| 1130 SkColor DownloadItemViewMd::GetDimmedTextColor() const { | |
| 1131 return SkColorSetA(GetTextColor(), 0xC7); | |
| 1132 } | |
| OLD | NEW |