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