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