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