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