chromium/ios/chrome/browser/ui/tab_switcher/tab_strip/ui/tab_strip_tab_cell.mm

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/tab_switcher/tab_strip/ui/tab_strip_tab_cell.h"

#import <MaterialComponents/MaterialActivityIndicator.h>

#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/notreached.h"
#import "base/strings/string_number_conversions.h"
#import "ios/chrome/browser/shared/ui/elements/extended_touch_target_button.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/image/image_util.h"
#import "ios/chrome/browser/shared/ui/util/rtl_geometry.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/ui/swift_constants_for_objective_c.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/ui/tab_strip_features_utils.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/ui/tab_strip_group_stroke_view.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/ui/tab_strip_utils.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/elements/gradient_view.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/raccoon/raccoon_api.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

// The size of the close button.
const CGFloat kCloseButtonSize = 16;
const CGFloat kCloseButtonMinimumTouchTarget = 36;

// Size of the decoration corner and corner radius when the cell is selected.
const CGFloat kCornerSize = 16;

// Threshold width for collapsing the cell and hiding the close button.
const CGFloat kCollapsedWidthThreshold = 150;

// Separator constraints.
const CGFloat kSeparatorHorizontalInset = 2;
const CGFloat kSeparatorGradientWidth = 4;

// Content view constants.
const CGFloat kFaviconLeadingMargin = 10;
const CGFloat kCloseButtonMargin = 10;
const CGFloat kTitleInset = 10;
const CGFloat kFaviconSize = 16;
const CGFloat kTitleGradientWidth = 16;
const CGFloat kContentViewBottomInset = 4;

// Selected border background view constants.
const CGFloat kSelectedBorderBackgroundViewWidth = 8;

// Returns the default favicon image.
UIImage* DefaultFavicon() {
  return DefaultSymbolWithPointSize(kGlobeAmericasSymbol, 14);
}

}  // namespace

@implementation TabStripTabCell {
  // Content view subviews.
  UIButton* _closeButton;
  UIView* _titleContainer;
  UILabel* _titleLabel;
  GradientView* _titleGradientView;
  UIImageView* _faviconView;

  // Group stroke views and constraints.
  TabStripGroupStrokeView* _groupStrokeView;
  NSLayoutConstraint* _groupStrokeViewWidthConstraint;

  // Decoration views, visible when the cell is selected.
  UIView* _leftTailView;
  UIView* _rightTailView;
  UIView* _bottomTailView;

  // Cell separator.
  UIView* _leadingSeparatorView;
  UIView* _trailingSeparatorView;
  UIView* _leadingSeparatorGradientView;
  UIView* _trailingSeparatorGradientView;

  // Background views displayed when the selected cell in on an edge.
  UIView* _leadingSelectedBorderBackgroundView;
  UIView* _trailingSelectedBorderBackgroundView;

  // Wether the decoration layers have been updated.
  BOOL _decorationLayersUpdated;

  // Circular spinner that shows the loading state of the tab.
  MDCActivityIndicator* _activityIndicator;

  // Title container's trailing constraints.
  NSLayoutConstraint* _titleContainerCollapsedTrailingConstraint;
  NSLayoutConstraint* _titleContainerTrailingConstraint;
  // Title label's alignment constraints.
  NSLayoutConstraint* _titleLabelLeadingConstraint;
  NSLayoutConstraint* _titleLabelTrailingConstraint;

  // Gradient view's constraints.
  NSLayoutConstraint* _titleGradientViewLeadingConstraint;
  NSLayoutConstraint* _titleGradientViewTrailingConstraint;

  // Stroke view's constraints.
  NSLayoutConstraint* _groupStrokeViewBottomConstraint;
  NSLayoutConstraint* _groupStrokeViewBottomSelectedConstraint;

  // Separator height constraints.
  NSArray<NSLayoutConstraint*>* _separatorHeightConstraints;
  CGFloat _separatorHeight;

  // whether the view is hovered.
  BOOL _hovered;

  // View used to provide accessibility labels/values while letting the close
  // button selectable by VoiceOver.
  UIView* _accessibilityContainerView;
}

