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