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