chromium/chrome/browser/ui/cocoa/main_menu_builder.mm

// Copyright 2018 The Chromium Authors
// 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/main_menu_builder.h"

#include "base/feature_list.h"
#include "base/mac/mac_util.h"
#include "build/branding_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/cocoa/accelerators_cocoa.h"
#include "chrome/browser/ui/cocoa/history_menu_bridge.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/dom_distiller/core/dom_distiller_features.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
#include "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
#include "ui/strings/grit/ui_strings.h"

namespace chrome {
namespace {

using Item = internal::MenuItemBuilder;

NSMenuItem* BuildAppMenu(NSApplication* nsapp,
                         id app_delegate,
                         const std::u16string& product_name,
                         bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      // NB: The IDS_APP_MENU_PRODUCT_NAME string is not actually used to
      // determine what is displayed in bold in the menu bar as the app menu
      // title. The Info.plist's CFBundleName value is what is actually used.
      Item(IDS_APP_MENU_PRODUCT_NAME)
          .tag(IDC_CHROME_MENU)
          .submenu({
              Item(IDS_ABOUT_MAC)
                  .string_format_1(product_name)
                  .tag(IDC_ABOUT)
                  .target(app_delegate)
                  .action(@selector(orderFrontStandardAboutPanel:)),
              Item().is_separator(),
              Item(IDS_PREFERENCES)
                  .tag(IDC_OPTIONS)
                  .target(app_delegate)
                  .action(@selector(showPreferences:))
                  .remove_if(is_pwa),
              Item(IDS_PREFERENCES)
                  .command_id(IDC_WEB_APP_SETTINGS)
                  .remove_if(!is_pwa),
              Item().is_separator(),
              Item(IDS_CLEAR_BROWSING_DATA)
                  .command_id(IDC_CLEAR_BROWSING_DATA)
                  .remove_if(is_pwa),
              Item(IDS_IMPORT_SETTINGS_MENU_MAC)
                  .command_id(IDC_IMPORT_SETTINGS)
                  .remove_if(is_pwa),
              Item().is_separator(),
              Item(IDS_SERVICES_MAC)
                  .tag(-1)
                  .submenu({}),
              Item().is_separator(),
              Item(IDS_HIDE_APP_MAC)
                  .string_format_1(product_name)
                  .tag(IDC_HIDE_APP)
                  .action(@selector(hide:)),
              Item(IDS_HIDE_OTHERS_MAC)
                  .action(@selector(hideOtherApplications:))
                  .key_equivalent(@"h", NSEventModifierFlagCommand |
                                            NSEventModifierFlagOption),
              Item(IDS_SHOW_ALL_MAC)
                  .action(@selector(unhideAllApplications:)),
              Item().is_separator(),
              Item(IDS_CONFIRM_TO_QUIT_OPTION)
                  .target(app_delegate)
                  .action(@selector(toggleConfirmToQuit:))
                  .remove_if(is_pwa),
              Item().is_separator(),
              Item(IDS_EXIT_MAC)
                  .string_format_1(product_name)
                  .tag(IDC_EXIT)
                  .target(nsapp)
                  .action(@selector(terminate:)),
          })
          .Build();
  // clang-format on

  NSMenuItem* services_item = [item.submenu itemWithTag:-1];
  services_item.tag = 0;

  nsapp.servicesMenu = services_item.submenu;

  return item;
}

NSMenuItem* BuildFileMenu(NSApplication* nsapp,
                          id app_delegate,
                          const std::u16string& product_name,
                          bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      Item(IDS_FILE_MENU_MAC)
          .tag(IDC_FILE_MENU)
          .submenu({
              Item(IDS_NEW_TAB_MAC)
                  .command_id(IDC_NEW_TAB)
                  .remove_if(is_pwa),
              Item(IDS_NEW_WINDOW_MAC)
                  .command_id(IDC_NEW_WINDOW),
              Item(IDS_NEW_INCOGNITO_WINDOW_MAC)
                  .command_id(IDC_NEW_INCOGNITO_WINDOW)
                  .remove_if(is_pwa),
              Item(IDS_REOPEN_CLOSED_TABS_MAC)
                  .command_id(IDC_RESTORE_TAB)
                  .remove_if(is_pwa),
              Item(IDS_OPEN_FILE_MAC)
                  .command_id(IDC_OPEN_FILE)
                  .remove_if(is_pwa),
              Item(IDS_OPEN_LOCATION_MAC)
                  .command_id(IDC_FOCUS_LOCATION)
                  .remove_if(is_pwa),
              Item().is_separator(),
              Item(IDS_CLOSE_WINDOW_MAC)
                  .tag(IDC_CLOSE_WINDOW)
                  .action(@selector(performClose:)),
              Item(IDS_CLOSE_ALL_WINDOWS_MAC)
                  .action(@selector(closeAll:))
                  .is_alternate()
                  .key_equivalent(@"W", NSEventModifierFlagCommand |
                                            NSEventModifierFlagOption),
              Item(IDS_CLOSE_TAB_MAC)
                  .command_id(IDC_CLOSE_TAB)
                  .remove_if(is_pwa),
              Item(IDS_SAVE_PAGE_MAC)
                  .command_id(IDC_SAVE_PAGE),
              Item().is_separator()
                  .remove_if(is_pwa),
              Item(IDS_SHARE_MAC)
                  .remove_if(is_pwa),
              Item().is_separator(),
              Item(IDS_PRINT)
                  .command_id(IDC_PRINT),
              Item(IDS_PRINT_USING_SYSTEM_DIALOG_MAC)
                  .command_id(IDC_BASIC_PRINT)
                  .is_alternate()
                  .remove_if(is_pwa),
          })
          .Build();
  // clang-format on

  // The default key bindings assign Cmd-W to Close Tab, and Shift-Cmd-W to
  // Close Window. For PWAs, we skipped adding the Close Tab item, but Close
  // Window still has the Shift-Cmd-W shortcut. Remove Shift from the shortcut.
  if (is_pwa) {
    NSMenuItem* closeWindowMenuItem =
        [[item submenu] itemWithTag:IDC_CLOSE_WINDOW];
    // @"W" corresponds to the "Shift-W" portion of Shift-Cmd-W. We remove the
    // Shift by making the equivalent string lower case.
    closeWindowMenuItem.keyEquivalent = @"w";
  }

  return item;
}

NSMenuItem* BuildEditMenu(NSApplication* nsapp,
                          id app_delegate,
                          const std::u16string& product_name,
                          bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      Item(IDS_EDIT_MENU_MAC)
          .tag(IDC_EDIT_MENU)
          .submenu({
              Item(IDS_EDIT_UNDO_MAC)
                  .tag(IDC_CONTENT_CONTEXT_UNDO)
                  .action(@selector(undo:)),
              Item(IDS_EDIT_REDO_MAC)
                  .tag(IDC_CONTENT_CONTEXT_REDO)
                  .action(@selector(redo:)),
              Item().is_separator(),
              Item(IDS_CUT_MAC)
                  .tag(IDC_CONTENT_CONTEXT_CUT)
                  .action(@selector(cut:)),
              Item(IDS_COPY_MAC)
                  .tag(IDC_CONTENT_CONTEXT_COPY)
                  .action(@selector(copy:)),
              Item(IDS_PASTE_MAC)
                  .tag(IDC_CONTENT_CONTEXT_PASTE)
                  .action(@selector(paste:)),
              Item(IDS_PASTE_MATCH_STYLE_MAC)
                  .tag(IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE)
                  .action(@selector(pasteAndMatchStyle:)),
              Item(IDS_PASTE_MATCH_STYLE_MAC)
                  .action(@selector(pasteAndMatchStyle:))
                  .is_alternate()
                  .key_equivalent(@"V", NSEventModifierFlagCommand |
                                            NSEventModifierFlagOption),
              Item(IDS_EDIT_DELETE_MAC)
                  .tag(IDC_CONTENT_CONTEXT_DELETE)
                  .action(@selector(delete:)),
              Item(IDS_EDIT_SELECT_ALL_MAC)
                  .tag(IDC_CONTENT_CONTEXT_SELECTALL)
                  .action(@selector(selectAll:)),
              Item().is_separator(),
              Item(IDS_EDIT_FIND_SUBMENU_MAC)
                  .tag(IDC_FIND_MENU)
                  .submenu({
                      Item(IDS_EDIT_SEARCH_WEB_MAC)
                          .command_id(IDC_FOCUS_SEARCH),
                      Item().is_separator(),
                      Item(IDS_EDIT_FIND_MAC)
                          .command_id(IDC_FIND),
                      Item(IDS_EDIT_FIND_NEXT_MAC)
                          .command_id(IDC_FIND_NEXT),
                      Item(IDS_EDIT_FIND_PREVIOUS_MAC)
                          .command_id(IDC_FIND_PREVIOUS),
                      Item(IDS_EDIT_USE_SELECTION_MAC)
                          .action(@selector(copyToFindPboard:))
                          .key_equivalent(@"e", NSEventModifierFlagCommand),
                      Item(IDS_EDIT_JUMP_TO_SELECTION_MAC)
                          .action(@selector(centerSelectionInVisibleArea:))
                          .key_equivalent(@"j", NSEventModifierFlagCommand),
              }),
              Item(IDS_EDIT_SPELLING_GRAMMAR_MAC)
                  .tag(IDC_SPELLCHECK_MENU)
                  .submenu({
                      Item(IDS_EDIT_SHOW_SPELLING_GRAMMAR_MAC)
                          .action(@selector(showGuessPanel:))
                          .key_equivalent(@":", NSEventModifierFlagCommand),
                      Item(IDS_EDIT_CHECK_DOCUMENT_MAC)
                          .action(@selector(checkSpelling:))
                          .key_equivalent(@";", NSEventModifierFlagCommand),
                      Item(IDS_EDIT_CHECK_SPELLING_TYPING_MAC)
                          .action(@selector
                                  (toggleContinuousSpellChecking:)),
                      Item(IDS_EDIT_CHECK_GRAMMAR_MAC)
                          .action(@selector(toggleGrammarChecking:)),
                  }),
              Item(IDS_EDIT_SUBSTITUTIONS_MAC)
                  .submenu({
                      Item(IDS_EDIT_SHOW_SUBSTITUTIONS_MAC)
                          .action(@selector(orderFrontSubstitutionsPanel:)),
                      Item().is_separator(),
                      Item(IDS_EDIT_SMART_QUOTES_MAC)
                          .action(@selector(toggleAutomaticQuoteSubstitution:)),
                      Item(IDS_EDIT_SMART_DASHES_MAC)
                          .action(@selector(toggleAutomaticDashSubstitution:)),
                      Item(IDS_EDIT_TEXT_REPLACEMENT_MAC)
                          .action(@selector(toggleAutomaticTextReplacement:)),
              }),
              Item(IDS_EDIT_TRANSFORMATIONS_MAC)
                  .submenu({
                      Item(IDS_EDIT_MAKE_UPPERCASE_MAC)
                          .action(@selector(uppercaseWord:)),
                      Item(IDS_EDIT_MAKE_LOWERCASE_MAC)
                          .action(@selector(lowercaseWord:)),
                      Item(IDS_EDIT_CAPITALIZE_MAC)
                          .action(@selector(capitalizeWord:)),
              }),
              Item(IDS_SPEECH_MAC)
                  .submenu({
                      Item(IDS_SPEECH_START_SPEAKING_MAC)
                          .action(@selector(startSpeaking:)),
                      Item(IDS_SPEECH_STOP_SPEAKING_MAC)
                          .action(@selector(stopSpeaking:)),
              }),
            // The "Start Dictation..." and "Emoji & Symbols" items are
            // inserted by AppKit.
          })
          .Build();
  // clang-format on
  return item;
}

NSMenuItem* BuildViewMenu(NSApplication* nsapp,
                          id app_delegate,
                          const std::u16string& product_name,
                          bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      Item(IDS_VIEW_MENU_MAC)
          .tag(IDC_VIEW_MENU)
          .submenu({
              Item(IDS_BOOKMARK_BAR_ALWAYS_SHOW_MAC)
                  .command_id(IDC_SHOW_BOOKMARK_BAR)
                  .remove_if(is_pwa),
              Item(IDS_TOGGLE_FULLSCREEN_TOOLBAR_MAC)
                  .command_id(IDC_TOGGLE_FULLSCREEN_TOOLBAR),
              Item(IDS_CONTEXT_MENU_SHOW_FULL_URLS)
                  .command_id(IDC_SHOW_FULL_URLS),
              Item(IDS_CONTEXT_MENU_SHOW_GOOGLE_LENS_SHORTCUT)
                  .command_id(IDC_SHOW_GOOGLE_LENS_SHORTCUT),
              Item(IDS_CUSTOMIZE_TOUCH_BAR)
                  .tag(IDC_CUSTOMIZE_TOUCH_BAR)
                  .action(@selector(toggleTouchBarCustomizationPalette:))
                  .remove_if(is_pwa),
              Item().is_separator(),
              Item(IDS_STOP_MENU_MAC)
                  .command_id(IDC_STOP),
              Item(IDS_RELOAD_MENU_MAC)
                  .command_id(IDC_RELOAD),
              Item(IDS_RELOAD_BYPASSING_CACHE_MENU_MAC)
                  .command_id(IDC_RELOAD_BYPASSING_CACHE)
                  .is_alternate(),
              Item().is_separator(),
              Item(IDS_ENTER_FULLSCREEN_MAC)
                  .tag(IDC_FULLSCREEN)
                  .action(@selector(toggleFullScreen:)),
              Item(IDS_ENTER_FULLSCREEN_MAC)
                  .action(@selector(toggleFullScreen:))
                  .is_alternate()
                  .key_equivalent(@"f", NSEventModifierFlagCommand |
                                            NSEventModifierFlagControl),
              Item(IDS_TEXT_DEFAULT_MAC)
                  .command_id(IDC_ZOOM_NORMAL),
              Item(IDS_TEXT_BIGGER_MAC)
                  .command_id(IDC_ZOOM_PLUS),
              Item(IDS_TEXT_SMALLER_MAC)
                  .command_id(IDC_ZOOM_MINUS),
              Item().is_separator(),
              Item(IDS_MEDIA_ROUTER_MENU_ITEM_TITLE)
                  .command_id(IDC_ROUTE_MEDIA),
              Item().is_separator(),
              Item(IDS_DEVELOPER_MENU_MAC)
                  .tag(IDC_DEVELOPER_MENU)
                  .submenu({
                      Item(IDS_VIEW_SOURCE_MAC)
                          .command_id(IDC_VIEW_SOURCE),
                      Item(IDS_DEV_TOOLS_MAC)
                          .command_id(IDC_DEV_TOOLS),
                      Item(IDS_DEV_TOOLS_ELEMENTS_MAC)
                          .command_id(IDC_DEV_TOOLS_INSPECT),
                      Item(IDS_DEV_TOOLS_CONSOLE_MAC)
                          .command_id(IDC_DEV_TOOLS_CONSOLE),
                      Item(IDS_ALLOW_JAVASCRIPT_APPLE_EVENTS_MAC)
                          .command_id(IDC_TOGGLE_JAVASCRIPT_APPLE_EVENTS),
                  }),
        })
        .Build();
  // clang-format on
  return item;
}

NSMenuItem* BuildHistoryMenu(NSApplication* nsapp,
                             id app_delegate,
                             const std::u16string& product_name,
                             bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      Item(IDS_HISTORY_MENU_MAC)
          .tag(IDC_HISTORY_MENU)
          .submenu({
              Item(IDS_HISTORY_HOME_MAC)
                  .command_id(IDC_HOME)
                  .remove_if(is_pwa),
              Item(IDS_HISTORY_BACK_MAC)
                  .command_id(IDC_BACK),
              Item(IDS_HISTORY_FORWARD_MAC)
                  .command_id(IDC_FORWARD),
              Item().is_separator()
                  .tag(HistoryMenuBridge::kRecentlyClosedSeparator)
                  .remove_if(is_pwa),
              Item(IDS_HISTORY_CLOSED_MAC)
                  .tag(HistoryMenuBridge::kRecentlyClosedTitle)
                  .is_section_header()
                  .remove_if(is_pwa),
              Item().is_separator()
                  .tag(HistoryMenuBridge::kVisitedSeparator)
                  .remove_if(is_pwa),
              Item(IDS_HISTORY_VISITED_MAC)
                  .tag(HistoryMenuBridge::kVisitedTitle)
                  .is_section_header()
                  .remove_if(is_pwa),
              Item().is_separator()
                  .tag(HistoryMenuBridge::kShowFullSeparator)
                  .remove_if(is_pwa),
              Item(IDS_HISTORY_SHOWFULLHISTORY_LINK)
                  .command_id(IDC_SHOW_HISTORY)
                  .remove_if(is_pwa),
          })
          .Build();
  // clang-format on
  return item;
}

NSMenuItem* BuildBookmarksMenu(NSApplication* nsapp,
                               id app_delegate,
                               const std::u16string& product_name,
                               bool is_pwa) {
  if (is_pwa) {
    return nil;
  }

  // clang-format off
  NSMenuItem* item =
      Item(IDS_BOOKMARKS_MENU)
          .tag(IDC_BOOKMARKS_MENU)
          .submenu({
              Item(IDS_BOOKMARK_MANAGER)
                  .command_id(IDC_SHOW_BOOKMARK_MANAGER),
              Item().is_separator()
                  .tag(IDC_BOOKMARK_THIS_TAB),
              Item(IDS_BOOKMARK_THIS_TAB)
                  .command_id(IDC_BOOKMARK_THIS_TAB),
              Item(IDS_BOOKMARK_ALL_TABS)
                  .command_id(IDC_BOOKMARK_ALL_TABS),
              Item().is_separator()
                  .tag(IDC_BOOKMARK_THIS_TAB),
          })
          .Build();
  // clang-format on
  return item;
}

NSMenuItem* BuildPeopleMenu(NSApplication* nsapp,
                            id app_delegate,
                            const std::u16string& product_name,
                            bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      Item(IDS_PROFILES_MENU_NAME)
          .tag(IDC_PROFILE_MAIN_MENU)
          .submenu({})
          .Build();
  // clang-format on
  return item;
}

NSMenuItem* BuildWindowMenu(NSApplication* nsapp,
                            id app_delegate,
                            const std::u16string& product_name,
                            bool is_pwa) {
  // clang-format off
  NSMenuItem* item =
      Item(IDS_WINDOW_MENU_MAC)
          .tag(IDC_WINDOW_MENU)
          .submenu({
              Item(IDS_MINIMIZE_WINDOW_MAC)
                  .tag(IDC_MINIMIZE_WINDOW)
                  .action(@selector(performMiniaturize:)),
              Item(IDS_ZOOM_WINDOW_MAC)
                  .tag(IDC_MAXIMIZE_WINDOW)
                  .action(@selector(performZoom:)),
              Item().is_separator(),
              Item(IDS_SHOW_AS_TAB)
                  .command_id(IDC_SHOW_AS_TAB)
                  .remove_if(is_pwa),
              Item(IDS_NAME_WINDOW)
                  .command_id(IDC_NAME_WINDOW)
                  .remove_if(is_pwa),
              Item().is_separator()
                  .remove_if(is_pwa),
              Item(IDS_SHOW_DOWNLOADS_MAC)
                  .command_id(IDC_SHOW_DOWNLOADS)
                  .remove_if(is_pwa),
              Item(IDS_SHOW_EXTENSIONS_MAC)
                  .command_id(IDC_MANAGE_EXTENSIONS)
                  .remove_if(is_pwa),
              Item(IDS_TASK_MANAGER_MAC)
                  .command_id(IDC_TASK_MANAGER)
                  .remove_if(is_pwa),
              Item().is_separator()
                  .remove_if(is_pwa),
              Item(IDS_ALL_WINDOWS_FRONT_MAC)
                  .tag(IDC_ALL_WINDOWS_FRONT)
                  .action(@selector(arrangeInFront:)),
              Item().is_separator(),
          })
          .Build();
  // clang-format on
  nsapp.windowsMenu = item.submenu;
  return item;
}

NSMenuItem* BuildTabMenu(NSApplication* nsapp,
                         id app_delegate,
                         const std::u16string& product_name,
                         bool is_pwa) {
  if (is_pwa) {
    return nil;
  }

  // clang-format off
  NSMenuItem* item =
      Item(IDS_TAB_MENU_MAC)
          .tag(IDC_TAB_MENU)
          .submenu({
              Item(IDS_TAB_CXMENU_NEWTABTORIGHT)
                  .command_id(IDC_NEW_TAB_TO_RIGHT),
              Item(IDS_NEXT_TAB_MAC)
                  .command_id(IDC_SELECT_NEXT_TAB),
              Item(IDS_PREV_TAB_MAC)
                  .command_id(IDC_SELECT_PREVIOUS_TAB),
              Item(IDS_DUPLICATE_TAB_MAC)
                  .command_id(IDC_DUPLICATE_TAB),
              Item(IDS_DUPLICATE_TARGET_TAB_MAC)
                  .command_id(IDC_DUPLICATE_TARGET_TAB)
                  .is_alternate()
                  .key_equivalent(@"", NSEventModifierFlagOption),
              Item(IDS_MUTE_SITE_MAC)
                  .command_id(IDC_WINDOW_MUTE_SITE),
              Item(IDS_MUTE_TARGET_SITE_MAC)
                  .command_id(IDC_MUTE_TARGET_SITE)
                  .is_alternate()
                  .key_equivalent(@"", NSEventModifierFlagOption),
              Item(IDS_PIN_TAB_MAC)
                  .command_id(IDC_WINDOW_PIN_TAB),
              Item(IDS_PIN_TARGET_TAB_MAC)
                  .command_id(IDC_PIN_TARGET_TAB)
                  .is_alternate()
                  .key_equivalent(@"", NSEventModifierFlagOption),
              Item(IDS_GROUP_TAB_MAC)
                  .command_id(IDC_WINDOW_GROUP_TAB),
              Item(IDS_GROUP_TARGET_TAB_MAC)
                  .command_id(IDC_GROUP_TARGET_TAB)
                  .is_alternate()
                  .key_equivalent(@"", NSEventModifierFlagOption),
              Item(IDS_TAB_CXMENU_CLOSEOTHERTABS)
                  .command_id(IDC_WINDOW_CLOSE_OTHER_TABS),
              Item(IDS_TAB_CXMENU_CLOSETABSTORIGHT)
                  .command_id(IDC_WINDOW_CLOSE_TABS_TO_RIGHT),
              Item(IDS_MOVE_TAB_TO_NEW_WINDOW)
                  .command_id(IDC_MOVE_TAB_TO_NEW_WINDOW),
              Item(IDS_SEARCH_TABS)
                  .command_id(IDC_TAB_SEARCH),
              Item().is_separator(),
          })
          .Build();
  // clang-format on
  return item;
}

NSMenuItem* BuildHelpMenu(NSApplication* nsapp,
                          id app_delegate,
                          const std::u16string& product_name,
                          bool is_pwa) {
  if (is_pwa) {
    return nil;
  }

  // clang-format off
  NSMenuItem* item =
      Item(IDS_HELP_MENU_MAC)
          .submenu({
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
              Item(IDS_FEEDBACK_MAC)
                  .command_id(IDC_FEEDBACK),
#endif
              Item(IDS_HELP_MAC)
                  .string_format_1(product_name)
                  .command_id(IDC_HELP_PAGE_VIA_MENU),
          })
          .Build();
  // clang-format on

  nsapp.helpMenu = item.submenu;
  return item;
}

}  // namespace

void BuildMainMenu(NSApplication* nsapp,
                   id<NSApplicationDelegate> app_delegate,
                   const std::u16string& product_name,
                   bool is_pwa) {
  NSMenu* main_menu = [[NSMenu alloc] initWithTitle:@""];
  for (auto* builder : {
           &BuildAppMenu,
           &BuildFileMenu,
           &BuildEditMenu,
           &BuildViewMenu,
           &BuildHistoryMenu,
           &BuildBookmarksMenu,
           &BuildPeopleMenu,
           &BuildTabMenu,
           &BuildWindowMenu,
           &BuildHelpMenu,
       }) {
    auto item = builder(nsapp, app_delegate, product_name, is_pwa);
    if (item) {
      [main_menu addItem:item];
    }
  }

  nsapp.mainMenu = main_menu;
}

NSMenuItem* BuildFileMenuForTesting(bool is_pwa) {
  return BuildFileMenu(nil, nil, u"", is_pwa);
}

namespace internal {

MenuItemBuilder::MenuItemBuilder(int string_id) : string_id_(string_id) {}

MenuItemBuilder::MenuItemBuilder(const MenuItemBuilder&) = default;

MenuItemBuilder& MenuItemBuilder::operator=(const MenuItemBuilder&) = default;

MenuItemBuilder::~MenuItemBuilder() = default;

NSMenuItem* MenuItemBuilder::Build() const {
  if (is_removed_) {
    return nil;
  }

  if (is_separator_) {
    NSMenuItem* item = [NSMenuItem separatorItem];
    if (tag_) {
      item.tag = tag_;
    }
    item.hidden = is_hidden_;
    return item;
  }

  NSString* title;
  if (!string_arg1_.empty()) {
    title = l10n_util::GetNSStringFWithFixup(string_id_, string_arg1_);
  } else {
    title = l10n_util::GetNSStringWithFixup(string_id_);
  }

  if (is_section_header_) {
    NSMenuItem* item;
    if (@available(macOS 14, *)) {
      item = [NSMenuItem sectionHeaderWithTitle:title];
    } else {
      item = [[NSMenuItem alloc] initWithTitle:title
                                        action:nil
                                 keyEquivalent:@""];
      item.enabled = NO;
    }
    if (tag_) {
      item.tag = tag_;
    }
    item.hidden = is_hidden_;
    return item;
  }

  // If the item is command-dispatched, look up the relevant key equivalent
  // from the accelerator table. Otherwise, use the builder-specified key
  // equivalent.
  NSString* key_equivalent = key_equivalent_;
  NSEventModifierFlags key_equivalent_flags = key_equivalent_flags_;
  if (tag_ != 0) {
    if (const ui::Accelerator* accelerator =
            AcceleratorsCocoa::GetInstance()->GetAcceleratorForCommand(tag_)) {
      KeyEquivalentAndModifierMask* equivalent =
          GetKeyEquivalentAndModifierMaskFromAccelerator(*accelerator);
      key_equivalent = equivalent.keyEquivalent;
      key_equivalent_flags = equivalent.modifierMask;
    }
  }

  SEL action = !submenu_.has_value() ? action_ : nil;

  NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title
                                                action:action
                                         keyEquivalent:key_equivalent];
  item.target = target_;
  item.tag = tag_;
  item.keyEquivalentModifierMask = key_equivalent_flags;
  item.alternate = is_alternate_;
  item.hidden = is_hidden_;

  if (submenu_.has_value()) {
    NSMenu* menu = [[NSMenu alloc] initWithTitle:title];
    for (const auto& subitem : submenu_.value()) {
      NSMenuItem* ns_subitem = subitem.Build();
      if (ns_subitem) {
        [menu addItem:ns_subitem];
      }
    }
    item.submenu = menu;
  }

  return item;
}

}  // namespace internal
}  // namespace chrome