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