- (instancetype)initWithFrame:(CGRect)frame {
  if ((self = [super initWithFrame:frame])) {
    self.layer.masksToBounds = NO;
    _decorationLayersUpdated = NO;
    _hovered = NO;
    _separatorHeight = 0;

    [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];

    if (ios::provider::IsRaccoonEnabled()) {
      if (@available(iOS 17.0, *)) {
        self.hoverStyle = [UIHoverStyle
            styleWithShape:[UIShape rectShapeWithCornerRadius:kCornerSize]];
      }
    }

    UIView* contentView = self.contentView;
    contentView.layer.masksToBounds = YES;

    _accessibilityContainerView = [[UIView alloc] init];
    _accessibilityContainerView.isAccessibilityElement = YES;
    _accessibilityContainerView.translatesAutoresizingMaskIntoConstraints = NO;
    _accessibilityContainerView.layer.cornerRadius = kCornerSize;
    [contentView addSubview:_accessibilityContainerView];

    _faviconView = [self createFaviconView];
    [_accessibilityContainerView addSubview:_faviconView];

    _activityIndicator = [self createActivityIndicatior];
    [_accessibilityContainerView addSubview:_activityIndicator];

    _closeButton = [self createCloseButton];
    [contentView addSubview:_closeButton];

    _titleContainer = [self createTitleContainer];
    [_accessibilityContainerView addSubview:_titleContainer];

    _leadingSelectedBorderBackgroundView =
        [self createSelectedBorderBackgroundView];
    [self addSubview:_leadingSelectedBorderBackgroundView];

    _trailingSelectedBorderBackgroundView =
        [self createSelectedBorderBackgroundView];
    [self addSubview:_trailingSelectedBorderBackgroundView];

    _leftTailView = [self createDecorationView];
    [self addSubview:_leftTailView];

    _rightTailView = [self createDecorationView];
    [self addSubview:_rightTailView];

    _bottomTailView = [self createDecorationView];
    [self insertSubview:_bottomTailView belowSubview:contentView];

    _leadingSeparatorView = [self createSeparatorView];
    [self addSubview:_leadingSeparatorView];

    _trailingSeparatorView = [self createSeparatorView];
    [self addSubview:_trailingSeparatorView];

    _leadingSeparatorGradientView = [self createGradientView];
    [self addSubview:_leadingSeparatorGradientView];

    _trailingSeparatorGradientView = [self createGradientView];
    [self addSubview:_trailingSeparatorGradientView];

    _groupStrokeView = [[TabStripGroupStrokeView alloc] init];
    [self addSubview:_groupStrokeView];

    [self setupConstraints];
    [self setupDecorationLayers];
    [self updateGroupStroke];

    self.selected = NO;
  }
  return self;
}

- (void)setFaviconImage:(UIImage*)image {
  if (!image) {
    _faviconView.image = DefaultFavicon();
  } else {
    _faviconView.image = image;
  }
}

#pragma mark - TabStripCell

- (UIDragPreviewParameters*)dragPreviewParameters {
  UIBezierPath* visiblePath =
      [UIBezierPath bezierPathWithRoundedRect:_accessibilityContainerView.frame
                                 cornerRadius:kCornerSize];
  UIDragPreviewParameters* params = [[UIDragPreviewParameters alloc] init];
  params.visiblePath = visiblePath;
  return params;
}

#pragma mark - Setters

- (void)setTitle:(NSString*)title {
  [super setTitle:title];
  _accessibilityContainerView.accessibilityLabel = title;
  NSTextAlignment titleTextAligment = DetermineBestAlignmentForText(title);
  _titleLabel.text = [title copy];
  _titleLabel.textAlignment = titleTextAligment;
  [self updateTitleConstraints];
}

- (void)setGroupStrokeColor:(UIColor*)color {
  [super setGroupStrokeColor:color];
  if (_groupStrokeView.backgroundColor == color) {
    return;
  }
  _groupStrokeView.backgroundColor = color;
  [self updateGroupStroke];
}

- (void)setIsLastTabInGroup:(BOOL)isLastTabInGroup {
  if (_isLastTabInGroup == isLastTabInGroup) {
    return;
  }
  _isLastTabInGroup = isLastTabInGroup;
  [self updateGroupStroke];
}

- (void)setLoading:(BOOL)loading {
  if (_loading == loading) {
    return;
  }
  _loading = loading;
  if (loading) {
    _activityIndicator.hidden = NO;
    [_activityIndicator startAnimating];
    _faviconView.hidden = YES;
    _faviconView.image = DefaultFavicon();
  } else {
    _activityIndicator.hidden = YES;
    [_activityIndicator stopAnimating];
    _faviconView.hidden = NO;
  }
}

- (void)setLeadingSeparatorHidden:(BOOL)leadingSeparatorHidden {
  _leadingSeparatorHidden = leadingSeparatorHidden;
  _leadingSeparatorView.hidden = leadingSeparatorHidden;
}

- (void)setTrailingSeparatorHidden:(BOOL)trailingSeparatorHidden {
  _trailingSeparatorHidden = trailingSeparatorHidden;
  _trailingSeparatorView.hidden = trailingSeparatorHidden;
}

- (void)setLeadingSeparatorGradientViewHidden:
    (BOOL)leadingSeparatorGradientViewHidden {
  _leadingSeparatorGradientViewHidden = leadingSeparatorGradientViewHidden;
  _leadingSeparatorGradientView.hidden = leadingSeparatorGradientViewHidden;
}

- (void)setTrailingSeparatorGradientViewHidden:
    (BOOL)trailingSeparatorGradientViewHidden {
  _trailingSeparatorGradientViewHidden = trailingSeparatorGradientViewHidden;
  _trailingSeparatorGradientView.hidden = trailingSeparatorGradientViewHidden;
}

