blob: d83f7e40819805e87dcd6cd376407d6f0a285edf [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
#import <QuartzCore/QuartzCore.h>
#include <algorithm>
#include <cmath>
#include "base/i18n/rtl.h"
#include "base/mac/bundle_locations.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/themes/theme_properties.h"
#import "chrome/browser/themes/theme_service.h"
#include "chrome/browser/ui/cocoa/l10n_util.h"
#import "chrome/browser/ui/cocoa/sprite_view.h"
#import "chrome/browser/ui/cocoa/tabs/alert_indicator_button_cocoa.h"
#import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h"
#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
#import "chrome/browser/ui/cocoa/tabs/tab_view.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#include "chrome/grit/theme_resources.h"
#include "components/grit/components_scaled_resources.h"
#import "extensions/common/extension.h"
#include "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/favicon_size.h"
#include "ui/native_theme/native_theme.h"
#include "ui/resources/grit/ui_resources.h"
namespace {
// A C++ delegate that handles enabling/disabling menu items and handling when
// a menu command is chosen. Also fixes up the menu item label for "pin/unpin
// tab".
class MenuDelegate : public ui::SimpleMenuModel::Delegate {
public:
explicit MenuDelegate(id<TabControllerTarget> target, TabController* owner)
: target_(target),
owner_(owner) {}
// Overridden from ui::SimpleMenuModel::Delegate
bool IsCommandIdChecked(int command_id) const override { return false; }
bool IsCommandIdEnabled(int command_id) const override {
TabStripModel::ContextMenuCommand command =
static_cast<TabStripModel::ContextMenuCommand>(command_id);
return [target_ isCommandEnabled:command forController:owner_];
}
void ExecuteCommand(int command_id, int event_flags) override {
TabStripModel::ContextMenuCommand command =
static_cast<TabStripModel::ContextMenuCommand>(command_id);
[target_ commandDispatch:command forController:owner_];
}
private:
id<TabControllerTarget> target_; // weak
TabController* owner_; // weak, owns me
};
} // namespace
@interface TabController () {
base::scoped_nsobject<SpriteView> iconView_;
base::scoped_nsobject<NSImage> icon_;
base::scoped_nsobject<NSView> attentionDotView_;
base::scoped_nsobject<AlertIndicatorButton> alertIndicatorButton_;
base::scoped_nsobject<HoverCloseButton> closeButton_;
BOOL active_;
BOOL selected_;
std::unique_ptr<ui::SimpleMenuModel> contextMenuModel_;
std::unique_ptr<MenuDelegate> contextMenuDelegate_;
base::scoped_nsobject<MenuControllerCocoa> contextMenuController_;
enum AttentionType : int {
kPinnedTabTitleChange = 1 << 0, // The title of a pinned tab changed.
kBlockedWebContents = 1 << 1, // The WebContents is marked as blocked.
kTabWantsAttentionStatus = 1 << 2, // SetTabNeedsAttention() was called.
};
}
@property(nonatomic) int currentAttentionTypes; // Bitmask of AttentionType.
// Recomputes the iconView's frame and updates it with or without animation.
- (void)updateIconViewFrameWithAnimation:(BOOL)shouldAnimate;
@end
@implementation TabController
@synthesize action = action_;
@synthesize currentAttentionTypes = currentAttentionTypes_;
@synthesize loadingState = loadingState_;
@synthesize showIcon = showIcon_;
@synthesize pinned = pinned_;
@synthesize target = target_;
@synthesize url = url_;
namespace {
static const CGFloat kTabLeadingPadding = 18;
static const CGFloat kTabTrailingPadding = 15;
static const CGFloat kCloseButtonSize = 16;
static const CGFloat kInitialTabWidth = 160;
static const CGFloat kTitleLeadingPadding = 4;
static const CGFloat kInitialTitleWidth = 92;
static const CGFloat kTitleHeight = 17;
static const CGFloat kTabElementYOrigin = 6;
static const CGFloat kDefaultTabHeight = 29;
static const CGFloat kPinnedTabWidth = kDefaultTabHeight * 2;
} // namespace
+ (CGFloat)defaultTabHeight {
return kDefaultTabHeight;
}
// The min widths is the smallest number at which the right edge of the right
// tab border image is not visibly clipped. It is a bit smaller than the sum
// of the two tab edge bitmaps because these bitmaps have a few transparent
// pixels on the side. The selected tab width includes the close button width.
+ (CGFloat)minTabWidth { return 36; }
+ (CGFloat)minActiveTabWidth { return 52; }
+ (CGFloat)maxTabWidth { return 246; }
+ (CGFloat)pinnedTabWidth {
return kPinnedTabWidth;
}
- (TabView*)tabView {
DCHECK([[self view] isKindOfClass:[TabView class]]);
return static_cast<TabView*>([self view]);
}
- (id)init {
if ((self = [super init])) {
BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
// Create the close button.
const CGFloat closeButtonXOrigin =
isRTL ? kTabTrailingPadding
: kInitialTabWidth - kCloseButtonSize - kTabTrailingPadding;
NSRect closeButtonFrame = NSMakeRect(closeButtonXOrigin, kTabElementYOrigin,
kCloseButtonSize, kCloseButtonSize);
closeButton_.reset(
[[HoverCloseButton alloc] initWithFrame:closeButtonFrame]);
[closeButton_
setAutoresizingMask:isRTL ? NSViewMaxXMargin : NSViewMinXMargin];
[closeButton_ setTarget:self];
[closeButton_ setAction:@selector(closeTab:)];
// Create the TabView. The TabView works directly with the closeButton so
// here (the TabView handles adding it as a subview).
base::scoped_nsobject<TabView> tabView([[TabView alloc]
initWithFrame:NSMakeRect(0, 0, kInitialTabWidth,
[TabController defaultTabHeight])
controller:self
closeButton:closeButton_]);
[tabView setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
[tabView setPostsFrameChangedNotifications:NO];
[tabView setPostsBoundsChangedNotifications:NO];
[super setView:tabView];
// Add the favicon view.
NSRect iconViewFrame =
NSMakeRect(0, kTabElementYOrigin, gfx::kFaviconSize, gfx::kFaviconSize);
iconView_.reset([[SpriteView alloc] initWithFrame:iconViewFrame]);
[iconView_ setAutoresizingMask:isRTL ? NSViewMinXMargin | NSViewMinYMargin
: NSViewMaxXMargin | NSViewMinYMargin];
[self updateIconViewFrameWithAnimation:NO];
[tabView addSubview:iconView_];
// Set up the title.
const CGFloat titleXOrigin =
isRTL ? NSMinX([iconView_ frame]) - kTitleLeadingPadding -
kInitialTitleWidth
: NSMaxX([iconView_ frame]) + kTitleLeadingPadding;
NSRect titleFrame = NSMakeRect(titleXOrigin, kTabElementYOrigin,
kInitialTitleWidth, kTitleHeight);
[tabView setTitleFrame:titleFrame];
NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:@selector(themeChangedNotification:)
name:kBrowserThemeDidChangeNotification
object:nil];
[self internalSetSelected:selected_];
}
return self;
}
- (void)dealloc {
[alertIndicatorButton_ setAnimationDoneTarget:nil withAction:nil];
[alertIndicatorButton_ setClickTarget:nil withAction:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[self tabView] setController:nil];
[super dealloc];
}
// The internals of |-setSelected:| and |-setActive:| but doesn't set the
// backing variables. This updates the drawing state and marks self as needing
// a re-draw.
- (void)internalSetSelected:(BOOL)selected {
TabView* tabView = [self tabView];
if ([self active]) {
[tabView setState:NSOnState];
self.currentAttentionTypes &= ~AttentionType::kPinnedTabTitleChange;
} else {
[tabView setState:selected ? NSMixedState : NSOffState];
}
// The attention indicator must always be updated, as it needs to disappear
// if a tab is blocked and is brought forward. It is updated at the end of
// -updateVisibility.
[self updateVisibility];
[self updateTitleColor];
}
// Called when Cocoa wants to display the context menu. Lazily instantiate
// the menu based off of the cross-platform model. Re-create the menu and
// model every time to get the correct labels and enabling.
- (NSMenu*)menu {
// If the menu is currently open, then this method is being called from
// the nested runloop of the menu. This can happen when an accessibility
// message is sent to retrieve the menu options. Do not delete the objects
// associated with a running menu, which could lead to a use-after-free,
// and instead just return the existing instance. https://crbug.com/778776
if ([contextMenuController_ isMenuOpen]) {
return [contextMenuController_ menu];
}
contextMenuDelegate_.reset(new MenuDelegate(target_, self));
contextMenuModel_.reset(
[target_ contextMenuModelForController:self
menuDelegate:contextMenuDelegate_.get()]);
contextMenuController_.reset([[MenuControllerCocoa alloc]
initWithModel:contextMenuModel_.get()
useWithPopUpButtonCell:NO]);
return [contextMenuController_ menu];
}
- (void)toggleMute:(id)sender {
if ([[self target] respondsToSelector:@selector(toggleMute:)]) {
[[self target] performSelector:@selector(toggleMute:)
withObject:[self view]];
}
}
- (void)closeTab:(id)sender {
using base::UserMetricsAction;
if (alertIndicatorButton_ && ![alertIndicatorButton_ isHidden]) {
if ([alertIndicatorButton_ isEnabled]) {
base::RecordAction(UserMetricsAction("CloseTab_MuteToggleAvailable"));
} else if ([alertIndicatorButton_ showingAlertState] ==
TabAlertState::AUDIO_PLAYING) {
base::RecordAction(UserMetricsAction("CloseTab_AudioIndicator"));
} else {
base::RecordAction(UserMetricsAction("CloseTab_RecordingIndicator"));
}
} else {
base::RecordAction(UserMetricsAction("CloseTab_NoAlertIndicator"));
}
if ([[self target] respondsToSelector:@selector(closeTab:)]) {
[[self target] performSelector:@selector(closeTab:)
withObject:[self view]];
}
}
- (void)selectTab:(id)sender {
if ([[self tabView] isClosing])
return;
if ([[self target] respondsToSelector:[self action]]) {
[[self target] performSelector:[self action]
withObject:[self view]];
}
}
- (void)setTitle:(NSString*)title {
if ([[self title] isEqualToString:title])
return;
TabView* tabView = [self tabView];
[tabView setTitle:title];
[super setTitle:title];
}
- (void)setActive:(BOOL)active {
if (active != active_) {
active_ = active;
[self internalSetSelected:[self selected]];
}
}
- (BOOL)active {
return active_;
}
- (void)setSelected:(BOOL)selected {
if (selected_ != selected) {
selected_ = selected;
[self internalSetSelected:[self selected]];
}
}
- (BOOL)selected {
return selected_ || active_;
}
- (void)setPinned:(BOOL)pinned {
if (pinned_ != pinned) {
pinned_ = pinned;
[self updateIconViewFrameWithAnimation:YES];
}
}
- (void)updateIconViewFrameWithAnimation:(BOOL)shouldAnimate {
static const CGFloat kPinnedTabLeadingPadding =
std::floor((kPinnedTabWidth - gfx::kFaviconSize) / 2.0);
BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
// Determine the padding between the iconView and the tab edge.
CGFloat leadingPadding =
[self pinned] ? kPinnedTabLeadingPadding : kTabLeadingPadding;
NSRect iconViewFrame = [iconView_ frame];
iconViewFrame.origin.x = isRTL ? NSWidth([[self tabView] frame]) -
leadingPadding - gfx::kFaviconSize
: leadingPadding;
// The iconView animation looks funky in RTL so don't allow it.
if (shouldAnimate && !isRTL) {
// Animate at the same rate as the tab changes shape.
[[NSAnimationContext currentContext]
setDuration:[TabStripController tabAnimationDuration]];
[[iconView_ animator] setFrame:iconViewFrame];
} else {
[iconView_ setFrame:iconViewFrame];
}
}
- (AlertIndicatorButton*)alertIndicatorButton {
return alertIndicatorButton_;
}
- (void)setAlertState:(TabAlertState)alertState {
if (!alertIndicatorButton_ && alertState != TabAlertState::NONE) {
alertIndicatorButton_.reset([[AlertIndicatorButton alloc] init]);
[self updateVisibility]; // Do layout and visibility before adding subview.
[[self view] addSubview:alertIndicatorButton_];
[alertIndicatorButton_ setAnimationDoneTarget:self
withAction:@selector(updateVisibility)];
[alertIndicatorButton_ setClickTarget:self
withAction:@selector(toggleMute:)];
}
[alertIndicatorButton_ transitionToAlertState:alertState];
}
- (BOOL)blocked {
return self.currentAttentionTypes & AttentionType::kBlockedWebContents ? YES
: NO;
}
- (void)setBlocked:(BOOL)blocked {
if (blocked)
self.currentAttentionTypes |= AttentionType::kBlockedWebContents;
else
self.currentAttentionTypes &= ~AttentionType::kBlockedWebContents;
}
- (void)titleChangedNotLoading {
if ([self pinned] && ![self active])
self.currentAttentionTypes |= AttentionType::kPinnedTabTitleChange;
}
- (void)setNeedsAttention:(bool)attention {
if (attention)
self.currentAttentionTypes |= AttentionType::kTabWantsAttentionStatus;
else
self.currentAttentionTypes &= ~AttentionType::kTabWantsAttentionStatus;
}
- (void)setCurrentAttentionTypes:(int)attentionTypes {
if (currentAttentionTypes_ == attentionTypes)
return;
currentAttentionTypes_ = attentionTypes;
[self updateAttentionIndicator];
}
- (HoverCloseButton*)closeButton {
return closeButton_;
}
- (NSString*)toolTip {
return [[self tabView] toolTipText];
}
- (void)setToolTip:(NSString*)toolTip {
[[self tabView] setToolTipText:toolTip];
}
- (NSView*)iconView {
return iconView_;
}
// Return a rough approximation of the number of icons we could fit in the
// tab. We never actually do this, but it's a helpful guide for determining
// how much space we have available.
- (int)iconCapacity {
const CGFloat availableWidth =
std::max<CGFloat>(0, NSWidth([[self tabView] frame]) -
kTabLeadingPadding - kTabTrailingPadding);
const CGFloat widthPerIcon = gfx::kFaviconSize;
const int kPaddingBetweenIcons = 2;
if (availableWidth >= widthPerIcon &&
availableWidth < (widthPerIcon + kPaddingBetweenIcons)) {
return 1;
}
return availableWidth / (widthPerIcon + kPaddingBetweenIcons);
}
- (BOOL)shouldShowIcon {
return chrome::ShouldTabShowFavicon(
[self iconCapacity], [self pinned], [self active], [self showIcon],
!alertIndicatorButton_ ? TabAlertState::NONE
: [alertIndicatorButton_ showingAlertState]);
}
- (BOOL)shouldShowAlertIndicator {
return chrome::ShouldTabShowAlertIndicator(
[self iconCapacity], [self pinned], [self active], [self showIcon],
!alertIndicatorButton_ ? TabAlertState::NONE
: [alertIndicatorButton_ showingAlertState]);
}
- (BOOL)shouldShowCloseButton {
return chrome::ShouldTabShowCloseButton(
[self iconCapacity], [self pinned], [self active]);
}
- (void)setIconImage:(NSImage*)image
forLoadingState:(TabLoadingState)newLoadingState
showIcon:(BOOL)showIcon {
// Update the favicon's visbility state. Note that TabStripController calls
// -updateVisibility immediately after calling this method, so we don't need
// to act on a change in this state.
showIcon_ = showIcon;
// Always draw the favicon when the state is already kTabDone because the site
// may have sent an updated favicon.
if (newLoadingState == loadingState_ && newLoadingState != kTabDone) {
return;
}
loadingState_ = newLoadingState;
// The Material Design spinner handles sad tab icon display, etc. directly
// based on the loading state. Handle it here until the new spinner code
// lands.
if (newLoadingState == kTabCrashed) {
static NSImage* sadFaviconImage =
ui::ResourceBundle::GetSharedInstance()
.GetNativeImageNamed(IDR_CRASH_SAD_FAVICON)
.CopyNSImage();
image = sadFaviconImage;
} else if (newLoadingState == kTabWaiting) {
static NSImage* throbberWaitingImage =
ui::ResourceBundle::GetSharedInstance()
.GetNativeImageNamed(IDR_THROBBER_WAITING)
.CopyNSImage();
static NSImage* throbberWaitingIncognitoImage =
ui::ResourceBundle::GetSharedInstance()
.GetNativeImageNamed(IDR_THROBBER_WAITING_INCOGNITO)
.CopyNSImage();
if ([[iconView_ window] hasDarkTheme]) {
image = throbberWaitingIncognitoImage;
} else {
image = throbberWaitingImage;
}
} else if (newLoadingState == kTabLoading) {
static NSImage* throbberLoadingImage =
ui::ResourceBundle::GetSharedInstance()
.GetNativeImageNamed(IDR_THROBBER)
.CopyNSImage();
static NSImage* throbberLoadingIncognitoImage =
ui::ResourceBundle::GetSharedInstance()
.GetNativeImageNamed(IDR_THROBBER_INCOGNITO)
.CopyNSImage();
if ([[iconView_ window] hasDarkTheme]) {
image = throbberLoadingIncognitoImage;
} else {
image = throbberLoadingImage;
}
}
[iconView_ setImage:image
withToastAnimation:(newLoadingState == kTabCrashed)];
}
- (void)updateAttentionIndicator {
// Don't show the attention indicator for blocked WebContentses if the tab is
// active; it's distracting.
int actualAttentionTypes = self.currentAttentionTypes;
if ([self active])
actualAttentionTypes &= ~AttentionType::kBlockedWebContents;
if (actualAttentionTypes != 0 && ![iconView_ isHidden]) {
// The attention indicator consists of two parts:
// . a wedge cut out of the bottom right (or left in rtl) of the favicon.
// . a circle in the bottom right (or left in rtl) of the favicon.
//
// The favicon lives in a view to itself, a view which is too small to
// contain the dot (the second part of the indicator), so the dot is added
// as a separate subview.
BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
CGRect iconViewBounds = iconView_.get().layer.bounds;
CGPoint indicatorCenter = CGPointMake(
isRTL ? CGRectGetMinX(iconViewBounds) : CGRectGetMaxX(iconViewBounds),
CGRectGetMinY(iconViewBounds));
const CGFloat kIndicatorCropRadius = 4.5;
CGRect cropCircleBounds = CGRectZero;
cropCircleBounds.origin = indicatorCenter;
cropCircleBounds = CGRectInset(cropCircleBounds, -kIndicatorCropRadius,
-kIndicatorCropRadius);
base::ScopedCFTypeRef<CGMutablePathRef> maskPath(CGPathCreateMutable());
CGPathAddRect(maskPath, nil, iconViewBounds);
CGPathAddEllipseInRect(maskPath, nil, cropCircleBounds);
CAShapeLayer* maskLayer = [CAShapeLayer layer];
maskLayer.frame = iconViewBounds;
maskLayer.path = maskPath.get();
maskLayer.fillRule = kCAFillRuleEvenOdd;
iconView_.get().layer.mask = maskLayer;
if (!attentionDotView_) {
NSRect iconViewFrame = [iconView_ frame];
NSPoint indicatorCenter =
NSMakePoint(isRTL ? NSMinX(iconViewFrame) : NSMaxX(iconViewFrame),
NSMinY(iconViewFrame));
const float kIndicatorRadius = 3.0f;
NSRect indicatorCircleFrame = NSZeroRect;
indicatorCircleFrame.origin = indicatorCenter;
indicatorCircleFrame = NSInsetRect(indicatorCircleFrame,
-kIndicatorRadius, -kIndicatorRadius);
attentionDotView_.reset(
[[NSView alloc] initWithFrame:indicatorCircleFrame]);
attentionDotView_.get().wantsLayer = YES;
SkColor indicatorColor =
ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
ui::NativeTheme::kColorId_ProminentButtonColor);
attentionDotView_.get().layer.backgroundColor =
skia::SkColorToSRGBNSColor(indicatorColor).CGColor;
attentionDotView_.get().layer.cornerRadius = kIndicatorRadius;
[[self view] addSubview:attentionDotView_];
}
} else {
iconView_.get().layer.mask = nil;
[attentionDotView_ removeFromSuperview];
attentionDotView_.reset();
}
}
- (void)updateVisibility {
BOOL newShowIcon = [self shouldShowIcon];
[iconView_ setHidden:!newShowIcon];
// If the tab is a pinned-tab, hide the title.
TabView* tabView = [self tabView];
[tabView setTitleHidden:[self pinned]];
BOOL newShowCloseButton = [self shouldShowCloseButton];
[closeButton_ setHidden:!newShowCloseButton];
BOOL newShowAlertIndicator = [self shouldShowAlertIndicator];
[alertIndicatorButton_ setHidden:!newShowAlertIndicator];
BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
if (newShowAlertIndicator) {
NSRect newFrame = [alertIndicatorButton_ frame];
newFrame.size = [[alertIndicatorButton_ image] size];
if ([self pinned]) {
// Tab is pinned: Position the alert indicator in the center.
const CGFloat tabWidth = [TabController pinnedTabWidth];
newFrame.origin.x = std::floor((tabWidth - NSWidth(newFrame)) / 2);
newFrame.origin.y =
kTabElementYOrigin -
std::floor((NSHeight(newFrame) - gfx::kFaviconSize) / 2);
} else {
// The Frame for the alertIndicatorButton_ depends on whether iconView_
// and/or closeButton_ are visible, and where they have been positioned.
const NSRect closeButtonFrame = [closeButton_ frame];
newFrame.origin.x = NSMinX(closeButtonFrame);
// Position before the close button when it is showing.
if (newShowCloseButton)
newFrame.origin.x += isRTL ? NSWidth(newFrame) : -NSWidth(newFrame);
// Alert indicator is centered vertically, with respect to closeButton_.
newFrame.origin.y = NSMinY(closeButtonFrame) -
std::floor((NSHeight(newFrame) - NSHeight(closeButtonFrame)) / 2);
}
[alertIndicatorButton_ setFrame:newFrame];
[alertIndicatorButton_ updateEnabledForMuteToggle];
}
// Adjust the title view based on changes to the icon's and close button's
// visibility.
NSRect oldTitleFrame = [tabView titleFrame];
NSRect newTitleFrame;
newTitleFrame.size.height = oldTitleFrame.size.height;
newTitleFrame.origin.y = oldTitleFrame.origin.y;
CGFloat titleLeft, titleRight;
if (isRTL) {
if (newShowAlertIndicator) {
titleLeft = NSMaxX([alertIndicatorButton_ frame]);
} else if (newShowCloseButton) {
titleLeft = NSMaxX([closeButton_ frame]);
} else {
titleLeft = kTabLeadingPadding;
}
titleRight = newShowIcon
? NSMinX([iconView_ frame]) - kTitleLeadingPadding
: NSWidth([[self tabView] frame]) - kTabLeadingPadding;
} else {
titleLeft = newShowIcon ? NSMaxX([iconView_ frame]) + kTitleLeadingPadding
: kTabLeadingPadding;
if (newShowAlertIndicator) {
titleRight = NSMinX([alertIndicatorButton_ frame]);
} else if (newShowCloseButton) {
titleRight = NSMinX([closeButton_ frame]);
} else {
titleRight = NSWidth([[self tabView] frame]) - kTabTrailingPadding;
}
}
newTitleFrame.size.width = titleRight - titleLeft;
newTitleFrame.origin.x = titleLeft;
[tabView setTitleFrame:newTitleFrame];
[self updateAttentionIndicator];
}
- (void)updateTitleColor {
NSColor* titleColor = nil;
const ui::ThemeProvider* theme = [[[self view] window] themeProvider];
if (theme && ![self selected])
titleColor = theme->GetNSColor(ThemeProperties::COLOR_BACKGROUND_TAB_TEXT);
// Default to the selected text color unless told otherwise.
if (theme && !titleColor)
titleColor = theme->GetNSColor(ThemeProperties::COLOR_TAB_TEXT);
[[self tabView] setTitleColor:titleColor ? titleColor : [NSColor textColor]];
}
- (NSString*)accessibilityTitle {
// TODO(ellyjones): the Cocoa tab strip code doesn't keep track of network
// error state, so it can't get surfaced here. It should, and then this could
// pass in the network error state.
return base::SysUTF16ToNSString(chrome::AssembleTabAccessibilityLabel(
base::SysNSStringToUTF16([self title]),
[self loadingState] == kTabCrashed, false,
[[self alertIndicatorButton] showingAlertState]));
}
- (void)themeChangedNotification:(NSNotification*)notification {
[self updateTitleColor];
}
// Called by the tabs to determine whether we are in rapid (tab) closure mode.
- (BOOL)inRapidClosureMode {
if ([[self target] respondsToSelector:@selector(inRapidClosureMode)]) {
return [[self target] performSelector:@selector(inRapidClosureMode)] ?
YES : NO;
}
return NO;
}
- (void)maybeStartDrag:(NSEvent*)event forTab:(TabController*)tab {
[[target_ dragController] maybeStartDrag:event forTab:tab];
}
- (void)performClick:(id)sender {
[self selectTab:self];
}
@end