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