- (void)setLeadingSelectedBorderBackgroundViewHidden:
    (BOOL)leadingSelectedBorderBackgroundViewHidden {
  _leadingSelectedBorderBackgroundViewHidden =
      leadingSelectedBorderBackgroundViewHidden;
  _leadingSelectedBorderBackgroundView.hidden =
      leadingSelectedBorderBackgroundViewHidden;
}

- (void)setTrailingSelectedBorderBackgroundViewHidden:
    (BOOL)trailingSelectedBorderBackgroundViewHidden {
  _trailingSelectedBorderBackgroundViewHidden =
      trailingSelectedBorderBackgroundViewHidden;
  _trailingSelectedBorderBackgroundView.hidden =
      trailingSelectedBorderBackgroundViewHidden;
}

- (void)setSelected:(BOOL)selected {
  BOOL oldSelected = self.selected;
  [super setSelected:selected];

  if (selected) {
    _accessibilityContainerView.accessibilityTraits |=
        UIAccessibilityTraitSelected;
  } else {
    _accessibilityContainerView.accessibilityTraits &=
        ~UIAccessibilityTraitSelected;
  }

  if (selected) {
    /// The cell attributes is updated just after the cell selection.
    /// Hide separtors to avoid an animation glitch when selecting/inserting.
    _leadingSeparatorView.hidden = YES;
    _trailingSeparatorView.hidden = YES;
    _leadingSeparatorGradientView.hidden = YES;
    _trailingSeparatorGradientView.hidden = YES;
  }

  [self updateColors];

  // Make the selected cell on top of other cells.
  self.layer.zPosition = selected ? TabStripTabItemConstants.selectedZIndex : 0;

  // Update decoration views visibility.
  _leftTailView.hidden = !selected;
  _rightTailView.hidden = !selected;
  _bottomTailView.hidden = !selected;
  [self setLeadingSelectedBorderBackgroundViewHidden:YES];
  [self setTrailingSelectedBorderBackgroundViewHidden:YES];

  [self updateCollapsedState];
  if (oldSelected != self.selected) {
    [self updateGroupStroke];
  }
}

- (void)setSeparatorsHeight:(CGFloat)height {
  if (_separatorHeight == height) {
    return;
  }
  _separatorHeight = height;

  if (_separatorHeightConstraints) {
    [NSLayoutConstraint deactivateConstraints:_separatorHeightConstraints];
  }
  _separatorHeightConstraints = @[
    [_leadingSeparatorView.heightAnchor constraintEqualToConstant:height],
    [_trailingSeparatorView.heightAnchor constraintEqualToConstant:height],
  ];
  [NSLayoutConstraint activateConstraints:_separatorHeightConstraints];
}

- (void)setTabIndex:(NSInteger)tabIndex {
  if (_tabIndex == tabIndex) {
    return;
  }
  _tabIndex = tabIndex;
  [self updateAccessibilityValue];
}

- (void)setNumberOfTabs:(NSInteger)numberOfTabs {
  if (_numberOfTabs == numberOfTabs) {
    return;
  }
  _numberOfTabs = numberOfTabs;
  [self updateAccessibilityValue];
}

- (void)setIntersectsLeftEdge:(BOOL)intersectsLeftEdge {
  if (super.intersectsLeftEdge != intersectsLeftEdge) {
    super.intersectsLeftEdge = intersectsLeftEdge;
    [self updateGroupStroke];
  }
}

- (void)setIntersectsRightEdge:(BOOL)intersectsRightEdge {
  if (super.intersectsRightEdge != intersectsRightEdge) {
    super.intersectsRightEdge = intersectsRightEdge;
    [self updateGroupStroke];
  }
}

#pragma mark - UICollectionViewCell

- (void)applyLayoutAttributes:
    (UICollectionViewLayoutAttributes*)layoutAttributes {
  [super applyLayoutAttributes:layoutAttributes];

  [self updateCollapsedState];
}

- (void)prepareForReuse {
  [super prepareForReuse];
  self.selected = NO;
  [self setFaviconImage:nil];
  self.item = nil;
  self.numberOfTabs = 0;
  self.tabIndex = 0;
  _accessibilityContainerView.accessibilityValue = nil;
  self.loading = NO;
  self.leadingSeparatorHidden = NO;
  self.trailingSeparatorHidden = NO;
  self.leadingSeparatorGradientViewHidden = NO;
  self.trailingSeparatorGradientViewHidden = NO;
  self.leadingSelectedBorderBackgroundViewHidden = NO;
  self.trailingSelectedBorderBackgroundViewHidden = NO;
  self.isFirstTabInGroup = NO;
  self.isLastTabInGroup = NO;
}

- (void)setHighlighted:(BOOL)highlighted {
  [super setHighlighted:highlighted];
  [self updateColors];
}

- (void)dragStateDidChange:(UICollectionViewCellDragState)dragState {
  [super dragStateDidChange:dragState];
  [self updateColors];
}

#pragma mark - UITraitEnvironment

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
  [super traitCollectionDidChange:previousTraitCollection];
  [self updateColors];
}

