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