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