#pragma mark - UIAccessibility

- (NSArray*)accessibilityCustomActions {
  return @[ [[UIAccessibilityCustomAction alloc]
      initWithName:l10n_util::GetNSString(IDS_IOS_TAB_SWITCHER_CLOSE_TAB)
            target:self
          selector:@selector(closeButtonTapped:)] ];
}

#pragma mark - UIPointerInteractionDelegate

- (UIPointerRegion*)pointerInteraction:(UIPointerInteraction*)interaction
                      regionForRequest:(UIPointerRegionRequest*)request
                         defaultRegion:(UIPointerRegion*)defaultRegion {
  return defaultRegion;
}

- (void)pointerInteraction:(UIPointerInteraction*)interaction
           willEnterRegion:(UIPointerRegion*)region
                  animator:(id<UIPointerInteractionAnimating>)animator {
  _hovered = YES;
  [self updateColors];
}

- (void)pointerInteraction:(UIPointerInteraction*)interaction
            willExitRegion:(UIPointerRegion*)region
                  animator:(id<UIPointerInteractionAnimating>)animator {
  _hovered = NO;
  [self updateColors];
}

#pragma mark - Private

/// Sets the decoration layers for the `selected` state of the cell.
- (void)setupDecorationLayers {
  // Bottom left corner path.
  UIBezierPath* cornerPath = [UIBezierPath bezierPath];
  [cornerPath moveToPoint:CGPointMake(kCornerSize, kCornerSize)];
  [cornerPath addLineToPoint:CGPointMake(kCornerSize, 0)];
  [cornerPath addArcWithCenter:CGPointMake(0, 0)
                        radius:kCornerSize
                    startAngle:0
                      endAngle:M_PI_2
                     clockwise:YES];
  [cornerPath closePath];

  // Round the left tail.
  CAShapeLayer* leftTailMaskLayer = [CAShapeLayer layer];
  leftTailMaskLayer.path = cornerPath.CGPath;
  _leftTailView.layer.mask = leftTailMaskLayer;

  // Round the right tail.
  CAShapeLayer* rightTailMaskLayer = [CAShapeLayer layer];
  rightTailMaskLayer.path = cornerPath.CGPath;
  _rightTailView.layer.mask = rightTailMaskLayer;
  _rightTailView.transform = CGAffineTransformMakeScale(-1, 1);

  // Setup and mirror separator gradient views if needed.
  _leadingSeparatorGradientView.hidden = YES;
  _trailingSeparatorGradientView.hidden = YES;
  if (UseRTLLayout()) {
    _trailingSeparatorGradientView.transform =
        CGAffineTransformMakeScale(-1, 1);

  } else {
    _leadingSeparatorGradientView.transform = CGAffineTransformMakeScale(-1, 1);
  }

  _decorationLayersUpdated = YES;
}

