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 |