chromium/ios/chrome/browser/ui/page_info/page_info_view_controller.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/page_info/page_info_view_controller.h"

#import "base/apple/foundation_util.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/time/time.h"
#import "components/page_info/core/page_info_history_data_source.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/keyboard/ui_bundled/UIKeyCommand+Chrome.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/permissions/ui_bundled/permission_info.h"
#import "ios/chrome/browser/permissions/ui_bundled/permissions_constants.h"
#import "ios/chrome/browser/permissions/ui_bundled/permissions_delegate.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/public/commands/page_info_commands.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_attributed_string_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_link_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_multi_detail_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_link_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/ui/page_info/features.h"
#import "ios/chrome/browser/ui/page_info/page_info_about_this_site_info.h"
#import "ios/chrome/browser/ui/page_info/page_info_constants.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/permissions/permissions.h"
#import "ui/base/l10n/l10n_util.h"
#import "url/gurl.h"

namespace {

typedef NS_ENUM(NSInteger, SectionIdentifier) {
  SectionIdentifierSecurityContent,
  SectionIdentifierPermissions,
  SectionIdentifierAboutThisSite,
  SectionIdentifierLastVisited
};

typedef NS_ENUM(NSInteger, ItemIdentifier) {
  ItemIdentifierSecurity,
  ItemIdentifierPermissionsCamera,
  ItemIdentifierPermissionsMicrophone,
  ItemIdentifierAboutThisSite,
  ItemIdentifierLastVisited
};

// The minimum scale factor of the title label showing the URL.
const float kTitleLabelMinimumScaleFactor = 0.7f;

// The maximum number of lines we should show for a page's description in the
// AboutThisSite section.
const NSInteger kAboutThisSiteDetailTextNumberOfLines = 2;

}  // namespace

@interface PageInfoViewController () <TableViewLinkHeaderFooterItemDelegate>

// The page info security description.
@property(nonatomic, strong)
    PageInfoSiteSecurityDescription* pageInfoSecurityDescription;

// The list of permissions info used to create switches.
@property(nonatomic, copy)
    NSMutableDictionary<NSNumber*, NSNumber*>* permissionsInfo;

@end

@implementation PageInfoViewController {
  UITableViewDiffableDataSource<NSNumber*, NSNumber*>* _dataSource;
  PageInfoAboutThisSiteInfo* _aboutThisSiteInfo;
  NSString* _lastVisitedTimestamp;
}

#pragma mark - UIViewController

- (instancetype)initWithSiteSecurityDescription:
    (PageInfoSiteSecurityDescription*)siteSecurityDescription {
  UITableViewStyle style = ChromeTableViewStyle();
  self = [super initWithStyle:style];
  if (self) {
    _pageInfoSecurityDescription = siteSecurityDescription;
  }
  return self;
}

- (void)viewDidLoad {
  [super viewDidLoad];

  self.title = l10n_util::GetNSString(IDS_IOS_PAGE_INFO_SITE_INFORMATION);
  if (IsRevampPageInfoIosEnabled()) {
    self.navigationItem.largeTitleDisplayMode =
        UINavigationItemLargeTitleDisplayModeNever;
    self.navigationItem.prompt = self.pageInfoSecurityDescription.siteURL;
  } else {
    self.navigationItem.titleView =
        [self titleViewLabelForURL:self.pageInfoSecurityDescription.siteURL];
  }

  self.tableView.accessibilityIdentifier = kPageInfoViewAccessibilityIdentifier;
  self.navigationController.navigationBar.accessibilityIdentifier =
      kPageInfoViewNavigationBarAccessibilityIdentifier;

  UIBarButtonItem* dismissButton = [[UIBarButtonItem alloc]
      initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                           target:self.pageInfoCommandsHandler
                           action:@selector(hidePageInfo)];
  self.navigationItem.rightBarButtonItem = dismissButton;
  self.tableView.separatorInset = UIEdgeInsetsMake(
      0,
      IsRevampPageInfoIosEnabled() ? kPageInfoTableViewSeparatorInsetWithIcon
                                   : kPageInfoTableViewSeparatorInset,
      0, 0);
  if (!IsRevampPageInfoIosEnabled()) {
    self.tableView.allowsSelection = NO;
  }

  if (self.pageInfoSecurityDescription.isEmpty) {
    [self addEmptyTableViewWithMessage:self.pageInfoSecurityDescription.message
                                 image:nil];
    self.tableView.alwaysBounceVertical = NO;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    return;
  }

  // Update the security information if it was fetched while the page was being
  // loaded. If page info is opened while the page is being loaded, its
  // certificates might not have been loaded yet and so the page is shown as
  // insecure. The next time the user opens page info, we should update the
  // security information when the page is fully loaded so it's up to date.
  if (self.pageInfoSecurityDescription.isPageLoading) {
    _pageInfoSecurityDescription =
        [self.pageInfoPresentationHandler updatedSiteSecurityDescription];
  }

  [self loadModel];
}