// Updates view colors.
- (void)updateColors {
  UIColor* backgroundColor;
  if (self.isHighlighted || self.configurationState.cellDragState !=
                                UICellConfigurationDragStateNone) {
    // Before a cell is dragged, it is highlighted.
    // The cell's background color must be updated at this moment, otherwise it
    // will not be applied correctly.
    backgroundColor = [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
  } else if (_hovered) {
    backgroundColor = [UIColor colorNamed:kUpdatedTertiaryBackgroundColor];
  } else {
    backgroundColor =
        self.isSelected ? [UIColor colorNamed:kGroupedSecondaryBackgroundColor]
                        : [TabStripHelper backgroundColor];
  }

  if ([TabStripFeaturesUtils isTabStripBlackBackgroundEnabled]) {
    if (self.isSelected) {
      self.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
    } else {
      self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
    }
  }

  // Needed to correctly update the `_titleGradientView` colors in incognito.
  backgroundColor =
      [backgroundColor resolvedColorWithTraitCollection:self.traitCollection];

  _accessibilityContainerView.backgroundColor = backgroundColor;
  _faviconView.tintColor = self.selected
                               ? [UIColor colorNamed:kCloseButtonColor]
                               : [UIColor colorNamed:kGrey500Color];
  [_titleGradientView setStartColor:[backgroundColor colorWithAlphaComponent:0]
                           endColor:backgroundColor];
}

// Hides the close button view if the cell is collapsed.
- (void)updateCollapsedState {
  BOOL collapsed = NO;
  if (self.frame.size.width < kCollapsedWidthThreshold) {
    // Don't hide the close button if the cell is selected.
    collapsed = !self.selected;
  }

  if (collapsed == _closeButton.hidden) {
    return;
  }

  _closeButton.hidden = collapsed;

  // To avoid breaking the layout, always disable the active constraint first.
  if (collapsed) {
    _titleContainerTrailingConstraint.active = NO;
    _titleContainerCollapsedTrailingConstraint.active = YES;
  } else {
    _titleContainerCollapsedTrailingConstraint.active = NO;
    _titleContainerTrailingConstraint.active = YES;
  }
}

// Updates the `_titleGradientView` horizontal constraints.
- (void)updateTitleConstraints {
  NSTextAlignment titleTextAligment = _titleLabel.textAlignment;

  // To avoid breaking the layout, always disable the active constraint first.
  if (UseRTLLayout()) {
    if (titleTextAligment == NSTextAlignmentLeft) {
      [_titleGradientView setTransform:CGAffineTransformMakeScale(1, 1)];
      _titleGradientViewTrailingConstraint.active = NO;
      _titleGradientViewLeadingConstraint.active = YES;
      _titleLabelLeadingConstraint.active = NO;
      _titleLabelTrailingConstraint.active = YES;
    } else {
      [_titleGradientView setTransform:CGAffineTransformMakeScale(-1, 1)];
      _titleGradientViewLeadingConstraint.active = NO;
      _titleGradientViewTrailingConstraint.active = YES;
      _titleLabelTrailingConstraint.active = NO;
      _titleLabelLeadingConstraint.active = YES;
    }
  } else {
    if (titleTextAligment == NSTextAlignmentLeft) {
      [_titleGradientView setTransform:CGAffineTransformMakeScale(1, 1)];
      _titleGradientViewLeadingConstraint.active = NO;
      _titleGradientViewTrailingConstraint.active = YES;
      _titleLabelTrailingConstraint.active = NO;
      _titleLabelLeadingConstraint.active = YES;
    } else {
      [_titleGradientView setTransform:CGAffineTransformMakeScale(-1, 1)];
      _titleGradientViewTrailingConstraint.active = NO;
      _titleGradientViewLeadingConstraint.active = YES;
      _titleLabelLeadingConstraint.active = NO;
      _titleLabelTrailingConstraint.active = YES;
    }
  }
}

// Updates the `_groupStrokeView` horizontal constraints.
- (void)updateGroupStroke {
  if (!_groupStrokeView.backgroundColor) {
    _groupStrokeView.hidden = YES;
    return;
  }
  _groupStrokeView.hidden = NO;

  const CGFloat lineWidth =
      TabStripCollectionViewConstants.groupStrokeLineWidth;
  if (self.selected) {
    _groupStrokeViewBottomConstraint.active = NO;
    _groupStrokeViewBottomSelectedConstraint.active = YES;
    _groupStrokeViewWidthConstraint.constant = -2 * kCornerSize;
  } else {
    _groupStrokeViewBottomSelectedConstraint.active = NO;
    _groupStrokeViewBottomConstraint.active = YES;
    _groupStrokeViewWidthConstraint.constant = -2 * lineWidth;
  }

  UIBezierPath* path = [UIBezierPath bezierPath];
  CGPoint leftPoint = CGPointZero;
  [path moveToPoint:leftPoint];
  if (self.selected) {
    leftPoint.y += kCornerSize + lineWidth / 2;
    [path addArcWithCenter:leftPoint
                    radius:kCornerSize + lineWidth / 2
                startAngle:M_PI + M_PI_2
                  endAngle:M_PI
                 clockwise:NO];
    leftPoint.x -= kCornerSize + lineWidth / 2;
    leftPoint.y += self.frame.size.height - kCornerSize * 2;
    [path addLineToPoint:leftPoint];
    leftPoint.x -= kCornerSize - lineWidth / 2;
    [path addArcWithCenter:leftPoint
                    radius:kCornerSize - lineWidth / 2
                startAngle:0
                  endAngle:M_PI_2
                 clockwise:YES];
    leftPoint.y += kCornerSize - lineWidth / 2;
    leftPoint.x -= lineWidth;
    [path addLineToPoint:leftPoint];
  }

  UIBezierPath* leftPath = [path copy];
  if (!self.selected) {
    leftPoint.x -= lineWidth;
    if (!self.intersectsRightEdge) {
      leftPoint.x -= TabStripTabItemConstants.horizontalSpacing;
      leftPoint.x -= lineWidth;
    }
    [leftPath addLineToPoint:leftPoint];
  }
  if (self.intersectsLeftEdge) {
    leftPoint.x -= TabStripCollectionViewConstants.groupStrokeExtension;
    [leftPath addLineToPoint:leftPoint];
  }
  leftPoint.y += lineWidth / 2;
  [leftPath addArcWithCenter:leftPoint
                      radius:lineWidth / 2
                  startAngle:M_PI + M_PI_2
                    endAngle:M_PI
                   clockwise:NO];
  [_groupStrokeView setLeadingPath:leftPath.CGPath];

  // The right path starts like the left path, but flipped horizontally.
  [path applyTransform:CGAffineTransformMakeScale(-1, 1)];
  CGPoint rightPoint = path.currentPoint;
  if (!self.isLastTabInGroup && !self.selected) {
    rightPoint.x += lineWidth;
    rightPoint.x += TabStripTabItemConstants.horizontalSpacing;
    rightPoint.x += lineWidth;
    [path addLineToPoint:rightPoint];
  }
  if (self.intersectsRightEdge) {
    rightPoint.x += TabStripCollectionViewConstants.groupStrokeExtension;
    [path addLineToPoint:rightPoint];
  }
    rightPoint.y += lineWidth / 2;
    [path addArcWithCenter:rightPoint
                    radius:lineWidth / 2
                startAngle:M_PI + M_PI_2
                  endAngle:0
                 clockwise:YES];
  [_groupStrokeView setTrailingPath:path.CGPath];
}

// Sets the cell constraints.
- (void)setupConstraints {
  UILayoutGuide* leadingImageGuide = [[UILayoutGuide alloc] init];
  [self addLayoutGuide:leadingImageGuide];

  UIView* contentView = self.contentView;

  /// `contentView` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_accessibilityContainerView.leadingAnchor
        constraintEqualToAnchor:contentView.leadingAnchor],
    [_accessibilityContainerView.trailingAnchor
        constraintEqualToAnchor:contentView.trailingAnchor],
    [_accessibilityContainerView.topAnchor
        constraintEqualToAnchor:contentView.topAnchor],
    [_accessibilityContainerView.bottomAnchor
        constraintEqualToAnchor:contentView.bottomAnchor
                       constant:-kContentViewBottomInset]
  ]];

  /// `leadingImageGuide` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [leadingImageGuide.leadingAnchor
        constraintEqualToAnchor:_accessibilityContainerView.leadingAnchor
                       constant:kFaviconLeadingMargin],
    [leadingImageGuide.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],
    [leadingImageGuide.widthAnchor constraintEqualToConstant:kFaviconSize],
    [leadingImageGuide.heightAnchor
        constraintEqualToAnchor:leadingImageGuide.widthAnchor],
  ]];
  AddSameConstraints(leadingImageGuide, _faviconView);
  AddSameConstraints(leadingImageGuide, _activityIndicator);

  /// `_closeButton` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_closeButton.trailingAnchor
        constraintEqualToAnchor:_accessibilityContainerView.trailingAnchor
                       constant:-kCloseButtonMargin],
    [_closeButton.widthAnchor constraintEqualToConstant:kCloseButtonSize],
    [_closeButton.heightAnchor constraintEqualToConstant:kCloseButtonSize],
    [_closeButton.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],
  ]];

  /// `_titleLabel` constraints.
  _titleContainerTrailingConstraint = [_titleContainer.trailingAnchor
      constraintEqualToAnchor:_closeButton.leadingAnchor
                     constant:-kTitleInset];
  _titleContainerTrailingConstraint.priority = UILayoutPriorityDefaultLow;
  _titleContainerCollapsedTrailingConstraint = [_titleContainer.trailingAnchor
      constraintEqualToAnchor:_accessibilityContainerView.trailingAnchor
                     constant:-kTitleInset];
  _titleContainerCollapsedTrailingConstraint.priority =
      UILayoutPriorityDefaultLow;
  _titleLabelLeadingConstraint = [_titleLabel.leadingAnchor
      constraintEqualToAnchor:_titleContainer.leadingAnchor];
  _titleLabelTrailingConstraint = [_titleLabel.trailingAnchor
      constraintEqualToAnchor:_titleContainer.trailingAnchor];
  [NSLayoutConstraint activateConstraints:@[
    [_titleContainer.leadingAnchor
        constraintEqualToAnchor:leadingImageGuide.trailingAnchor
                       constant:kTitleInset],
    _titleContainerTrailingConstraint,
    [_titleContainer.heightAnchor
        constraintEqualToAnchor:_accessibilityContainerView.heightAnchor],
    [_titleContainer.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],
    _titleLabelLeadingConstraint,
    [_titleLabel.centerYAnchor
        constraintEqualToAnchor:_titleContainer.centerYAnchor],
  ]];

  /// `_titleGradientView` constraints.
  _titleGradientViewLeadingConstraint = [_titleGradientView.leadingAnchor
      constraintEqualToAnchor:_titleContainer.leadingAnchor];
  _titleGradientViewTrailingConstraint = [_titleGradientView.trailingAnchor
      constraintEqualToAnchor:_titleContainer.trailingAnchor];
  [NSLayoutConstraint activateConstraints:@[
    _titleGradientViewTrailingConstraint,
    [_titleGradientView.widthAnchor
        constraintEqualToConstant:kTitleGradientWidth],
    [_titleGradientView.heightAnchor
        constraintEqualToAnchor:_titleContainer.heightAnchor],
    [_titleGradientView.centerYAnchor
        constraintEqualToAnchor:_titleContainer.centerYAnchor],
  ]];

  /// `_leadingSelectedBorderBackgroundView` and
  /// `_trailingSelectedBorderBackgroundView constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_leadingSelectedBorderBackgroundView.trailingAnchor
        constraintEqualToAnchor:_accessibilityContainerView.leadingAnchor],
    [_leadingSelectedBorderBackgroundView.widthAnchor
        constraintEqualToConstant:kSelectedBorderBackgroundViewWidth],
    [_leadingSelectedBorderBackgroundView.heightAnchor
        constraintEqualToAnchor:_accessibilityContainerView.heightAnchor],
    [_leadingSelectedBorderBackgroundView.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],

    [_trailingSelectedBorderBackgroundView.leadingAnchor
        constraintEqualToAnchor:_accessibilityContainerView.trailingAnchor],
    [_trailingSelectedBorderBackgroundView.widthAnchor
        constraintEqualToConstant:kSelectedBorderBackgroundViewWidth],
    [_trailingSelectedBorderBackgroundView.heightAnchor
        constraintEqualToAnchor:_accessibilityContainerView.heightAnchor],
    [_trailingSelectedBorderBackgroundView.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],
  ]];

  /// `_leftTailView`, `_rightTailView` and `_bottomTailView` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_leftTailView.rightAnchor constraintEqualToAnchor:contentView.leftAnchor],
    [_leftTailView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
    [_leftTailView.widthAnchor constraintEqualToConstant:kCornerSize],
    [_leftTailView.heightAnchor constraintEqualToConstant:kCornerSize],

    [_rightTailView.leftAnchor constraintEqualToAnchor:contentView.rightAnchor],
    [_rightTailView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
    [_rightTailView.widthAnchor constraintEqualToConstant:kCornerSize],
    [_rightTailView.heightAnchor constraintEqualToConstant:kCornerSize],

    [_bottomTailView.leadingAnchor
        constraintEqualToAnchor:contentView.leadingAnchor],
    [_bottomTailView.trailingAnchor
        constraintEqualToAnchor:contentView.trailingAnchor],
    [_bottomTailView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
    [_bottomTailView.heightAnchor constraintEqualToConstant:kCornerSize],
  ]];

  /// `_leadingSeparatorView` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_leadingSeparatorView.trailingAnchor
        constraintEqualToAnchor:contentView.leadingAnchor
                       constant:-kSeparatorHorizontalInset],
    [_leadingSeparatorView.widthAnchor
        constraintEqualToConstant:TabStripStaticSeparatorConstants
                                      .separatorWidth],
    [_leadingSeparatorView.centerYAnchor
        constraintEqualToAnchor:_closeButton.centerYAnchor],
  ]];

  /// `_trailingSeparatorView` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_trailingSeparatorView.leadingAnchor
        constraintEqualToAnchor:contentView.trailingAnchor
                       constant:kSeparatorHorizontalInset],
    [_trailingSeparatorView.widthAnchor
        constraintEqualToConstant:TabStripStaticSeparatorConstants
                                      .separatorWidth],
    [_trailingSeparatorView.centerYAnchor
        constraintEqualToAnchor:_closeButton.centerYAnchor],
  ]];

  [self setSeparatorsHeight:TabStripStaticSeparatorConstants
                                .regularSeparatorHeight];

  /// `_leadingSeparatorGradientView` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_leadingSeparatorGradientView.leadingAnchor
        constraintEqualToAnchor:_leadingSeparatorView.trailingAnchor],
    [_leadingSeparatorGradientView.widthAnchor
        constraintEqualToConstant:kSeparatorGradientWidth],
    [_leadingSeparatorGradientView.heightAnchor
        constraintEqualToAnchor:_accessibilityContainerView.heightAnchor],
    [_leadingSeparatorGradientView.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],
  ]];

  /// `_trailingSeparatorGradientView` constraints.
  [NSLayoutConstraint activateConstraints:@[
    [_trailingSeparatorGradientView.trailingAnchor
        constraintEqualToAnchor:_trailingSeparatorView.leadingAnchor],
    [_trailingSeparatorGradientView.widthAnchor
        constraintEqualToConstant:kSeparatorGradientWidth],
    [_trailingSeparatorGradientView.heightAnchor
        constraintEqualToAnchor:_accessibilityContainerView.heightAnchor],
    [_trailingSeparatorGradientView.centerYAnchor
        constraintEqualToAnchor:_accessibilityContainerView.centerYAnchor],
  ]];

  /// `_groupStrokeView` constraints.
  _groupStrokeViewBottomConstraint =
      [_groupStrokeView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor];
  _groupStrokeViewBottomConstraint.active = YES;
  _groupStrokeViewBottomSelectedConstraint =
      [_groupStrokeView.bottomAnchor constraintEqualToAnchor:self.topAnchor];
  _groupStrokeViewWidthConstraint =
      [_groupStrokeView.widthAnchor constraintEqualToAnchor:self.widthAnchor];
  _groupStrokeViewWidthConstraint.priority = UILayoutPriorityDefaultHigh;
  _groupStrokeViewWidthConstraint.active = YES;
  AddSameCenterXConstraint(_groupStrokeView, self);
}

