• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/tabs/tab.h"
6 
7 #include <limits>
8 
9 #include "base/command_line.h"
10 #include "base/debug/alias.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/defaults.h"
13 #include "chrome/browser/themes/theme_properties.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
16 #include "chrome/browser/ui/tabs/tab_resources.h"
17 #include "chrome/browser/ui/tabs/tab_utils.h"
18 #include "chrome/browser/ui/view_ids.h"
19 #include "chrome/browser/ui/views/tabs/media_indicator_button.h"
20 #include "chrome/browser/ui/views/tabs/tab_controller.h"
21 #include "chrome/browser/ui/views/theme_image_mapper.h"
22 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "content/public/browser/user_metrics.h"
26 #include "grit/theme_resources.h"
27 #include "third_party/skia/include/effects/SkGradientShader.h"
28 #include "ui/accessibility/ax_view_state.h"
29 #include "ui/aura/env.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/models/list_selection_model.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/base/theme_provider.h"
34 #include "ui/gfx/animation/animation_container.h"
35 #include "ui/gfx/animation/multi_animation.h"
36 #include "ui/gfx/animation/throb_animation.h"
37 #include "ui/gfx/canvas.h"
38 #include "ui/gfx/color_analysis.h"
39 #include "ui/gfx/favicon_size.h"
40 #include "ui/gfx/image/image_skia_operations.h"
41 #include "ui/gfx/path.h"
42 #include "ui/gfx/rect_conversions.h"
43 #include "ui/gfx/skia_util.h"
44 #include "ui/resources/grit/ui_resources.h"
45 #include "ui/views/border.h"
46 #include "ui/views/controls/button/image_button.h"
47 #include "ui/views/controls/label.h"
48 #include "ui/views/rect_based_targeting_utils.h"
49 #include "ui/views/view_targeter.h"
50 #include "ui/views/widget/tooltip_manager.h"
51 #include "ui/views/widget/widget.h"
52 #include "ui/views/window/non_client_view.h"
53 
54 using base::UserMetricsAction;
55 
56 namespace {
57 
58 // Padding around the "content" of a tab, occupied by the tab border graphics.
59 const int kLeftPadding = 22;
60 const int kTopPadding = 4;
61 const int kRightPadding = 17;
62 const int kBottomPadding = 2;
63 
64 // Height of the shadow at the top of the tab image assets.
65 const int kDropShadowHeight = 4;
66 
67 // How long the pulse throb takes.
68 const int kPulseDurationMs = 200;
69 
70 // Width of touch tabs.
71 static const int kTouchWidth = 120;
72 
73 static const int kToolbarOverlap = 1;
74 static const int kFaviconTitleSpacing = 4;
75 static const int kViewSpacing = 3;
76 static const int kStandardTitleWidth = 175;
77 
78 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
79 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
80 // is rendered as a normal tab. This is done to avoid having the title
81 // immediately disappear when transitioning a tab from normal to mini-tab.
82 static const int kMiniTabRendererAsNormalTabWidth =
83     browser_defaults::kMiniTabWidth + 30;
84 
85 // How opaque to make the hover state (out of 1).
86 static const double kHoverOpacity = 0.33;
87 
88 // Opacity for non-active selected tabs.
89 static const double kSelectedTabOpacity = .45;
90 
91 // Selected (but not active) tabs have their throb value scaled down by this.
92 static const double kSelectedTabThrobScale = .5;
93 
94 // Durations for the various parts of the mini tab title animation.
95 static const int kMiniTitleChangeAnimationDuration1MS = 1600;
96 static const int kMiniTitleChangeAnimationStart1MS = 0;
97 static const int kMiniTitleChangeAnimationEnd1MS = 1900;
98 static const int kMiniTitleChangeAnimationDuration2MS = 0;
99 static const int kMiniTitleChangeAnimationDuration3MS = 550;
100 static const int kMiniTitleChangeAnimationStart3MS = 150;
101 static const int kMiniTitleChangeAnimationEnd3MS = 800;
102 static const int kMiniTitleChangeAnimationIntervalMS = 40;
103 
104 // Offset from the right edge for the start of the mini title change animation.
105 static const int kMiniTitleChangeInitialXOffset = 6;
106 
107 // Radius of the radial gradient used for mini title change animation.
108 static const int kMiniTitleChangeGradientRadius = 20;
109 
110 // Colors of the gradient used during the mini title change animation.
111 static const SkColor kMiniTitleChangeGradientColor1 = SK_ColorWHITE;
112 static const SkColor kMiniTitleChangeGradientColor2 =
113     SkColorSetARGB(0, 255, 255, 255);
114 
115 // Max number of images to cache. This has to be at least two since rounding
116 // errors may lead to tabs in the same tabstrip having different sizes.
117 const size_t kMaxImageCacheSize = 4;
118 
119 // Height of the miniature tab strip in immersive mode.
120 const int kImmersiveTabHeight = 3;
121 
122 // Height of the small tab indicator rectangles in immersive mode.
123 const int kImmersiveBarHeight = 2;
124 
125 // Color for active and inactive tabs in the immersive mode light strip. These
126 // should be a little brighter than the color of the normal art assets for tabs,
127 // which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184.
128 const SkColor kImmersiveActiveTabColor = SkColorSetRGB(235, 235, 235);
129 const SkColor kImmersiveInactiveTabColor = SkColorSetRGB(190, 190, 190);
130 
131 // The minimum opacity (out of 1) when a tab (either active or inactive) is
132 // throbbing in the immersive mode light strip.
133 const double kImmersiveTabMinThrobOpacity = 0.66;
134 
135 // Number of steps in the immersive mode loading animation.
136 const int kImmersiveLoadingStepCount = 32;
137 
138 const char kTabCloseButtonName[] = "TabCloseButton";
139 
DrawIconAtLocation(gfx::Canvas * canvas,const gfx::ImageSkia & image,int image_offset,int dst_x,int dst_y,int icon_width,int icon_height,bool filter,const SkPaint & paint)140 void DrawIconAtLocation(gfx::Canvas* canvas,
141                         const gfx::ImageSkia& image,
142                         int image_offset,
143                         int dst_x,
144                         int dst_y,
145                         int icon_width,
146                         int icon_height,
147                         bool filter,
148                         const SkPaint& paint) {
149   // NOTE: the clipping is a work around for 69528, it shouldn't be necessary.
150   canvas->Save();
151   canvas->ClipRect(gfx::Rect(dst_x, dst_y, icon_width, icon_height));
152   canvas->DrawImageInt(image,
153                        image_offset, 0, icon_width, icon_height,
154                        dst_x, dst_y, icon_width, icon_height,
155                        filter, paint);
156   canvas->Restore();
157 }
158 
159 // Draws the icon image at the center of |bounds|.
DrawIconCenter(gfx::Canvas * canvas,const gfx::ImageSkia & image,int image_offset,int icon_width,int icon_height,const gfx::Rect & bounds,bool filter,const SkPaint & paint)160 void DrawIconCenter(gfx::Canvas* canvas,
161                     const gfx::ImageSkia& image,
162                     int image_offset,
163                     int icon_width,
164                     int icon_height,
165                     const gfx::Rect& bounds,
166                     bool filter,
167                     const SkPaint& paint) {
168   // Center the image within bounds.
169   int dst_x = bounds.x() - (icon_width - bounds.width()) / 2;
170   int dst_y = bounds.y() - (icon_height - bounds.height()) / 2;
171   DrawIconAtLocation(canvas, image, image_offset, dst_x, dst_y, icon_width,
172                      icon_height, filter, paint);
173 }
174 
GetHostDesktopType(views::View * view)175 chrome::HostDesktopType GetHostDesktopType(views::View* view) {
176   // Widget is NULL when tabs are detached.
177   views::Widget* widget = view->GetWidget();
178   return chrome::GetHostDesktopTypeForNativeView(
179       widget ? widget->GetNativeView() : NULL);
180 }
181 
182 // Stop()s |animation| and then deletes it. We do this rather than just deleting
183 // so that the delegate is notified before the destruction.
StopAndDeleteAnimation(scoped_ptr<gfx::Animation> animation)184 void StopAndDeleteAnimation(scoped_ptr<gfx::Animation> animation) {
185   if (animation)
186     animation->Stop();
187 }
188 
189 }  // namespace
190 
191 ////////////////////////////////////////////////////////////////////////////////
192 // FaviconCrashAnimation
193 //
194 //  A custom animation subclass to manage the favicon crash animation.
195 class Tab::FaviconCrashAnimation : public gfx::LinearAnimation,
196                                    public gfx::AnimationDelegate {
197  public:
FaviconCrashAnimation(Tab * target)198   explicit FaviconCrashAnimation(Tab* target)
199       : gfx::LinearAnimation(1000, 25, this),
200         target_(target) {
201   }
~FaviconCrashAnimation()202   virtual ~FaviconCrashAnimation() {}
203 
204   // gfx::Animation overrides:
AnimateToState(double state)205   virtual void AnimateToState(double state) OVERRIDE {
206     const double kHidingOffset = 27;
207 
208     if (state < .5) {
209       target_->SetFaviconHidingOffset(
210           static_cast<int>(floor(kHidingOffset * 2.0 * state)));
211     } else {
212       target_->DisplayCrashedFavicon();
213       target_->SetFaviconHidingOffset(
214           static_cast<int>(
215               floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
216     }
217   }
218 
219   // gfx::AnimationDelegate overrides:
AnimationCanceled(const gfx::Animation * animation)220   virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
221     target_->SetFaviconHidingOffset(0);
222   }
223 
224  private:
225   Tab* target_;
226 
227   DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation);
228 };
229 
230 ////////////////////////////////////////////////////////////////////////////////
231 // TabCloseButton
232 //
233 //  This is a Button subclass that causes middle clicks to be forwarded to the
234 //  parent View by explicitly not handling them in OnMousePressed.
235 class Tab::TabCloseButton : public views::ImageButton,
236                             public views::MaskedTargeterDelegate {
237  public:
TabCloseButton(Tab * tab)238   explicit TabCloseButton(Tab* tab)
239       : views::ImageButton(tab),
240         tab_(tab) {
241     SetEventTargeter(
242         scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
243   }
244 
~TabCloseButton()245   virtual ~TabCloseButton() {}
246 
247   // views::View:
GetTooltipHandlerForPoint(const gfx::Point & point)248   virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE {
249     // Tab close button has no children, so tooltip handler should be the same
250     // as the event handler.
251     // In addition, a hit test has to be performed for the point (as
252     // GetTooltipHandlerForPoint() is responsible for it).
253     if (!HitTestPoint(point))
254       return NULL;
255     return GetEventHandlerForPoint(point);
256   }
257 
OnMousePressed(const ui::MouseEvent & event)258   virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
259     tab_->controller_->OnMouseEventInTab(this, event);
260 
261     bool handled = ImageButton::OnMousePressed(event);
262     // Explicitly mark midle-mouse clicks as non-handled to ensure the tab
263     // sees them.
264     return event.IsOnlyMiddleMouseButton() ? false : handled;
265   }
266 
OnMouseMoved(const ui::MouseEvent & event)267   virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE {
268     tab_->controller_->OnMouseEventInTab(this, event);
269     CustomButton::OnMouseMoved(event);
270   }
271 
OnMouseReleased(const ui::MouseEvent & event)272   virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
273     tab_->controller_->OnMouseEventInTab(this, event);
274     CustomButton::OnMouseReleased(event);
275   }
276 
OnGestureEvent(ui::GestureEvent * event)277   virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
278     // Consume all gesture events here so that the parent (Tab) does not
279     // start consuming gestures.
280     ImageButton::OnGestureEvent(event);
281     event->SetHandled();
282   }
283 
GetClassName() const284   virtual const char* GetClassName() const OVERRIDE {
285     return kTabCloseButtonName;
286   }
287 
288  private:
289   // Returns the rectangular bounds of parent tab's visible region in the
290   // local coordinate space of |this|.
GetTabBounds() const291   gfx::Rect GetTabBounds() const {
292     gfx::Path tab_mask;
293     tab_->GetHitTestMask(&tab_mask);
294 
295     gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds()));
296     views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f);
297     return gfx::ToEnclosingRect(tab_bounds_f);
298   }
299 
300   // Returns the rectangular bounds of the tab close button in the local
301   // coordinate space of |this|, not including clipped regions on the top
302   // or bottom of the button. |tab_bounds| is the rectangular bounds of
303   // the parent tab's visible region in the local coordinate space of |this|.
GetTabCloseButtonBounds(const gfx::Rect & tab_bounds) const304   gfx::Rect GetTabCloseButtonBounds(const gfx::Rect& tab_bounds) const {
305     gfx::Rect button_bounds(GetContentsBounds());
306     button_bounds.set_x(GetMirroredXForRect(button_bounds));
307 
308     int top_overflow = tab_bounds.y() - button_bounds.y();
309     int bottom_overflow = button_bounds.bottom() - tab_bounds.bottom();
310     if (top_overflow > 0)
311       button_bounds.set_y(tab_bounds.y());
312     else if (bottom_overflow > 0)
313       button_bounds.set_height(button_bounds.height() - bottom_overflow);
314 
315     return button_bounds;
316   }
317 
318   // views::ViewTargeterDelegate:
TargetForRect(View * root,const gfx::Rect & rect)319   virtual View* TargetForRect(View* root, const gfx::Rect& rect) OVERRIDE {
320     CHECK_EQ(root, this);
321 
322     if (!views::UsePointBasedTargeting(rect))
323       return ViewTargeterDelegate::TargetForRect(root, rect);
324 
325     // Ignore the padding set on the button.
326     gfx::Rect contents_bounds = GetContentsBounds();
327     contents_bounds.set_x(GetMirroredXForRect(contents_bounds));
328 
329     // Include the padding in hit-test for touch events.
330     if (aura::Env::GetInstance()->is_touch_down())
331       contents_bounds = GetLocalBounds();
332 
333     return contents_bounds.Intersects(rect) ? this : parent();
334   }
335 
336   // views:MaskedTargeterDelegate:
GetHitTestMask(gfx::Path * mask) const337   virtual bool GetHitTestMask(gfx::Path* mask) const OVERRIDE {
338     DCHECK(mask);
339     mask->reset();
340 
341     // The parent tab may be partially occluded by another tab if we are
342     // in stacked tab mode, which means that the tab close button may also
343     // be partially occluded. Define the hit test mask of the tab close
344     // button to be the intersection of the parent tab's visible bounds
345     // and the bounds of the tab close button.
346     gfx::Rect tab_bounds(GetTabBounds());
347     gfx::Rect button_bounds(GetTabCloseButtonBounds(tab_bounds));
348     gfx::Rect intersection(gfx::IntersectRects(tab_bounds, button_bounds));
349 
350     if (!intersection.IsEmpty()) {
351       mask->addRect(RectToSkRect(intersection));
352       return true;
353     }
354 
355     return false;
356   }
357 
DoesIntersectRect(const View * target,const gfx::Rect & rect) const358   virtual bool DoesIntersectRect(const View* target,
359                                  const gfx::Rect& rect) const OVERRIDE {
360     CHECK_EQ(target, this);
361 
362     // If the request is not made in response to a gesture, use the
363     // default implementation.
364     if (views::UsePointBasedTargeting(rect))
365       return MaskedTargeterDelegate::DoesIntersectRect(target, rect);
366 
367     // The hit test request is in response to a gesture. Return false if any
368     // part of the tab close button is hidden from the user.
369     // TODO(tdanderson): Consider always returning the intersection if the
370     //                   non-rectangular shape of the tab can be accounted for.
371     gfx::Rect tab_bounds(GetTabBounds());
372     gfx::Rect button_bounds(GetTabCloseButtonBounds(tab_bounds));
373     if (!tab_bounds.Contains(button_bounds))
374       return false;
375 
376     return MaskedTargeterDelegate::DoesIntersectRect(target, rect);
377   }
378 
379   Tab* tab_;
380 
381   DISALLOW_COPY_AND_ASSIGN(TabCloseButton);
382 };
383 
384 ////////////////////////////////////////////////////////////////////////////////
385 // ImageCacheEntry
386 
ImageCacheEntry()387 Tab::ImageCacheEntry::ImageCacheEntry()
388     : resource_id(-1),
389       scale_factor(ui::SCALE_FACTOR_NONE) {
390 }
391 
~ImageCacheEntry()392 Tab::ImageCacheEntry::~ImageCacheEntry() {}
393 
394 ////////////////////////////////////////////////////////////////////////////////
395 // Tab, statics:
396 
397 // static
398 const char Tab::kViewClassName[] = "Tab";
399 Tab::TabImage Tab::tab_active_ = {0};
400 Tab::TabImage Tab::tab_inactive_ = {0};
401 Tab::TabImage Tab::tab_alpha_ = {0};
402 Tab::ImageCache* Tab::image_cache_ = NULL;
403 
404 ////////////////////////////////////////////////////////////////////////////////
405 // Tab, public:
406 
Tab(TabController * controller)407 Tab::Tab(TabController* controller)
408     : controller_(controller),
409       closing_(false),
410       dragging_(false),
411       detached_(false),
412       favicon_hiding_offset_(0),
413       loading_animation_frame_(0),
414       immersive_loading_step_(0),
415       should_display_crashed_favicon_(false),
416       close_button_(NULL),
417       media_indicator_button_(NULL),
418       title_(new views::Label()),
419       tab_activated_with_last_tap_down_(false),
420       hover_controller_(this),
421       showing_icon_(false),
422       showing_media_indicator_(false),
423       showing_close_button_(false),
424       close_button_color_(0) {
425   DCHECK(controller);
426   InitTabResources();
427 
428   // So we get don't get enter/exit on children and don't prematurely stop the
429   // hover.
430   set_notify_enter_exit_on_child(true);
431 
432   set_id(VIEW_ID_TAB);
433 
434   title_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
435   title_->SetElideBehavior(gfx::FADE_TAIL);
436   title_->SetAutoColorReadabilityEnabled(false);
437   title_->SetText(CoreTabHelper::GetDefaultTitle());
438   AddChildView(title_);
439 
440   SetEventTargeter(
441       scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
442 
443   // Add the Close Button.
444   close_button_ = new TabCloseButton(this);
445   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
446   close_button_->SetImage(views::CustomButton::STATE_NORMAL,
447                           rb.GetImageSkiaNamed(IDR_CLOSE_1));
448   close_button_->SetImage(views::CustomButton::STATE_HOVERED,
449                           rb.GetImageSkiaNamed(IDR_CLOSE_1_H));
450   close_button_->SetImage(views::CustomButton::STATE_PRESSED,
451                           rb.GetImageSkiaNamed(IDR_CLOSE_1_P));
452   close_button_->SetAccessibleName(
453       l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
454   // Disable animation so that the red danger sign shows up immediately
455   // to help avoid mis-clicks.
456   close_button_->SetAnimationDuration(0);
457   AddChildView(close_button_);
458 
459   set_context_menu_controller(this);
460 }
461 
~Tab()462 Tab::~Tab() {
463 }
464 
set_animation_container(gfx::AnimationContainer * container)465 void Tab::set_animation_container(gfx::AnimationContainer* container) {
466   animation_container_ = container;
467   hover_controller_.SetAnimationContainer(container);
468 }
469 
IsActive() const470 bool Tab::IsActive() const {
471   return controller_->IsActiveTab(this);
472 }
473 
IsSelected() const474 bool Tab::IsSelected() const {
475   return controller_->IsTabSelected(this);
476 }
477 
SetData(const TabRendererData & data)478 void Tab::SetData(const TabRendererData& data) {
479   if (data_.Equals(data))
480     return;
481 
482   TabRendererData old(data_);
483   data_ = data;
484 
485   base::string16 title = data_.title;
486   if (title.empty()) {
487     title = data_.loading ?
488         l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
489         CoreTabHelper::GetDefaultTitle();
490   } else {
491     Browser::FormatTitleForDisplay(&title);
492   }
493   title_->SetText(title);
494 
495   if (data_.IsCrashed()) {
496     if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation()) {
497       data_.media_state = TAB_MEDIA_STATE_NONE;
498 #if defined(OS_CHROMEOS)
499       // On Chrome OS, we reload killed tabs automatically when the user
500       // switches to them.  Don't display animations for these unless they're
501       // selected (i.e. in the foreground) -- we won't reload these
502       // automatically since we don't want to get into a crash loop.
503       if (IsSelected() ||
504           data_.crashed_status != base::TERMINATION_STATUS_PROCESS_WAS_KILLED)
505         StartCrashAnimation();
506 #else
507       StartCrashAnimation();
508 #endif
509     }
510   } else {
511     if (IsPerformingCrashAnimation())
512       StopCrashAnimation();
513     ResetCrashedFavicon();
514   }
515 
516   if (data_.media_state != old.media_state)
517     GetMediaIndicatorButton()->TransitionToMediaState(data_.media_state);
518 
519   if (old.mini != data_.mini) {
520     StopAndDeleteAnimation(
521         mini_title_change_animation_.PassAs<gfx::Animation>());
522   }
523 
524   DataChanged(old);
525 
526   Layout();
527   SchedulePaint();
528 }
529 
UpdateLoadingAnimation(TabRendererData::NetworkState state)530 void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) {
531   if (state == data_.network_state &&
532       state == TabRendererData::NETWORK_STATE_NONE) {
533     // If the network state is none and hasn't changed, do nothing. Otherwise we
534     // need to advance the animation frame.
535     return;
536   }
537 
538   TabRendererData::NetworkState old_state = data_.network_state;
539   data_.network_state = state;
540   AdvanceLoadingAnimation(old_state, state);
541 }
542 
StartPulse()543 void Tab::StartPulse() {
544   pulse_animation_.reset(new gfx::ThrobAnimation(this));
545   pulse_animation_->SetSlideDuration(kPulseDurationMs);
546   if (animation_container_.get())
547     pulse_animation_->SetContainer(animation_container_.get());
548   pulse_animation_->StartThrobbing(std::numeric_limits<int>::max());
549 }
550 
StopPulse()551 void Tab::StopPulse() {
552   StopAndDeleteAnimation(pulse_animation_.PassAs<gfx::Animation>());
553 }
554 
StartMiniTabTitleAnimation()555 void Tab::StartMiniTabTitleAnimation() {
556   if (!data().mini)
557     return;
558   if (!mini_title_change_animation_) {
559     gfx::MultiAnimation::Parts parts;
560     parts.push_back(
561         gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS,
562                                  gfx::Tween::EASE_OUT));
563     parts.push_back(
564         gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS,
565                                  gfx::Tween::ZERO));
566     parts.push_back(
567         gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS,
568                                  gfx::Tween::EASE_IN));
569     parts[0].start_time_ms = kMiniTitleChangeAnimationStart1MS;
570     parts[0].end_time_ms = kMiniTitleChangeAnimationEnd1MS;
571     parts[2].start_time_ms = kMiniTitleChangeAnimationStart3MS;
572     parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS;
573     base::TimeDelta timeout =
574         base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS);
575     mini_title_change_animation_.reset(new gfx::MultiAnimation(parts, timeout));
576     if (animation_container_.get())
577       mini_title_change_animation_->SetContainer(animation_container_.get());
578     mini_title_change_animation_->set_delegate(this);
579   }
580   mini_title_change_animation_->Start();
581 }
582 
StopMiniTabTitleAnimation()583 void Tab::StopMiniTabTitleAnimation() {
584   StopAndDeleteAnimation(mini_title_change_animation_.PassAs<gfx::Animation>());
585 }
586 
587 // static
GetBasicMinimumUnselectedSize()588 gfx::Size Tab::GetBasicMinimumUnselectedSize() {
589   InitTabResources();
590 
591   gfx::Size minimum_size;
592   minimum_size.set_width(kLeftPadding + kRightPadding);
593   // Since we use image images, the real minimum height of the image is
594   // defined most accurately by the height of the end cap images.
595   minimum_size.set_height(tab_active_.image_l->height());
596   return minimum_size;
597 }
598 
GetMinimumUnselectedSize()599 gfx::Size Tab::GetMinimumUnselectedSize() {
600   return GetBasicMinimumUnselectedSize();
601 }
602 
603 // static
GetMinimumSelectedSize()604 gfx::Size Tab::GetMinimumSelectedSize() {
605   gfx::Size minimum_size = GetBasicMinimumUnselectedSize();
606   minimum_size.set_width(
607       kLeftPadding + gfx::kFaviconSize + kRightPadding);
608   return minimum_size;
609 }
610 
611 // static
GetStandardSize()612 gfx::Size Tab::GetStandardSize() {
613   gfx::Size standard_size = GetBasicMinimumUnselectedSize();
614   standard_size.set_width(
615       standard_size.width() + kFaviconTitleSpacing + kStandardTitleWidth);
616   return standard_size;
617 }
618 
619 // static
GetTouchWidth()620 int Tab::GetTouchWidth() {
621   return kTouchWidth;
622 }
623 
624 // static
GetMiniWidth()625 int Tab::GetMiniWidth() {
626   return browser_defaults::kMiniTabWidth;
627 }
628 
629 // static
GetImmersiveHeight()630 int Tab::GetImmersiveHeight() {
631   return kImmersiveTabHeight;
632 }
633 
634 ////////////////////////////////////////////////////////////////////////////////
635 // Tab, AnimationDelegate overrides:
636 
AnimationProgressed(const gfx::Animation * animation)637 void Tab::AnimationProgressed(const gfx::Animation* animation) {
638   // Ignore if the pulse animation is being performed on active tab because
639   // it repaints the same image. See |Tab::PaintTabBackground()|.
640   if (animation == pulse_animation_.get() && IsActive())
641     return;
642   SchedulePaint();
643 }
644 
AnimationCanceled(const gfx::Animation * animation)645 void Tab::AnimationCanceled(const gfx::Animation* animation) {
646   SchedulePaint();
647 }
648 
AnimationEnded(const gfx::Animation * animation)649 void Tab::AnimationEnded(const gfx::Animation* animation) {
650   SchedulePaint();
651 }
652 
653 ////////////////////////////////////////////////////////////////////////////////
654 // Tab, views::ButtonListener overrides:
655 
ButtonPressed(views::Button * sender,const ui::Event & event)656 void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) {
657   if (media_indicator_button_ && media_indicator_button_->visible()) {
658     if (media_indicator_button_->enabled())
659       content::RecordAction(UserMetricsAction("CloseTab_MuteToggleAvailable"));
660     else if (data_.media_state == TAB_MEDIA_STATE_AUDIO_PLAYING)
661       content::RecordAction(UserMetricsAction("CloseTab_AudioIndicator"));
662     else
663       content::RecordAction(UserMetricsAction("CloseTab_RecordingIndicator"));
664   } else {
665     content::RecordAction(UserMetricsAction("CloseTab_NoMediaIndicator"));
666   }
667 
668   const CloseTabSource source =
669       (event.type() == ui::ET_MOUSE_RELEASED &&
670        (event.flags() & ui::EF_FROM_TOUCH) == 0) ? CLOSE_TAB_FROM_MOUSE :
671       CLOSE_TAB_FROM_TOUCH;
672   DCHECK_EQ(close_button_, sender);
673   controller_->CloseTab(this, source);
674   if (event.type() == ui::ET_GESTURE_TAP)
675     TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP);
676 }
677 
678 ////////////////////////////////////////////////////////////////////////////////
679 // Tab, views::ContextMenuController overrides:
680 
ShowContextMenuForView(views::View * source,const gfx::Point & point,ui::MenuSourceType source_type)681 void Tab::ShowContextMenuForView(views::View* source,
682                                  const gfx::Point& point,
683                                  ui::MenuSourceType source_type) {
684   if (!closing())
685     controller_->ShowContextMenuForTab(this, point, source_type);
686 }
687 
688 ////////////////////////////////////////////////////////////////////////////////
689 // Tab, views::MaskedTargeterDelegate overrides:
690 
GetHitTestMask(gfx::Path * mask) const691 bool Tab::GetHitTestMask(gfx::Path* mask) const {
692   DCHECK(mask);
693 
694   // When the window is maximized we don't want to shave off the edges or top
695   // shadow of the tab, such that the user can click anywhere along the top
696   // edge of the screen to select a tab. Ditto for immersive fullscreen.
697   const views::Widget* widget = GetWidget();
698   bool include_top_shadow =
699       widget && (widget->IsMaximized() || widget->IsFullscreen());
700   TabResources::GetHitTestMask(width(), height(), include_top_shadow, mask);
701 
702   // It is possible for a portion of the tab to be occluded if tabs are
703   // stacked, so modify the hit test mask to only include the visible
704   // region of the tab.
705   gfx::Rect clip;
706   controller_->ShouldPaintTab(this, &clip);
707   if (clip.size().GetArea()) {
708     SkRect intersection(mask->getBounds());
709     intersection.intersect(RectToSkRect(clip));
710     mask->reset();
711     mask->addRect(intersection);
712   }
713 
714   return true;
715 }
716 
717 ////////////////////////////////////////////////////////////////////////////////
718 // Tab, views::View overrides:
719 
OnPaint(gfx::Canvas * canvas)720 void Tab::OnPaint(gfx::Canvas* canvas) {
721   // Don't paint if we're narrower than we can render correctly. (This should
722   // only happen during animations).
723   if (width() < GetMinimumUnselectedSize().width() && !data().mini)
724     return;
725 
726   gfx::Rect clip;
727   if (!controller_->ShouldPaintTab(this, &clip))
728     return;
729   if (!clip.IsEmpty()) {
730     canvas->Save();
731     canvas->ClipRect(clip);
732   }
733 
734   if (controller_->IsImmersiveStyle())
735     PaintImmersiveTab(canvas);
736   else
737     PaintTab(canvas);
738 
739   if (!clip.IsEmpty())
740     canvas->Restore();
741 }
742 
Layout()743 void Tab::Layout() {
744   gfx::Rect lb = GetContentsBounds();
745   if (lb.IsEmpty())
746     return;
747 
748   lb.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
749   showing_icon_ = ShouldShowIcon();
750   favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0);
751   if (showing_icon_) {
752     favicon_bounds_.set_size(gfx::Size(gfx::kFaviconSize, gfx::kFaviconSize));
753     favicon_bounds_.set_y(lb.y() + (lb.height() - gfx::kFaviconSize + 1) / 2);
754     MaybeAdjustLeftForMiniTab(&favicon_bounds_);
755   }
756 
757   showing_close_button_ = ShouldShowCloseBox();
758   if (showing_close_button_) {
759     // If the ratio of the close button size to tab width exceeds the maximum.
760     // The close button should be as large as possible so that there is a larger
761     // hit-target for touch events. So the close button bounds extends to the
762     // edges of the tab. However, the larger hit-target should be active only
763     // for mouse events, and the close-image should show up in the right place.
764     // So a border is added to the button with necessary padding. The close
765     // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target
766     // only for touch events.
767     close_button_->SetBorder(views::Border::NullBorder());
768     const gfx::Size close_button_size(close_button_->GetPreferredSize());
769     const int top = lb.y() + (lb.height() - close_button_size.height() + 1) / 2;
770     const int bottom = height() - (close_button_size.height() + top);
771     const int left = kViewSpacing;
772     const int right = width() - (lb.width() + close_button_size.width() + left);
773     close_button_->SetBorder(
774         views::Border::CreateEmptyBorder(top, left, bottom, right));
775     close_button_->SetPosition(gfx::Point(lb.width(), 0));
776     close_button_->SizeToPreferredSize();
777   }
778   close_button_->SetVisible(showing_close_button_);
779 
780   showing_media_indicator_ = ShouldShowMediaIndicator();
781   if (showing_media_indicator_) {
782     views::ImageButton* const button = GetMediaIndicatorButton();
783     const gfx::Size image_size(button->GetPreferredSize());
784     const int right = showing_close_button_ ?
785         close_button_->x() + close_button_->GetInsets().left() : lb.right();
786     gfx::Rect bounds(
787         std::max(lb.x(), right - image_size.width()),
788         lb.y() + (lb.height() - image_size.height() + 1) / 2,
789         image_size.width(),
790         image_size.height());
791     MaybeAdjustLeftForMiniTab(&bounds);
792     button->SetBoundsRect(bounds);
793     button->SetVisible(true);
794   } else if (media_indicator_button_) {
795     media_indicator_button_->SetVisible(false);
796   }
797 
798   // Size the title to fill the remaining width and use all available height.
799   bool show_title = !data().mini || width() >= kMiniTabRendererAsNormalTabWidth;
800   if (show_title) {
801     int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
802     int title_width = lb.width() - title_left;
803     if (showing_media_indicator_) {
804       title_width = media_indicator_button_->x() - kViewSpacing - title_left;
805     } else if (close_button_->visible()) {
806       // Allow the title to overlay the close button's empty border padding.
807       title_width = close_button_->x() + close_button_->GetInsets().left() -
808           kViewSpacing - title_left;
809     }
810     gfx::Rect rect(title_left, lb.y(), std::max(title_width, 0), lb.height());
811     const int title_height = title_->GetPreferredSize().height();
812     if (title_height > rect.height()) {
813       rect.set_y(lb.y() - (title_height - rect.height()) / 2);
814       rect.set_height(title_height);
815     }
816     title_->SetBoundsRect(rect);
817   }
818   title_->SetVisible(show_title);
819 }
820 
OnThemeChanged()821 void Tab::OnThemeChanged() {
822   LoadTabImages();
823 }
824 
GetClassName() const825 const char* Tab::GetClassName() const {
826   return kViewClassName;
827 }
828 
GetTooltipText(const gfx::Point & p,base::string16 * tooltip) const829 bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const {
830   // Note: Anything that affects the tooltip text should be accounted for when
831   // calling TooltipTextChanged() from Tab::DataChanged().
832   *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state);
833   return !tooltip->empty();
834 }
835 
GetTooltipTextOrigin(const gfx::Point & p,gfx::Point * origin) const836 bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const {
837   origin->set_x(title_->x() + 10);
838   origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4);
839   return true;
840 }
841 
OnMousePressed(const ui::MouseEvent & event)842 bool Tab::OnMousePressed(const ui::MouseEvent& event) {
843   controller_->OnMouseEventInTab(this, event);
844 
845   // Allow a right click from touch to drag, which corresponds to a long click.
846   if (event.IsOnlyLeftMouseButton() ||
847       (event.IsOnlyRightMouseButton() && event.flags() & ui::EF_FROM_TOUCH)) {
848     ui::ListSelectionModel original_selection;
849     original_selection.Copy(controller_->GetSelectionModel());
850     // Changing the selection may cause our bounds to change. If that happens
851     // the location of the event may no longer be valid. Create a copy of the
852     // event in the parents coordinate, which won't change, and recreate an
853     // event after changing so the coordinates are correct.
854     ui::MouseEvent event_in_parent(event, static_cast<View*>(this), parent());
855     if (controller_->SupportsMultipleSelection()) {
856       if (event.IsShiftDown() && event.IsControlDown()) {
857         controller_->AddSelectionFromAnchorTo(this);
858       } else if (event.IsShiftDown()) {
859         controller_->ExtendSelectionTo(this);
860       } else if (event.IsControlDown()) {
861         controller_->ToggleSelected(this);
862         if (!IsSelected()) {
863           // Don't allow dragging non-selected tabs.
864           return false;
865         }
866       } else if (!IsSelected()) {
867         controller_->SelectTab(this);
868       }
869     } else if (!IsSelected()) {
870       controller_->SelectTab(this);
871     }
872     ui::MouseEvent cloned_event(event_in_parent, parent(),
873                                 static_cast<View*>(this));
874     controller_->MaybeStartDrag(this, cloned_event, original_selection);
875   }
876   return true;
877 }
878 
OnMouseDragged(const ui::MouseEvent & event)879 bool Tab::OnMouseDragged(const ui::MouseEvent& event) {
880   controller_->ContinueDrag(this, event);
881   return true;
882 }
883 
OnMouseReleased(const ui::MouseEvent & event)884 void Tab::OnMouseReleased(const ui::MouseEvent& event) {
885   controller_->OnMouseEventInTab(this, event);
886 
887   // Notify the drag helper that we're done with any potential drag operations.
888   // Clean up the drag helper, which is re-created on the next mouse press.
889   // In some cases, ending the drag will schedule the tab for destruction; if
890   // so, bail immediately, since our members are already dead and we shouldn't
891   // do anything else except drop the tab where it is.
892   if (controller_->EndDrag(END_DRAG_COMPLETE))
893     return;
894 
895   // Close tab on middle click, but only if the button is released over the tab
896   // (normal windows behavior is to discard presses of a UI element where the
897   // releases happen off the element).
898   if (event.IsMiddleMouseButton()) {
899     if (HitTestPoint(event.location())) {
900       controller_->CloseTab(this, CLOSE_TAB_FROM_MOUSE);
901     } else if (closing_) {
902       // We're animating closed and a middle mouse button was pushed on us but
903       // we don't contain the mouse anymore. We assume the user is clicking
904       // quicker than the animation and we should close the tab that falls under
905       // the mouse.
906       Tab* closest_tab = controller_->GetTabAt(this, event.location());
907       if (closest_tab)
908         controller_->CloseTab(closest_tab, CLOSE_TAB_FROM_MOUSE);
909     }
910   } else if (event.IsOnlyLeftMouseButton() && !event.IsShiftDown() &&
911              !event.IsControlDown()) {
912     // If the tab was already selected mouse pressed doesn't change the
913     // selection. Reset it now to handle the case where multiple tabs were
914     // selected.
915     controller_->SelectTab(this);
916 
917     if (media_indicator_button_ && media_indicator_button_->visible() &&
918         media_indicator_button_->bounds().Contains(event.location())) {
919       content::RecordAction(UserMetricsAction("TabMediaIndicator_Clicked"));
920     }
921   }
922 }
923 
OnMouseCaptureLost()924 void Tab::OnMouseCaptureLost() {
925   controller_->EndDrag(END_DRAG_CAPTURE_LOST);
926 }
927 
OnMouseEntered(const ui::MouseEvent & event)928 void Tab::OnMouseEntered(const ui::MouseEvent& event) {
929   hover_controller_.Show(views::GlowHoverController::SUBTLE);
930 }
931 
OnMouseMoved(const ui::MouseEvent & event)932 void Tab::OnMouseMoved(const ui::MouseEvent& event) {
933   hover_controller_.SetLocation(event.location());
934   controller_->OnMouseEventInTab(this, event);
935 }
936 
OnMouseExited(const ui::MouseEvent & event)937 void Tab::OnMouseExited(const ui::MouseEvent& event) {
938   hover_controller_.Hide();
939 }
940 
OnGestureEvent(ui::GestureEvent * event)941 void Tab::OnGestureEvent(ui::GestureEvent* event) {
942   switch (event->type()) {
943     case ui::ET_GESTURE_TAP_DOWN: {
944       // TAP_DOWN is only dispatched for the first touch point.
945       DCHECK_EQ(1, event->details().touch_points());
946 
947       // See comment in OnMousePressed() as to why we copy the event.
948       ui::GestureEvent event_in_parent(*event, static_cast<View*>(this),
949                                        parent());
950       ui::ListSelectionModel original_selection;
951       original_selection.Copy(controller_->GetSelectionModel());
952       tab_activated_with_last_tap_down_ = !IsActive();
953       if (!IsSelected())
954         controller_->SelectTab(this);
955       gfx::Point loc(event->location());
956       views::View::ConvertPointToScreen(this, &loc);
957       ui::GestureEvent cloned_event(event_in_parent, parent(),
958                                     static_cast<View*>(this));
959       controller_->MaybeStartDrag(this, cloned_event, original_selection);
960       break;
961     }
962 
963     case ui::ET_GESTURE_END:
964       controller_->EndDrag(END_DRAG_COMPLETE);
965       break;
966 
967     case ui::ET_GESTURE_SCROLL_UPDATE:
968       controller_->ContinueDrag(this, *event);
969       break;
970 
971     default:
972       break;
973   }
974   event->SetHandled();
975 }
976 
GetAccessibleState(ui::AXViewState * state)977 void Tab::GetAccessibleState(ui::AXViewState* state) {
978   state->role = ui::AX_ROLE_TAB;
979   state->name = data_.title;
980   state->AddStateFlag(ui::AX_STATE_MULTISELECTABLE);
981   state->AddStateFlag(ui::AX_STATE_SELECTABLE);
982   controller_->UpdateTabAccessibilityState(this, state);
983   if (IsSelected())
984     state->AddStateFlag(ui::AX_STATE_SELECTED);
985 }
986 
987 ////////////////////////////////////////////////////////////////////////////////
988 // Tab, private
989 
MaybeAdjustLeftForMiniTab(gfx::Rect * bounds) const990 void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect* bounds) const {
991   if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth)
992     return;
993   const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
994   const int ideal_delta = width() - GetMiniWidth();
995   const int ideal_x = (GetMiniWidth() - bounds->width()) / 2;
996   bounds->set_x(bounds->x() + static_cast<int>(
997       (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) *
998       (ideal_x - bounds->x())));
999 }
1000 
DataChanged(const TabRendererData & old)1001 void Tab::DataChanged(const TabRendererData& old) {
1002   if (data().media_state != old.media_state || data().title != old.title)
1003     TooltipTextChanged();
1004 
1005   if (data().blocked == old.blocked)
1006     return;
1007 
1008   if (data().blocked)
1009     StartPulse();
1010   else
1011     StopPulse();
1012 }
1013 
PaintTab(gfx::Canvas * canvas)1014 void Tab::PaintTab(gfx::Canvas* canvas) {
1015   // See if the model changes whether the icons should be painted.
1016   const bool show_icon = ShouldShowIcon();
1017   const bool show_media_indicator = ShouldShowMediaIndicator();
1018   const bool show_close_button = ShouldShowCloseBox();
1019   if (show_icon != showing_icon_ ||
1020       show_media_indicator != showing_media_indicator_ ||
1021       show_close_button != showing_close_button_) {
1022     Layout();
1023   }
1024 
1025   PaintTabBackground(canvas);
1026 
1027   const SkColor title_color = GetThemeProvider()->GetColor(IsSelected() ?
1028       ThemeProperties::COLOR_TAB_TEXT :
1029       ThemeProperties::COLOR_BACKGROUND_TAB_TEXT);
1030   title_->SetVisible(!data().mini ||
1031                      width() > kMiniTabRendererAsNormalTabWidth);
1032   title_->SetEnabledColor(title_color);
1033 
1034   if (show_icon)
1035     PaintIcon(canvas);
1036 
1037   // If the close button color has changed, generate a new one.
1038   if (!close_button_color_ || title_color != close_button_color_) {
1039     close_button_color_ = title_color;
1040     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1041     close_button_->SetBackground(close_button_color_,
1042         rb.GetImageSkiaNamed(IDR_CLOSE_1),
1043         rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK));
1044   }
1045 }
1046 
PaintImmersiveTab(gfx::Canvas * canvas)1047 void Tab::PaintImmersiveTab(gfx::Canvas* canvas) {
1048   // Use transparency for the draw-attention animation.
1049   int alpha = 255;
1050   if (pulse_animation_ && pulse_animation_->is_animating() && !data().mini) {
1051     alpha = pulse_animation_->CurrentValueBetween(
1052         255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity));
1053   }
1054 
1055   // Draw a gray rectangle to represent the tab. This works for mini-tabs as
1056   // well as regular ones. The active tab has a brigher bar.
1057   SkColor color =
1058       IsActive() ? kImmersiveActiveTabColor : kImmersiveInactiveTabColor;
1059   gfx::Rect bar_rect = GetImmersiveBarRect();
1060   canvas->FillRect(bar_rect, SkColorSetA(color, alpha));
1061 
1062   // Paint network activity indicator.
1063   // TODO(jamescook): Replace this placeholder animation with a real one.
1064   // For now, let's go with a Cylon eye effect, but in blue.
1065   if (data().network_state != TabRendererData::NETWORK_STATE_NONE) {
1066     const SkColor kEyeColor = SkColorSetARGB(alpha, 71, 138, 217);
1067     int eye_width = bar_rect.width() / 3;
1068     int eye_offset = bar_rect.width() * immersive_loading_step_ /
1069         kImmersiveLoadingStepCount;
1070     if (eye_offset + eye_width < bar_rect.width()) {
1071       // Draw a single indicator strip because it fits inside |bar_rect|.
1072       gfx::Rect eye_rect(
1073           bar_rect.x() + eye_offset, 0, eye_width, kImmersiveBarHeight);
1074       canvas->FillRect(eye_rect, kEyeColor);
1075     } else {
1076       // Draw two indicators to simulate the eye "wrapping around" to the left
1077       // side. The first part fills the remainder of the bar.
1078       int right_eye_width = bar_rect.width() - eye_offset;
1079       gfx::Rect right_eye_rect(
1080           bar_rect.x() + eye_offset, 0, right_eye_width, kImmersiveBarHeight);
1081       canvas->FillRect(right_eye_rect, kEyeColor);
1082       // The second part parts the remaining |eye_width| on the left.
1083       int left_eye_width = eye_offset + eye_width - bar_rect.width();
1084       gfx::Rect left_eye_rect(
1085           bar_rect.x(), 0, left_eye_width, kImmersiveBarHeight);
1086       canvas->FillRect(left_eye_rect, kEyeColor);
1087     }
1088   }
1089 }
1090 
PaintTabBackground(gfx::Canvas * canvas)1091 void Tab::PaintTabBackground(gfx::Canvas* canvas) {
1092   if (IsActive()) {
1093     PaintActiveTabBackground(canvas);
1094   } else {
1095     if (mini_title_change_animation_ &&
1096         mini_title_change_animation_->is_animating()) {
1097       PaintInactiveTabBackgroundWithTitleChange(canvas);
1098     } else {
1099       PaintInactiveTabBackground(canvas);
1100     }
1101 
1102     double throb_value = GetThrobValue();
1103     if (throb_value > 0) {
1104       canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff),
1105                              GetLocalBounds());
1106       PaintActiveTabBackground(canvas);
1107       canvas->Restore();
1108     }
1109   }
1110 }
1111 
PaintInactiveTabBackgroundWithTitleChange(gfx::Canvas * canvas)1112 void Tab::PaintInactiveTabBackgroundWithTitleChange(gfx::Canvas* canvas) {
1113   // Render the inactive tab background. We'll use this for clipping.
1114   gfx::Canvas background_canvas(size(), canvas->image_scale(), false);
1115   PaintInactiveTabBackground(&background_canvas);
1116 
1117   gfx::ImageSkia background_image(background_canvas.ExtractImageRep());
1118 
1119   // Draw a radial gradient to hover_canvas.
1120   gfx::Canvas hover_canvas(size(), canvas->image_scale(), false);
1121   int radius = kMiniTitleChangeGradientRadius;
1122   int x0 = width() + radius - kMiniTitleChangeInitialXOffset;
1123   int x1 = radius;
1124   int x2 = -radius;
1125   int x;
1126   if (mini_title_change_animation_->current_part_index() == 0) {
1127     x = mini_title_change_animation_->CurrentValueBetween(x0, x1);
1128   } else if (mini_title_change_animation_->current_part_index() == 1) {
1129     x = x1;
1130   } else {
1131     x = mini_title_change_animation_->CurrentValueBetween(x1, x2);
1132   }
1133   SkPoint center_point;
1134   center_point.iset(x, 0);
1135   SkColor colors[2] = { kMiniTitleChangeGradientColor1,
1136                         kMiniTitleChangeGradientColor2 };
1137   skia::RefPtr<SkShader> shader = skia::AdoptRef(
1138       SkGradientShader::CreateRadial(
1139           center_point, SkIntToScalar(radius), colors, NULL, 2,
1140           SkShader::kClamp_TileMode));
1141   SkPaint paint;
1142   paint.setShader(shader.get());
1143   hover_canvas.DrawRect(gfx::Rect(x - radius, -radius, radius * 2, radius * 2),
1144                         paint);
1145 
1146   // Draw the radial gradient clipped to the background into hover_image.
1147   gfx::ImageSkia hover_image = gfx::ImageSkiaOperations::CreateMaskedImage(
1148       gfx::ImageSkia(hover_canvas.ExtractImageRep()), background_image);
1149 
1150   // Draw the tab background to the canvas.
1151   canvas->DrawImageInt(background_image, 0, 0);
1152 
1153   // And then the gradient on top of that.
1154   if (mini_title_change_animation_->current_part_index() == 2) {
1155     uint8 alpha = mini_title_change_animation_->CurrentValueBetween(255, 0);
1156     canvas->DrawImageInt(hover_image, 0, 0, alpha);
1157   } else {
1158     canvas->DrawImageInt(hover_image, 0, 0);
1159   }
1160 }
1161 
PaintInactiveTabBackground(gfx::Canvas * canvas)1162 void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas) {
1163   int tab_id;
1164   int frame_id;
1165   views::Widget* widget = GetWidget();
1166   GetTabIdAndFrameId(widget, &tab_id, &frame_id);
1167 
1168   // Explicitly map the id so we cache correctly.
1169   const chrome::HostDesktopType host_desktop_type = GetHostDesktopType(this);
1170   tab_id = chrome::MapThemeImage(host_desktop_type, tab_id);
1171 
1172   // HasCustomImage() is only true if the theme provides the image. However,
1173   // even if the theme does not provide a tab background, the theme machinery
1174   // will make one if given a frame image.
1175   ui::ThemeProvider* theme_provider = GetThemeProvider();
1176   const bool theme_provided_image = theme_provider->HasCustomImage(tab_id) ||
1177       (frame_id != 0 && theme_provider->HasCustomImage(frame_id));
1178 
1179   const bool can_cache = !theme_provided_image &&
1180       !hover_controller_.ShouldDraw();
1181 
1182   if (can_cache) {
1183     ui::ScaleFactor scale_factor =
1184         ui::GetSupportedScaleFactor(canvas->image_scale());
1185     gfx::ImageSkia cached_image(GetCachedImage(tab_id, size(), scale_factor));
1186     if (cached_image.width() == 0) {
1187       gfx::Canvas tmp_canvas(size(), canvas->image_scale(), false);
1188       PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas, tab_id);
1189       cached_image = gfx::ImageSkia(tmp_canvas.ExtractImageRep());
1190       SetCachedImage(tab_id, scale_factor, cached_image);
1191     }
1192     canvas->DrawImageInt(cached_image, 0, 0);
1193   } else {
1194     PaintInactiveTabBackgroundUsingResourceId(canvas, tab_id);
1195   }
1196 }
1197 
PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas * canvas,int tab_id)1198 void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas* canvas,
1199                                                     int tab_id) {
1200   // WARNING: the inactive tab background may be cached. If you change what it
1201   // is drawn from you may need to update whether it can be cached.
1202 
1203   // The tab image needs to be lined up with the background image
1204   // so that it feels partially transparent.  These offsets represent the tab
1205   // position within the frame background image.
1206   int offset = GetMirroredX() + background_offset_.x();
1207 
1208   gfx::ImageSkia* tab_bg = GetThemeProvider()->GetImageSkiaNamed(tab_id);
1209 
1210   TabImage* tab_image = &tab_active_;
1211   TabImage* tab_inactive_image = &tab_inactive_;
1212   TabImage* alpha = &tab_alpha_;
1213 
1214   // If the theme is providing a custom background image, then its top edge
1215   // should be at the top of the tab. Otherwise, we assume that the background
1216   // image is a composited foreground + frame image.
1217   int bg_offset_y = GetThemeProvider()->HasCustomImage(tab_id) ?
1218       0 : background_offset_.y();
1219 
1220   // We need a gfx::Canvas object to be able to extract the image from.
1221   // We draw everything to this canvas and then output it to the canvas
1222   // parameter in addition to using it to mask the hover glow if needed.
1223   gfx::Canvas background_canvas(size(), canvas->image_scale(), false);
1224 
1225   // Draw left edge.  Don't draw over the toolbar, as we're not the foreground
1226   // tab.
1227   gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage(
1228       *tab_bg, offset, bg_offset_y, tab_image->l_width, height());
1229   gfx::ImageSkia theme_l =
1230       gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l);
1231   background_canvas.DrawImageInt(theme_l,
1232       0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap,
1233       0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap,
1234       false);
1235 
1236   // Draw right edge.  Again, don't draw over the toolbar.
1237   gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg,
1238       offset + width() - tab_image->r_width, bg_offset_y,
1239       tab_image->r_width, height());
1240   gfx::ImageSkia theme_r =
1241       gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r);
1242   background_canvas.DrawImageInt(theme_r,
1243       0, 0, theme_r.width(), theme_r.height() - kToolbarOverlap,
1244       width() - theme_r.width(), 0, theme_r.width(),
1245       theme_r.height() - kToolbarOverlap, false);
1246 
1247   // Draw center.  Instead of masking out the top portion we simply skip over
1248   // it by incrementing by GetDropShadowHeight(), since it's a simple
1249   // rectangle. And again, don't draw over the toolbar.
1250   background_canvas.TileImageInt(*tab_bg,
1251      offset + tab_image->l_width,
1252      bg_offset_y + kDropShadowHeight,
1253      tab_image->l_width,
1254      kDropShadowHeight,
1255      width() - tab_image->l_width - tab_image->r_width,
1256      height() - kDropShadowHeight - kToolbarOverlap);
1257 
1258   canvas->DrawImageInt(
1259       gfx::ImageSkia(background_canvas.ExtractImageRep()), 0, 0);
1260 
1261   if (!GetThemeProvider()->HasCustomImage(tab_id) &&
1262       hover_controller_.ShouldDraw()) {
1263     hover_controller_.Draw(canvas, gfx::ImageSkia(
1264         background_canvas.ExtractImageRep()));
1265   }
1266 
1267   // Now draw the highlights/shadows around the tab edge.
1268   canvas->DrawImageInt(*tab_inactive_image->image_l, 0, 0);
1269   canvas->TileImageInt(*tab_inactive_image->image_c,
1270                        tab_inactive_image->l_width, 0,
1271                        width() - tab_inactive_image->l_width -
1272                            tab_inactive_image->r_width,
1273                        height());
1274   canvas->DrawImageInt(*tab_inactive_image->image_r,
1275                        width() - tab_inactive_image->r_width, 0);
1276 }
1277 
PaintActiveTabBackground(gfx::Canvas * canvas)1278 void Tab::PaintActiveTabBackground(gfx::Canvas* canvas) {
1279   gfx::ImageSkia* tab_background =
1280       GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR);
1281   int offset = GetMirroredX() + background_offset_.x();
1282 
1283   TabImage* tab_image = &tab_active_;
1284   TabImage* alpha = &tab_alpha_;
1285 
1286   // Draw left edge.
1287   gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage(
1288       *tab_background, offset, 0, tab_image->l_width, height());
1289   gfx::ImageSkia theme_l =
1290       gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l);
1291   canvas->DrawImageInt(theme_l, 0, 0);
1292 
1293   // Draw right edge.
1294   gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(
1295       *tab_background,
1296       offset + width() - tab_image->r_width, 0, tab_image->r_width, height());
1297   gfx::ImageSkia theme_r =
1298       gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r);
1299   canvas->DrawImageInt(theme_r, width() - tab_image->r_width, 0);
1300 
1301   // Draw center.  Instead of masking out the top portion we simply skip over it
1302   // by incrementing by GetDropShadowHeight(), since it's a simple rectangle.
1303   canvas->TileImageInt(*tab_background,
1304      offset + tab_image->l_width,
1305      kDropShadowHeight,
1306      tab_image->l_width,
1307      kDropShadowHeight,
1308      width() - tab_image->l_width - tab_image->r_width,
1309      height() - kDropShadowHeight);
1310 
1311   // Now draw the highlights/shadows around the tab edge.
1312   canvas->DrawImageInt(*tab_image->image_l, 0, 0);
1313   canvas->TileImageInt(*tab_image->image_c, tab_image->l_width, 0,
1314       width() - tab_image->l_width - tab_image->r_width, height());
1315   canvas->DrawImageInt(*tab_image->image_r, width() - tab_image->r_width, 0);
1316 }
1317 
PaintIcon(gfx::Canvas * canvas)1318 void Tab::PaintIcon(gfx::Canvas* canvas) {
1319   gfx::Rect bounds = favicon_bounds_;
1320   if (bounds.IsEmpty())
1321     return;
1322 
1323   bounds.set_x(GetMirroredXForRect(bounds));
1324 
1325   if (data().network_state != TabRendererData::NETWORK_STATE_NONE) {
1326     // Paint network activity (aka throbber) animation frame.
1327     ui::ThemeProvider* tp = GetThemeProvider();
1328     gfx::ImageSkia frames(*tp->GetImageSkiaNamed(
1329         (data().network_state == TabRendererData::NETWORK_STATE_WAITING) ?
1330         IDR_THROBBER_WAITING : IDR_THROBBER));
1331 
1332     int icon_size = frames.height();
1333     int image_offset = loading_animation_frame_ * icon_size;
1334     DrawIconCenter(canvas, frames, image_offset,
1335                    icon_size, icon_size,
1336                    bounds, false, SkPaint());
1337   } else if (should_display_crashed_favicon_) {
1338     // Paint crash favicon.
1339     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1340     gfx::ImageSkia crashed_favicon(*rb.GetImageSkiaNamed(IDR_SAD_FAVICON));
1341     bounds.set_y(bounds.y() + favicon_hiding_offset_);
1342     DrawIconCenter(canvas, crashed_favicon, 0,
1343                    crashed_favicon.width(),
1344                    crashed_favicon.height(),
1345                    bounds, true, SkPaint());
1346   } else if (!data().favicon.isNull()) {
1347     // Paint the normal favicon.
1348     DrawIconCenter(canvas, data().favicon, 0,
1349                    data().favicon.width(),
1350                    data().favicon.height(),
1351                    bounds, true, SkPaint());
1352   }
1353 }
1354 
AdvanceLoadingAnimation(TabRendererData::NetworkState old_state,TabRendererData::NetworkState state)1355 void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state,
1356                                   TabRendererData::NetworkState state) {
1357   static bool initialized = false;
1358   static int loading_animation_frame_count = 0;
1359   static int waiting_animation_frame_count = 0;
1360   static int waiting_to_loading_frame_count_ratio = 0;
1361   if (!initialized) {
1362     initialized = true;
1363     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1364     gfx::ImageSkia loading_animation(*rb.GetImageSkiaNamed(IDR_THROBBER));
1365     loading_animation_frame_count =
1366         loading_animation.width() / loading_animation.height();
1367     gfx::ImageSkia waiting_animation(*rb.GetImageSkiaNamed(
1368         IDR_THROBBER_WAITING));
1369     waiting_animation_frame_count =
1370         waiting_animation.width() / waiting_animation.height();
1371     waiting_to_loading_frame_count_ratio =
1372         waiting_animation_frame_count / loading_animation_frame_count;
1373 
1374     base::debug::Alias(&loading_animation_frame_count);
1375     base::debug::Alias(&waiting_animation_frame_count);
1376     CHECK_NE(0, waiting_to_loading_frame_count_ratio) <<
1377         "Number of frames in IDR_THROBBER must be equal to or greater " <<
1378         "than the number of frames in IDR_THROBBER_WAITING. Please " <<
1379         "investigate how this happened and update http://crbug.com/132590, " <<
1380         "this is causing crashes in the wild.";
1381   }
1382 
1383   // The waiting animation is the reverse of the loading animation, but at a
1384   // different rate - the following reverses and scales the animation_frame_
1385   // so that the frame is at an equivalent position when going from one
1386   // animation to the other.
1387   if (state != old_state) {
1388     loading_animation_frame_ = loading_animation_frame_count -
1389         (loading_animation_frame_ / waiting_to_loading_frame_count_ratio);
1390   }
1391 
1392   if (state == TabRendererData::NETWORK_STATE_WAITING) {
1393     loading_animation_frame_ = (loading_animation_frame_ + 1) %
1394         waiting_animation_frame_count;
1395     // Waiting steps backwards.
1396     immersive_loading_step_ =
1397         (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) %
1398             kImmersiveLoadingStepCount;
1399   } else if (state == TabRendererData::NETWORK_STATE_LOADING) {
1400     loading_animation_frame_ = (loading_animation_frame_ + 1) %
1401         loading_animation_frame_count;
1402     immersive_loading_step_ = (immersive_loading_step_ + 1) %
1403         kImmersiveLoadingStepCount;
1404   } else {
1405     loading_animation_frame_ = 0;
1406     immersive_loading_step_ = 0;
1407   }
1408   if (controller_->IsImmersiveStyle())
1409     SchedulePaintInRect(GetImmersiveBarRect());
1410   else
1411     ScheduleIconPaint();
1412 }
1413 
IconCapacity() const1414 int Tab::IconCapacity() const {
1415   if (height() < GetMinimumUnselectedSize().height())
1416     return 0;
1417   const int available_width =
1418       std::max(0, width() - kLeftPadding - kRightPadding);
1419   const int width_per_icon = gfx::kFaviconSize;
1420   const int kPaddingBetweenIcons = 2;
1421   if (available_width >= width_per_icon &&
1422       available_width < (width_per_icon + kPaddingBetweenIcons)) {
1423     return 1;
1424   }
1425   return available_width / (width_per_icon + kPaddingBetweenIcons);
1426 }
1427 
ShouldShowIcon() const1428 bool Tab::ShouldShowIcon() const {
1429   return chrome::ShouldTabShowFavicon(
1430       IconCapacity(), data().mini, IsActive(), data().show_icon,
1431       media_indicator_button_ ? media_indicator_button_->showing_media_state() :
1432                                 data_.media_state);
1433 }
1434 
ShouldShowMediaIndicator() const1435 bool Tab::ShouldShowMediaIndicator() const {
1436   return chrome::ShouldTabShowMediaIndicator(
1437       IconCapacity(), data().mini, IsActive(), data().show_icon,
1438       media_indicator_button_ ? media_indicator_button_->showing_media_state() :
1439                                 data_.media_state);
1440 }
1441 
ShouldShowCloseBox() const1442 bool Tab::ShouldShowCloseBox() const {
1443   return chrome::ShouldTabShowCloseButton(
1444       IconCapacity(), data().mini, IsActive());
1445 }
1446 
GetThrobValue()1447 double Tab::GetThrobValue() {
1448   const bool is_selected = IsSelected();
1449   const double min = is_selected ? kSelectedTabOpacity : 0;
1450   const double scale = is_selected ? kSelectedTabThrobScale : 1;
1451 
1452   // Showing both the pulse and title change animation at the same time is too
1453   // much.
1454   if (pulse_animation_ && pulse_animation_->is_animating() &&
1455       (!mini_title_change_animation_ ||
1456        !mini_title_change_animation_->is_animating())) {
1457     return pulse_animation_->GetCurrentValue() * kHoverOpacity * scale + min;
1458   }
1459 
1460   if (hover_controller_.ShouldDraw()) {
1461     return kHoverOpacity * hover_controller_.GetAnimationValue() * scale +
1462         min;
1463   }
1464 
1465   return is_selected ? kSelectedTabOpacity : 0;
1466 }
1467 
SetFaviconHidingOffset(int offset)1468 void Tab::SetFaviconHidingOffset(int offset) {
1469   favicon_hiding_offset_ = offset;
1470   ScheduleIconPaint();
1471 }
1472 
DisplayCrashedFavicon()1473 void Tab::DisplayCrashedFavicon() {
1474   should_display_crashed_favicon_ = true;
1475 }
1476 
ResetCrashedFavicon()1477 void Tab::ResetCrashedFavicon() {
1478   should_display_crashed_favicon_ = false;
1479 }
1480 
StopCrashAnimation()1481 void Tab::StopCrashAnimation() {
1482   crash_icon_animation_.reset();
1483 }
1484 
StartCrashAnimation()1485 void Tab::StartCrashAnimation() {
1486   crash_icon_animation_.reset(new FaviconCrashAnimation(this));
1487   crash_icon_animation_->Start();
1488 }
1489 
IsPerformingCrashAnimation() const1490 bool Tab::IsPerformingCrashAnimation() const {
1491   return crash_icon_animation_.get() && data_.IsCrashed();
1492 }
1493 
ScheduleIconPaint()1494 void Tab::ScheduleIconPaint() {
1495   gfx::Rect bounds = favicon_bounds_;
1496   if (bounds.IsEmpty())
1497     return;
1498 
1499   // Extends the area to the bottom when sad_favicon is animating.
1500   if (IsPerformingCrashAnimation())
1501     bounds.set_height(height() - bounds.y());
1502   bounds.set_x(GetMirroredXForRect(bounds));
1503   SchedulePaintInRect(bounds);
1504 }
1505 
GetImmersiveBarRect() const1506 gfx::Rect Tab::GetImmersiveBarRect() const {
1507   // The main bar is as wide as the normal tab's horizontal top line.
1508   // This top line of the tab extends a few pixels left and right of the
1509   // center image due to pixels in the rounded corner images.
1510   const int kBarPadding = 1;
1511   int main_bar_left = tab_active_.l_width - kBarPadding;
1512   int main_bar_right = width() - tab_active_.r_width + kBarPadding;
1513   return gfx::Rect(
1514       main_bar_left, 0, main_bar_right - main_bar_left, kImmersiveBarHeight);
1515 }
1516 
GetTabIdAndFrameId(views::Widget * widget,int * tab_id,int * frame_id) const1517 void Tab::GetTabIdAndFrameId(views::Widget* widget,
1518                              int* tab_id,
1519                              int* frame_id) const {
1520   if (widget &&
1521       widget->GetTopLevelWidget()->ShouldWindowContentsBeTransparent()) {
1522     *tab_id = IDR_THEME_TAB_BACKGROUND_V;
1523     *frame_id = 0;
1524   } else if (data().incognito) {
1525     *tab_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
1526     *frame_id = IDR_THEME_FRAME_INCOGNITO;
1527   } else {
1528     *tab_id = IDR_THEME_TAB_BACKGROUND;
1529     *frame_id = IDR_THEME_FRAME;
1530   }
1531 }
1532 
GetMediaIndicatorButton()1533 MediaIndicatorButton* Tab::GetMediaIndicatorButton() {
1534   if (!media_indicator_button_) {
1535     media_indicator_button_ = new MediaIndicatorButton();
1536     AddChildView(media_indicator_button_);  // Takes ownership.
1537   }
1538   return media_indicator_button_;
1539 }
1540 
1541 ////////////////////////////////////////////////////////////////////////////////
1542 // Tab, private static:
1543 
1544 // static
InitTabResources()1545 void Tab::InitTabResources() {
1546   static bool initialized = false;
1547   if (initialized)
1548     return;
1549 
1550   initialized = true;
1551   image_cache_ = new ImageCache();
1552 
1553   // Load the tab images once now, and maybe again later if the theme changes.
1554   LoadTabImages();
1555 }
1556 
1557 // static
LoadTabImages()1558 void Tab::LoadTabImages() {
1559   // We're not letting people override tab images just yet.
1560   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1561 
1562   tab_alpha_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT);
1563   tab_alpha_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT);
1564 
1565   tab_active_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT);
1566   tab_active_.image_c = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER);
1567   tab_active_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT);
1568   tab_active_.l_width = tab_active_.image_l->width();
1569   tab_active_.r_width = tab_active_.image_r->width();
1570 
1571   tab_inactive_.image_l = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT);
1572   tab_inactive_.image_c = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER);
1573   tab_inactive_.image_r = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT);
1574   tab_inactive_.l_width = tab_inactive_.image_l->width();
1575   tab_inactive_.r_width = tab_inactive_.image_r->width();
1576 }
1577 
1578 // static
GetCachedImage(int resource_id,const gfx::Size & size,ui::ScaleFactor scale_factor)1579 gfx::ImageSkia Tab::GetCachedImage(int resource_id,
1580                                    const gfx::Size& size,
1581                                    ui::ScaleFactor scale_factor) {
1582   for (ImageCache::const_iterator i = image_cache_->begin();
1583        i != image_cache_->end(); ++i) {
1584     if (i->resource_id == resource_id && i->scale_factor == scale_factor &&
1585         i->image.size() == size) {
1586       return i->image;
1587     }
1588   }
1589   return gfx::ImageSkia();
1590 }
1591 
1592 // static
SetCachedImage(int resource_id,ui::ScaleFactor scale_factor,const gfx::ImageSkia & image)1593 void Tab::SetCachedImage(int resource_id,
1594                          ui::ScaleFactor scale_factor,
1595                          const gfx::ImageSkia& image) {
1596   DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE);
1597   ImageCacheEntry entry;
1598   entry.resource_id = resource_id;
1599   entry.scale_factor = scale_factor;
1600   entry.image = image;
1601   image_cache_->push_front(entry);
1602   if (image_cache_->size() > kMaxImageCacheSize)
1603     image_cache_->pop_back();
1604 }
1605