chromium/ios/chrome/browser/ui/tab_switcher/tab_strip/ui/context_menu/tab_strip_context_menu_helper.mm

// Copyright 2024 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/context_menu/tab_strip_context_menu_helper.h"

#import "base/check.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_util.h"
#import "ios/chrome/browser/shared/model/web_state_list/tab_group.h"
#import "ios/chrome/browser/shared/model/web_state_list/tab_group_utils.h"
#import "ios/chrome/browser/shared/model/web_state_list/tab_utils.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/tab_strip_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/ui/menu/action_factory.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_group_item.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_mutator.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_item.h"
#import "url/gurl.h"

namespace {

// Creates a `UIMenu` displayed inline with `children` as elements.
UIMenu* CreateDisplayInlineUIMenu(NSArray<UIMenuElement*>* children) {
  return [UIMenu menuWithTitle:@""
                         image:nil
                    identifier:nil
                       options:UIMenuOptionsDisplayInline
                      children:children];
}

// Creates a `UIContextMenuConfiguration` with `children` as elements.
UIContextMenuConfiguration* CreateUIContextMenuConfiguration(
    NSArray<UIMenuElement*>* children) {
  UIContextMenuActionProvider actionProvider =
      ^(NSArray<UIMenuElement*>* suggestedActions) {
        return [UIMenu menuWithTitle:@"" children:children];
      };
  return
      [UIContextMenuConfiguration configurationWithIdentifier:nil
                                              previewProvider:nil
                                               actionProvider:actionProvider];
}

}  // namespace

@implementation TabStripContextMenuHelper {
  raw_ptr<BrowserList> _browserList;
  base::WeakPtr<WebStateList> _webStateList;
}

- (instancetype)initWithBrowserList:(BrowserList*)browserList
                       webStateList:(WebStateList*)webStateList {
  self = [super init];
  if (self) {
    CHECK(browserList);
    CHECK(webStateList);
    _browserList = browserList;
    _webStateList = webStateList->AsWeakPtr();
  }
  return self;
}

- (void)disconnect {
  _browserList = nullptr;
  _webStateList = nullptr;
}

#pragma mark - TabStripContextMenuProvider

- (UIContextMenuConfiguration*)
    contextMenuConfigurationForTabSwitcherItem:(TabSwitcherItem*)tabSwitcherItem
                                    originView:(UIView*)originView
                                  menuScenario:
                                      (enum MenuScenarioHistogram)scenario {
  NSArray<UIMenuElement*>* elements =
      [self menuElementsForTabSwitcherItem:tabSwitcherItem
                                originView:originView
                              menuScenario:scenario];
  return CreateUIContextMenuConfiguration(elements);
}

- (UIContextMenuConfiguration*)
    contextMenuConfigurationForTabGroupItem:(TabGroupItem*)tabGroupItem
                                 originView:(UIView*)originView
                               menuScenario:
                                   (enum MenuScenarioHistogram)scenario {
  NSArray<UIMenuElement*>* elements =
      [self menuElementsForTabGroupItem:tabGroupItem
                             originView:originView
                           menuScenario:scenario];
  return CreateUIContextMenuConfiguration(elements);
}

#pragma mark - Private