// Selector registered to the close button.
- (void)closeButtonTapped:(id)sender {
  base::RecordAction(base::UserMetricsAction("MobileTabStripCloseTab"));
  [self.delegate closeButtonTappedForCell:self];
}

// Returns a new favicon view.
- (UIImageView*)createFaviconView {
  UIImageView* faviconView =
      [[UIImageView alloc] initWithImage:DefaultFavicon()];
  faviconView.translatesAutoresizingMaskIntoConstraints = NO;
  faviconView.contentMode = UIViewContentModeScaleAspectFit;
  return faviconView;
}

// Returns a new close button.
- (UIButton*)createCloseButton {
  UIImage* closeSymbol =
      DefaultSymbolWithPointSize(kXMarkSymbol, kCloseButtonSize);
  UIButton* closeButton;
  if ([TabStripFeaturesUtils isTabStripBiggerCloseTargetEnabled]) {
    ExtendedTouchTargetButton* button =
        [[ExtendedTouchTargetButton alloc] init];
    button.minimumDiameter = kCloseButtonMinimumTouchTarget;
    closeButton = button;
  } else {
    closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
  }
  closeButton.translatesAutoresizingMaskIntoConstraints = NO;
  closeButton.tintColor = [UIColor colorNamed:kTextSecondaryColor];
  [closeButton setImage:closeSymbol forState:UIControlStateNormal];
  [closeButton addTarget:self
                  action:@selector(closeButtonTapped:)
        forControlEvents:UIControlEventTouchUpInside];
  closeButton.pointerInteractionEnabled = YES;
  closeButton.accessibilityIdentifier =
      TabStripTabItemConstants.closeButtonAccessibilityIdentifier;
  return closeButton;
}

