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