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