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