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