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