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