- (NSArray<UIMenuElement*>*)
    menuElementsForTabSwitcherItem:(TabSwitcherItem*)tabSwitcherItem
                        originView:(UIView*)originView
                      menuScenario:(MenuScenarioHistogram)scenario {
  // Record that this context menu was shown to the user.
  RecordMenuShown(scenario);
  ActionFactory* actionFactory =
      [[ActionFactory alloc] initWithScenario:scenario];
  __weak __typeof(self) weakSelf = self;

  NSMutableArray<UIMenuElement*>* menuElements = [[NSMutableArray alloc] init];

  // If groups are enabled, add "Add Tab to Group" menu.
  if ([TabStripFeaturesUtils isModernTabStripWithTabGroups]) {
    std::set<const TabGroup*> groups =
        GetAllGroupsForBrowserList(_browserList, self.incognito);
    CHECK(_webStateList);
    int webStateIndex = GetWebStateIndex(
        _webStateList.get(),
        WebStateSearchCriteria{.identifier = tabSwitcherItem.identifier});
    CHECK(_webStateList->ContainsIndex(webStateIndex),
          base::NotFatalUntil::M128);
    const TabGroup* currentGroup =
        _webStateList->GetGroupOfWebStateAt(webStateIndex);
    auto addTabToGroupBlock = ^(const TabGroup* group) {
      if (group) {
        [weakSelf.mutator addItem:tabSwitcherItem toGroup:group];
      } else {
        [weakSelf.mutator createNewGroupWithItem:tabSwitcherItem];
      }
    };
    if (currentGroup) {
      auto removeTabFromGroupBlock = ^{
        [weakSelf.mutator removeItemFromGroup:tabSwitcherItem];
      };
      UIMenuElement* moveTabToGroupMenu = [actionFactory
          menuToMoveTabToGroupWithGroups:groups
                            currentGroup:currentGroup
                               moveBlock:addTabToGroupBlock
                             removeBlock:removeTabFromGroupBlock];
      [menuElements addObject:moveTabToGroupMenu];
    } else {
      UIMenuElement* addTabToGroupMenu =
          [actionFactory menuToAddTabToGroupWithGroups:groups
                                          numberOfTabs:1
                                                 block:addTabToGroupBlock];
      [menuElements addObject:addTabToGroupMenu];
    }
  }

  // If tab is not NTP, add "Share" menu.
  if (!IsURLNewTabPage(tabSwitcherItem.URL)) {
    UIAction* shareAction = [actionFactory actionToShareWithBlock:^{
      [weakSelf.handler shareItem:tabSwitcherItem originView:originView];
    }];
    UIMenu* shareMenu = CreateDisplayInlineUIMenu(@[ shareAction ]);
    [menuElements addObject:shareMenu];
  }

  // Add "Close" menu to close this tab or all tabs except this one.
  NSMutableArray<UIMenuElement*>* closeMenuElements =
      [[NSMutableArray alloc] init];
  UIAction* closeTabAction = [actionFactory actionToCloseRegularTabWithBlock:^{
    [weakSelf.mutator closeItem:tabSwitcherItem];
  }];
  [closeMenuElements addObject:closeTabAction];
  UIAction* closeOtherTabsAction =
      [actionFactory actionToCloseAllOtherTabsWithBlock:^{
        [weakSelf.mutator closeAllItemsExcept:tabSwitcherItem];
      }];
  [closeMenuElements addObject:closeOtherTabsAction];
  UIMenu* closeMenu = CreateDisplayInlineUIMenu(closeMenuElements);
  [menuElements addObject:closeMenu];

  return menuElements;
}

- (NSArray<UIMenuElement*>*)
    menuElementsForTabGroupItem:(TabGroupItem*)tabGroupItem
                     originView:(UIView*)originView
                   menuScenario:(MenuScenarioHistogram)scenario {
  // Record that this context menu was shown to the user.
  RecordMenuShown(scenario);
  ActionFactory* actionFactory =
      [[ActionFactory alloc] initWithScenario:scenario];
  __weak __typeof(self) weakSelf = self;

  NSMutableArray<UIMenuElement*>* menuElements = [[NSMutableArray alloc] init];

  // Add menu to edit group e.g. rename it, add a new tab to it or ungroup it.
  NSMutableArray<UIMenuElement*>* editGroupMenuElements =
      [[NSMutableArray alloc] init];

  base::WeakPtr<const TabGroup> tabGroup = tabGroupItem.tabGroup->GetWeakPtr();

  [editGroupMenuElements
      addObject:[actionFactory actionToRenameTabGroupWithBlock:^{
        [weakSelf.handler showTabStripGroupEditionForGroup:tabGroup];
      }]];
  [editGroupMenuElements
      addObject:[actionFactory actionToAddNewTabInGroupWithBlock:^{
        [weakSelf.mutator addNewTabInGroup:tabGroupItem];
      }]];
  [editGroupMenuElements
      addObject:[actionFactory actionToUngroupTabGroupWithBlock:^{
        [weakSelf.mutator ungroupGroup:tabGroupItem sourceView:originView];
      }]];
  UIMenu* editGroupMenu = CreateDisplayInlineUIMenu(editGroupMenuElements);
  [menuElements addObject:editGroupMenu];

  if (IsTabGroupSyncEnabled()) {
    [menuElements addObject:[actionFactory actionToCloseTabGroupWithBlock:^{
                    [weakSelf.mutator closeGroup:tabGroupItem];
                  }]];
    if (!self.incognito) {
      [menuElements addObject:[actionFactory actionToDeleteTabGroupWithBlock:^{
                      [weakSelf.mutator deleteGroup:tabGroupItem
                                         sourceView:originView];
                    }]];
    }
  } else {
    [menuElements addObject:[actionFactory actionToDeleteTabGroupWithBlock:^{
                    [weakSelf.mutator deleteGroup:tabGroupItem
                                       sourceView:originView];
                  }]];
  }

  return menuElements;
}

@end