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