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