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