// Returns a new title label.
- (UILabel*)createTitleLabel {
  UILabel* titleLabel = [[UILabel alloc] init];
  titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  titleLabel.font = [UIFont systemFontOfSize:TabStripTabItemConstants.fontSize
                                      weight:UIFontWeightMedium];
  titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
  titleLabel.adjustsFontForContentSizeCategory = YES;

  return titleLabel;
}

// Returns a new gradient view.
- (GradientView*)createGradientView {
  GradientView* gradientView =
      [[GradientView alloc] initWithStartColor:[[TabStripHelper backgroundColor]
                                                   colorWithAlphaComponent:0]
                                      endColor:[TabStripHelper backgroundColor]
                                    startPoint:CGPointMake(0.0f, 0.5f)
                                      endPoint:CGPointMake(1.0f, 0.5f)];
  gradientView.translatesAutoresizingMaskIntoConstraints = NO;
  return gradientView;
}

// Returns a new title container view.
- (UIView*)createTitleContainer {
  UIView* titleContainer = [[UIView alloc] init];
  titleContainer.translatesAutoresizingMaskIntoConstraints = NO;
  titleContainer.clipsToBounds = YES;

  _titleLabel = [self createTitleLabel];
  [titleContainer addSubview:_titleLabel];

  _titleGradientView = [self createGradientView];
  [titleContainer addSubview:_titleGradientView];

  return titleContainer;
}