#pragma mark - LegacyChromeTableViewController

- (void)loadModel {
  __weak __typeof(self) weakSelf = self;
  _dataSource = [[UITableViewDiffableDataSource alloc]
      initWithTableView:self.tableView
           cellProvider:^UITableViewCell*(UITableView* tableView,
                                          NSIndexPath* indexPath,
                                          NSNumber* itemIdentifier) {
             return
                 [weakSelf cellForTableView:tableView
                                  indexPath:indexPath
                             itemIdentifier:static_cast<ItemIdentifier>(
                                                itemIdentifier.integerValue)];
           }];

  RegisterTableViewCell<TableViewDetailIconCell>(self.tableView);
  RegisterTableViewCell<TableViewSwitchCell>(self.tableView);
  RegisterTableViewHeaderFooter<TableViewTextHeaderFooterView>(self.tableView);
  RegisterTableViewHeaderFooter<TableViewLinkHeaderFooterView>(self.tableView);
  RegisterTableViewHeaderFooter<TableViewAttributedStringHeaderFooterView>(
      self.tableView);

  NSDiffableDataSourceSnapshot* snapshot =
      [[NSDiffableDataSourceSnapshot alloc] init];
  [snapshot
      appendSectionsWithIdentifiers:@[ @(SectionIdentifierSecurityContent) ]];
  [snapshot appendItemsWithIdentifiers:@[ @(ItemIdentifierSecurity) ]];

  // Permissions section.
  for (NSNumber* permission in self.permissionsInfo.allKeys) {
    [self updateSnapshot:snapshot forPermission:permission];
  }

  if (IsAboutThisSiteFeatureEnabled()) {
    [self updateSnapshotForAboutThisSite:snapshot];
  }

  // Append cell for the Last Visited row only if the `kPageInfoLastVisitedIOS`
  // flag is enabled and the Last Visited timestamp is available.
  if (IsPageInfoLastVisitedIOSEnabled() && _lastVisitedTimestamp) {
    [snapshot
        appendSectionsWithIdentifiers:@[ @(SectionIdentifierLastVisited) ]];
    [snapshot appendItemsWithIdentifiers:@[ @(ItemIdentifierLastVisited) ]];
  }

  [_dataSource applySnapshot:snapshot animatingDifferences:NO];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  DCHECK(self.pageInfoPresentationHandler);
  ItemIdentifier itemType = static_cast<ItemIdentifier>(
      [_dataSource itemIdentifierForIndexPath:indexPath].integerValue);
  switch (itemType) {
    case ItemIdentifierSecurity:
      if (IsRevampPageInfoIosEnabled()) {
        [self.pageInfoPresentationHandler showSecurityPage];
      }
      break;
    case ItemIdentifierAboutThisSite:
      if (IsRevampPageInfoIosEnabled()) {
        [self.pageInfoPresentationHandler
            showAboutThisSitePage:_aboutThisSiteInfo.moreAboutURL];
      }
      break;
    case ItemIdentifierLastVisited:
      // TODO(crbug.com/358341532): Handle the tap on the Last Visited row.
      break;
    case ItemIdentifierPermissionsCamera:
    case ItemIdentifierPermissionsMicrophone:
      break;
  }

  // Deselect the row so the UI seems responsive when the action triggered by
  // the selection (e.g. opening a new tab, opening a subpage) takes a bit
  // longer to happen.
  [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (CGFloat)tableView:(UITableView*)tableView
    heightForHeaderInSection:(NSInteger)section {
  SectionIdentifier sectionIdentifier = static_cast<SectionIdentifier>(
      [_dataSource sectionIdentifierForIndex:section].integerValue);

  if (IsRevampPageInfoIosEnabled()) {
    return ChromeTableViewHeightForHeaderInSection(sectionIdentifier);
  }

  return sectionIdentifier == SectionIdentifierSecurityContent
             ? kPageInfoPaddingFirstSectionHeader
             : UITableViewAutomaticDimension;
}

- (UIView*)tableView:(UITableView*)tableView
    viewForHeaderInSection:(NSInteger)section {
  SectionIdentifier sectionIdentifier = static_cast<SectionIdentifier>(
      [_dataSource sectionIdentifierForIndex:section].integerValue);
  switch (sectionIdentifier) {
    case SectionIdentifierSecurityContent:
    case SectionIdentifierAboutThisSite:
    case SectionIdentifierLastVisited:
      return nil;
    case SectionIdentifierPermissions: {
      if (IsRevampPageInfoIosEnabled()) {
        return nil;
      }

      TableViewTextHeaderFooterView* header =
          DequeueTableViewHeaderFooter<TableViewTextHeaderFooterView>(
              self.tableView);
      header.textLabel.text =
          l10n_util::GetNSString(IDS_IOS_PAGE_INFO_PERMISSIONS_HEADER);
      [header setSubtitle:nil];
      return header;
    }
  }
}

- (UIView*)tableView:(UITableView*)tableView
    viewForFooterInSection:(NSInteger)section {
  SectionIdentifier sectionIdentifier = static_cast<SectionIdentifier>(
      [_dataSource sectionIdentifierForIndex:section].integerValue);
  switch (sectionIdentifier) {
    case SectionIdentifierSecurityContent: {
      if (IsRevampPageInfoIosEnabled()) {
        // Don't show the security footer in the revamp UI.
        return nil;
      }

      TableViewLinkHeaderFooterView* footer =
          DequeueTableViewHeaderFooter<TableViewLinkHeaderFooterView>(
              self.tableView);
      footer.urls =
          @[ [[CrURL alloc] initWithGURL:GURL(kPageInfoHelpCenterURL)] ];
      [footer setText:self.pageInfoSecurityDescription.message
            withColor:[UIColor colorNamed:kTextSecondaryColor]];
      footer.delegate = self;
      footer.accessibilityIdentifier =
          kPageInfoSecurityFooterAccessibilityIdentifier;
      return footer;
    }
    case SectionIdentifierPermissions: {
      TableViewAttributedStringHeaderFooterView* footer =
          DequeueTableViewHeaderFooter<
              TableViewAttributedStringHeaderFooterView>(self.tableView);
      footer.attributedString = [self permissionFooterAttributedString];
      return footer;
    }
    default:
      return nil;
  }
}

#pragma mark - TableViewLinkHeaderFooterItemDelegate

- (void)view:(TableViewLinkHeaderFooterView*)view didTapLinkURL:(CrURL*)URL {
  DCHECK(URL.gurl == GURL(kPageInfoHelpCenterURL));
  [self.pageInfoPresentationHandler showSecurityHelpPage];
}

#pragma mark - UIAdaptivePresentationControllerDelegate

- (void)presentationControllerDidDismiss:
    (UIPresentationController*)presentationController {
  [self.pageInfoCommandsHandler hidePageInfo];
}

#pragma mark - UIResponder

// To always be able to register key commands via -keyCommands, the VC must be
// able to become first responder.
- (BOOL)canBecomeFirstResponder {
  return YES;
}

- (NSArray*)keyCommands {
  return @[ UIKeyCommand.cr_close ];
}

- (void)keyCommand_close {
  base::RecordAction(base::UserMetricsAction("MobileKeyCommandClose"));
  [self.pageInfoCommandsHandler hidePageInfo];
}

#pragma mark - Private

// Returns a cell.
- (UITableViewCell*)cellForTableView:(UITableView*)tableView
                           indexPath:(NSIndexPath*)indexPath
                      itemIdentifier:(ItemIdentifier)itemIdentifier {
  switch (itemIdentifier) {
    case ItemIdentifierSecurity: {
      TableViewDetailIconCell* cell =
          DequeueTableViewCell<TableViewDetailIconCell>(tableView);
      cell.textLabel.text = l10n_util::GetNSString(
          IsRevampPageInfoIosEnabled() ? IDS_IOS_PAGE_INFO_CONNECTION
                                       : IDS_IOS_PAGE_INFO_SITE_SECURITY);
      cell.detailText = self.pageInfoSecurityDescription.status;
      [cell setIconImage:self.pageInfoSecurityDescription.iconImage
                tintColor:UIColor.whiteColor
          backgroundColor:self.pageInfoSecurityDescription.iconBackgroundColor
             cornerRadius:kColorfulBackgroundSymbolCornerRadius];

      if (IsRevampPageInfoIosEnabled()) {
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
      }

      return cell;
    }
    case ItemIdentifierPermissionsCamera: {
      TableViewSwitchCell* cell =
          DequeueTableViewCell<TableViewSwitchCell>(tableView);
      BOOL permissionOn =
          self.permissionsInfo[@(web::PermissionCamera)].unsignedIntValue ==
          web::PermissionStateAllowed;
      NSString* title = l10n_util::GetNSString(IDS_IOS_PERMISSIONS_CAMERA);
      [cell configureCellWithTitle:title
                          subtitle:nil
                     switchEnabled:YES
                                on:permissionOn];
      cell.accessibilityIdentifier =
          kPageInfoCameraSwitchAccessibilityIdentifier;
      cell.switchView.tag = itemIdentifier;
      [cell.switchView addTarget:self
                          action:@selector(permissionSwitchToggled:)
                forControlEvents:UIControlEventValueChanged];

      if (IsRevampPageInfoIosEnabled()) {
        [cell setIconImage:CustomSymbolWithPointSize(kCameraSymbol,
                                                     kPageInfoSymbolPointSize)
                  tintColor:UIColor.whiteColor
            backgroundColor:[UIColor colorNamed:kOrange500Color]
               cornerRadius:kColorfulBackgroundSymbolCornerRadius
                borderWidth:0];
      }

      return cell;
    }
    case ItemIdentifierPermissionsMicrophone: {
      TableViewSwitchCell* cell =
          DequeueTableViewCell<TableViewSwitchCell>(tableView);
      BOOL permissionOn =
          self.permissionsInfo[@(web::PermissionMicrophone)].unsignedIntValue ==
          web::PermissionStateAllowed;
      NSString* title = l10n_util::GetNSString(IDS_IOS_PERMISSIONS_MICROPHONE);
      [cell configureCellWithTitle:title
                          subtitle:nil
                     switchEnabled:YES
                                on:permissionOn];
      cell.accessibilityIdentifier =
          kPageInfoMicrophoneSwitchAccessibilityIdentifier;
      cell.switchView.tag = itemIdentifier;
      [cell.switchView addTarget:self
                          action:@selector(permissionSwitchToggled:)
                forControlEvents:UIControlEventValueChanged];

      if (IsRevampPageInfoIosEnabled()) {
        [cell setIconImage:DefaultSymbolWithPointSize(kMicrophoneSymbol,
                                                      kPageInfoSymbolPointSize)
                  tintColor:UIColor.whiteColor
            backgroundColor:[UIColor colorNamed:kOrange500Color]
               cornerRadius:kColorfulBackgroundSymbolCornerRadius
                borderWidth:0];
      }

      return cell;
    }
    case ItemIdentifierAboutThisSite: {
      TableViewDetailIconCell* cell =
          DequeueTableViewCell<TableViewDetailIconCell>(tableView);
      cell.textLabel.text =
          l10n_util::GetNSString(IDS_IOS_PAGE_INFO_ABOUT_THIS_PAGE);
      cell.detailText = _aboutThisSiteInfo.summary;
      cell.detailTextNumberOfLines = kAboutThisSiteDetailTextNumberOfLines;
      cell.textLayoutConstraintAxis = UILayoutConstraintAxisVertical;

      UIImage* icon =
#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
          CustomSymbolTemplateWithPointSize(kPageInsightsSymbol,
                                            kPageInfoSymbolPointSize);
#else
          DefaultSymbolTemplateWithPointSize(kInfoCircleSymbol,
                                             kPageInfoSymbolPointSize);
#endif  // BUILDFLAG(IOS_USE_BRANDED_SYMBOLS),

      [cell setIconImage:icon
                tintColor:UIColor.whiteColor
          backgroundColor:[UIColor colorNamed:kPurple500Color]
             cornerRadius:kColorfulBackgroundSymbolCornerRadius];

      cell.accessoryView = [[UIImageView alloc]
          initWithImage:DefaultAccessorySymbolConfigurationWithRegularWeight(
                            kExternalLinkSymbol)];
      cell.accessoryView.tintColor = [UIColor colorNamed:kTextQuaternaryColor];
      return cell;
    }
    case ItemIdentifierLastVisited: {
      TableViewDetailIconCell* cell =
          DequeueTableViewCell<TableViewDetailIconCell>(tableView);
      cell.textLabel.text = l10n_util::GetNSString(IDS_PAGE_INFO_HISTORY);

      cell.detailText = _lastVisitedTimestamp;
      cell.textLayoutConstraintAxis = UILayoutConstraintAxisVertical;
      [cell setIconImage:DefaultSymbolTemplateWithPointSize(
                             kClockSymbol, kPageInfoSymbolPointSize)
                tintColor:UIColor.whiteColor
          backgroundColor:[UIColor colorNamed:kBlue500Color]
             cornerRadius:kColorfulBackgroundSymbolCornerRadius];

      if (IsRevampPageInfoIosEnabled()) {
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
      }

      return cell;
    }
  }
}

// Returns the attributed string for the permission footer.
- (NSAttributedString*)permissionFooterAttributedString {
  NSString* description = l10n_util::GetNSStringF(
      IDS_IOS_PERMISSIONS_INFOBAR_MODAL_DESCRIPTION,
      base::SysNSStringToUTF16(self.pageInfoSecurityDescription.siteURL));
  NSMutableAttributedString* descriptionAttributedString =
      [[NSMutableAttributedString alloc]
          initWithAttributedString:PutBoldPartInString(
                                       description, UIFontTextStyleFootnote)];
  [descriptionAttributedString
      addAttributes:@{
        NSForegroundColorAttributeName :
            [UIColor colorNamed:kTextSecondaryColor]
      }
              range:NSMakeRange(0, descriptionAttributedString.length)];
  return descriptionAttributedString;
}

// Returns an UILabel for the navigationItem titleView for `siteURL`.
- (UILabel*)titleViewLabelForURL:(NSString*)siteURL {
  UILabel* labelURL = [[UILabel alloc] init];
  labelURL.lineBreakMode = NSLineBreakByTruncatingHead;
  labelURL.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
  labelURL.text = siteURL;
  labelURL.adjustsFontSizeToFitWidth = YES;
  labelURL.minimumScaleFactor = kTitleLabelMinimumScaleFactor;
  return labelURL;
}

// Updates `snapshot` to reflect the changes to AboutThisSite info.
- (void)updateSnapshotForAboutThisSite:
    (NSDiffableDataSourceSnapshot<NSNumber*, NSNumber*>*)snapshot {
  CHECK(IsAboutThisSiteFeatureEnabled());
  if (!_aboutThisSiteInfo || !self.pageInfoSecurityDescription.secure) {
    return;
  }

  NSInteger sectionIndex =
      [snapshot indexOfSectionIdentifier:@(SectionIdentifierPermissions)];
  SectionIdentifier afterSectionWithIdentifier =
      (sectionIndex == NSNotFound) ? SectionIdentifierSecurityContent
                                   : SectionIdentifierPermissions;
  [snapshot insertSectionsWithIdentifiers:@[ @(SectionIdentifierAboutThisSite) ]
               afterSectionWithIdentifier:@(afterSectionWithIdentifier)];

  [snapshot appendItemsWithIdentifiers:@[ @(ItemIdentifierAboutThisSite) ]
             intoSectionWithIdentifier:@(SectionIdentifierAboutThisSite)];
}

// Updates `snapshot` to reflect the changes done to `permissions`.
- (void)updateSnapshot:
            (NSDiffableDataSourceSnapshot<NSNumber*, NSNumber*>*)snapshot
         forPermission:(NSNumber*)permission {
  web::PermissionState state = static_cast<web::PermissionState>(
      self.permissionsInfo[permission].unsignedIntegerValue);
  ItemIdentifier itemIdentifier;
  switch (static_cast<web::Permission>(permission.unsignedIntValue)) {
    case web::PermissionCamera:
      itemIdentifier = ItemIdentifierPermissionsCamera;
      break;
    case web::PermissionMicrophone:
      itemIdentifier = ItemIdentifierPermissionsMicrophone;
      break;
  }
  [self updateSnapshot:snapshot forPermissionState:state toItem:itemIdentifier];
}

// Invoked when a permission switch is toggled.
- (void)permissionSwitchToggled:(UISwitch*)sender {
  web::Permission permission;
  switch (sender.tag) {
    case ItemIdentifierPermissionsCamera:
      permission = web::PermissionCamera;
      break;
    case ItemIdentifierPermissionsMicrophone:
      permission = web::PermissionMicrophone;
      break;
  }
  PermissionInfo* permissionsDescription = [[PermissionInfo alloc] init];
  permissionsDescription.permission = permission;
  permissionsDescription.state =
      sender.isOn ? web::PermissionStateAllowed : web::PermissionStateBlocked;
  [self.permissionsDelegate updateStateForPermission:permissionsDescription];
}

// Updates the `snapshot` (including adds/removes section) to reflect changes to
// `itemIdentifier`'s `state`.
- (void)updateSnapshot:
            (NSDiffableDataSourceSnapshot<NSNumber*, NSNumber*>*)snapshot
    forPermissionState:(web::PermissionState)state
                toItem:(ItemIdentifier)itemIdentifier {
  NSInteger sectionIndex =
      [snapshot indexOfSectionIdentifier:@(SectionIdentifierPermissions)];
  if (sectionIndex == NSNotFound) {
    [snapshot
        insertSectionsWithIdentifiers:@[ @(SectionIdentifierPermissions) ]
           afterSectionWithIdentifier:@(SectionIdentifierSecurityContent)];
  }

  BOOL itemVisible = state != web::PermissionStateNotAccessible;
  if (itemVisible) {
    if ([_dataSource indexPathForItemIdentifier:@(itemIdentifier)]) {
      [snapshot reconfigureItemsWithIdentifiers:@[ @(itemIdentifier) ]];
    } else {
      if (itemIdentifier == ItemIdentifierPermissionsCamera &&
          [_dataSource indexPathForItemIdentifier:
                           @(ItemIdentifierPermissionsMicrophone)]) {
        [snapshot
            insertItemsWithIdentifiers:@[ @(itemIdentifier) ]
              beforeItemWithIdentifier:@(ItemIdentifierPermissionsMicrophone)];
      } else {
        [snapshot appendItemsWithIdentifiers:@[ @(itemIdentifier) ]
                   intoSectionWithIdentifier:@(SectionIdentifierPermissions)];
      }
    }
  } else {
    [snapshot deleteItemsWithIdentifiers:@[ @(itemIdentifier) ]];
  }

  if ([snapshot numberOfItemsInSection:@(SectionIdentifierPermissions)] == 0) {
    [snapshot
        deleteSectionsWithIdentifiers:@[ @(SectionIdentifierPermissions) ]];
  }
}

#pragma mark - PageInfoAboutThisSiteConsumer

- (void)setAboutThisSiteSection:(PageInfoAboutThisSiteInfo*)info {
  CHECK(IsAboutThisSiteFeatureEnabled());
  _aboutThisSiteInfo = info;
}

#pragma mark - PermissionsConsumer

- (void)setPermissionsInfo:
    (NSDictionary<NSNumber*, NSNumber*>*)permissionsInfo {
  _permissionsInfo = [permissionsInfo mutableCopy];
}

- (void)permissionStateChanged:(PermissionInfo*)permissionInfo {
  self.permissionsInfo[@(permissionInfo.permission)] = @(permissionInfo.state);

  NSDiffableDataSourceSnapshot<NSNumber*, NSNumber*>* snapshot =
      [_dataSource snapshot];
  [self updateSnapshot:snapshot forPermission:@(permissionInfo.permission)];
  [_dataSource applySnapshot:snapshot animatingDifferences:YES];
}

#pragma mark - PageInfoHistoryConsumer

- (void)setLastVisitedTimestamp:(base::Time)lastVisited {
  CHECK(IsPageInfoLastVisitedIOSEnabled());
  std::string timestamp = base::UTF16ToUTF8(
      page_info::PageInfoHistoryDataSource::FormatLastVisitedTimestamp(
          lastVisited));
  _lastVisitedTimestamp = [NSString stringWithUTF8String:timestamp.c_str()];

  NSDiffableDataSourceSnapshot<NSNumber*, NSNumber*>* snapshot =
      [_dataSource snapshot];

  // It was observed that the Last Visited timestamp is usually available before
  // the view is displayed. If that is the case, then we can just store the
  // value of the timestamp and rely on `loadModel` to display the Last Visited
  // row.
  if (!_dataSource || !snapshot) {
    return;
  }

  // Append cell for the Last Visited row.
  [snapshot appendSectionsWithIdentifiers:@[ @(SectionIdentifierLastVisited) ]];
  [snapshot appendItemsWithIdentifiers:@[ @(ItemIdentifierLastVisited) ]];

  // Update the UI.
  [_dataSource applySnapshot:snapshot animatingDifferences:NO];
}

@end