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