Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(102)

Side by Side Diff: chrome/browser/ui/views/download/download_item_view_md.cc

Issue 2346043002: Delete old (pre-MD) download shelf view code. (Closed)
Patch Set: gotta git add Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/views/download/download_item_view_md.h ('k') | chrome/browser/ui/views/download/download_shelf_view.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698