// Returns a new Activity Indicator.
- (MDCActivityIndicator*)createActivityIndicatior {
  MDCActivityIndicator* activityIndicator = [[MDCActivityIndicator alloc] init];
  activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
  activityIndicator.hidden = YES;
  return activityIndicator;
}

// Returns a new decoration view.
- (UIView*)createDecorationView {
  UIView* tailView = [[UIView alloc] init];
  tailView.backgroundColor =
      [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
  tailView.translatesAutoresizingMaskIntoConstraints = NO;
  tailView.clipsToBounds = NO;
  tailView.hidden = YES;
  return tailView;
}

// Returns a new separator view.
- (UIView*)createSeparatorView {
  UIView* separatorView = [[UIView alloc] init];
  separatorView.backgroundColor = [TabStripHelper backgroundColor];
  separatorView.translatesAutoresizingMaskIntoConstraints = NO;
  separatorView.layer.cornerRadius =
      TabStripStaticSeparatorConstants.separatorCornerRadius;
  separatorView.layer.masksToBounds = YES;

  UIView* backgroundView = [[UIView alloc] init];
  backgroundView.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  backgroundView.backgroundColor = [UIColor colorNamed:kTextQuaternaryColor];
  [separatorView addSubview:backgroundView];
  return separatorView;
}

// Returns a new selected border background view.
- (UIView*)createSelectedBorderBackgroundView {
  UIView* backgroundView = [[UIView alloc] init];
  backgroundView.backgroundColor = [TabStripHelper backgroundColor];
  backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
  backgroundView.hidden = YES;
  return backgroundView;
}

- (void)updateAccessibilityValue {
  // Use the accessibility Value as there is a pause when using the
  // accessibility hint.
  BOOL grouped = self.groupStrokeColor != nil;
  _accessibilityContainerView.accessibilityValue = l10n_util::GetNSStringF(
      grouped ? IDS_IOS_TAB_STRIP_TAB_CELL_IN_GROUP_VOICE_OVER_VALUE
              : IDS_IOS_TAB_STRIP_TAB_CELL_VOICE_OVER_VALUE,
      base::NumberToString16(self.tabIndex),
      base::NumberToString16(self.numberOfTabs));
}

@end