chromium/chrome/browser/apps/app_service/menu_util.cc

// 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.

#include "chrome/browser/apps/app_service/menu_util.h"

#include <string_view>
#include <utility>

#include "ash/public/cpp/app_menu_constants.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/common/features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/color/color_id.h"
#include "ui/gfx/image/image_skia.h"

namespace {

const int kInvalidRadioGroupId = -1;
const int kGroupId = 1;

apps::MenuItemPtr CreateRadioItem(uint32_t command_id,
                                  uint32_t string_id,
                                  int group_id) {
  apps::MenuItemPtr menu_item =
      std::make_unique<apps::MenuItem>(apps::MenuItemType::kRadio, command_id);
  menu_item->string_id = string_id;
  menu_item->radio_group_id = group_id;
  return menu_item;
}

void PopulateRadioItemFromMenuItems(
    const std::vector<apps::MenuItemPtr>& menu_items,
    ui::SimpleMenuModel* model) {
  for (const auto& item : menu_items) {
    DCHECK_EQ(apps::MenuItemType::kRadio, item->type);
    model->AddRadioItem(item->command_id,
                        l10n_util::GetStringUTF16(item->string_id),
                        item->radio_group_id);
  }
}

}  // namespace

namespace apps {

DEFINE_ELEMENT_IDENTIFIER_VALUE(kLaunchNewMenuItem);

void AddCommandItem(uint32_t command_id,
                    uint32_t string_id,
                    MenuItems& menu_items) {
  MenuItemPtr menu_item =
      std::make_unique<MenuItem>(MenuItemType::kCommand, command_id);
  menu_item->string_id = string_id;
  menu_item->radio_group_id = kInvalidRadioGroupId;
  menu_items.items.push_back(std::move(menu_item));
}

void AddSeparator(ui::MenuSeparatorType separator_type, MenuItems& menu_items) {
  MenuItemPtr menu_item =
      std::make_unique<MenuItem>(MenuItemType::kSeparator, separator_type);
  menu_items.items.push_back(std::move(menu_item));
}

void AddShortcutCommandItem(int command_id,
                            const std::string& shortcut_id,
                            const std::string& label,
                            const gfx::ImageSkia& icon,
                            MenuItems& menu_items) {
  MenuItemPtr menu_item =
      std::make_unique<MenuItem>(MenuItemType::kPublisherCommand, command_id);
  menu_item->shortcut_id = shortcut_id;
  menu_item->label = label;
  menu_item->image = icon;
  menu_items.items.push_back(std::move(menu_item));
}

void CreateOpenNewSubmenu(uint32_t string_id, MenuItems& menu_items) {
  MenuItemPtr menu_item =
      std::make_unique<MenuItem>(MenuItemType::kSubmenu, ash::LAUNCH_NEW);
  menu_item->string_id = string_id;

  menu_item->submenu.push_back(CreateRadioItem(
      ash::USE_LAUNCH_TYPE_REGULAR,
      StringIdForUseLaunchTypeCommand(ash::USE_LAUNCH_TYPE_REGULAR), kGroupId));
  menu_item->submenu.push_back(CreateRadioItem(
      ash::USE_LAUNCH_TYPE_WINDOW,
      StringIdForUseLaunchTypeCommand(ash::USE_LAUNCH_TYPE_WINDOW), kGroupId));
  if (base::FeatureList::IsEnabled(blink::features::kDesktopPWAsTabStrip) &&
      base::FeatureList::IsEnabled(features::kDesktopPWAsTabStripSettings)) {
    menu_item->submenu.push_back(CreateRadioItem(
        ash::USE_LAUNCH_TYPE_TABBED_WINDOW,
        StringIdForUseLaunchTypeCommand(ash::USE_LAUNCH_TYPE_TABBED_WINDOW),
        kGroupId));
  }

  menu_item->radio_group_id = kInvalidRadioGroupId;

  menu_items.items.push_back(std::move(menu_item));
}

bool ShouldAddOpenItem(const std::string& app_id,
                       MenuType menu_type,
                       Profile* profile) {
  if (menu_type != MenuType::kShelf) {
    return false;
  }

  return !apps::AppServiceProxyFactory::GetForProfile(profile)
              ->InstanceRegistry()
              .ContainsAppId(app_id);
}

bool ShouldAddCloseItem(const std::string& app_id,
                        MenuType menu_type,
                        Profile* profile) {
  if (menu_type != MenuType::kShelf) {
    return false;
  }

  bool can_close = true;
  apps::AppServiceProxyFactory::GetForProfile(profile)
      ->AppRegistryCache()
      .ForOneApp(app_id, [&can_close](const apps::AppUpdate& update) {
        can_close = update.AllowClose().value_or(true);
      });

  return can_close && apps::AppServiceProxyFactory::GetForProfile(profile)
                          ->InstanceRegistry()
                          .ContainsAppId(app_id);
}

void PopulateLaunchNewItemFromMenuItem(const MenuItemPtr& menu_item,
                                       ui::SimpleMenuModel* model,
                                       ui::SimpleMenuModel* submenu,
                                       int* launch_new_string_id) {
  DCHECK_EQ(menu_item->command_id, ash::LAUNCH_NEW);

  if (launch_new_string_id) {
    *launch_new_string_id = menu_item->string_id;
  }

  switch (menu_item->type) {
    case apps::MenuItemType::kCommand: {
      model->AddItemWithStringId(menu_item->command_id, menu_item->string_id);
      model->SetElementIdentifierAt(
          model->GetIndexOfCommandId(menu_item->command_id).value(),
          kLaunchNewMenuItem);
      break;
    }
    case apps::MenuItemType::kSubmenu:
      if (!menu_item->submenu.empty()) {
        PopulateRadioItemFromMenuItems(menu_item->submenu, submenu);
        model->AddActionableSubMenu(
            menu_item->command_id,
            l10n_util::GetStringUTF16(menu_item->string_id), submenu);
      }
      break;
    case apps::MenuItemType::kRadio:
    case apps::MenuItemType::kSeparator:
    case apps::MenuItemType::kPublisherCommand:
      NOTREACHED_IN_MIGRATION();
  }
}

void PopulateItemFromMenuItem(const apps::MenuItemPtr& item,
                              ui::SimpleMenuModel* model,
                              apps::AppShortcutItems* arc_shortcut_items) {
  switch (item->type) {
    case apps::MenuItemType::kSeparator:
      model->AddSeparator(static_cast<ui::MenuSeparatorType>(item->command_id));
      break;
    case apps::MenuItemType::kPublisherCommand: {
      model->AddItemWithIcon(item->command_id, base::UTF8ToUTF16(item->label),
                             ui::ImageModel::FromImageSkia(item->image));
      apps::AppShortcutItem arc_shortcut_item;
      arc_shortcut_item.shortcut_id = item->shortcut_id;
      arc_shortcut_items->push_back(arc_shortcut_item);
      break;
    }
    case apps::MenuItemType::kCommand:
    case apps::MenuItemType::kRadio:
    case apps::MenuItemType::kSubmenu:
      NOTREACHED_IN_MIGRATION();
      break;
  }
}

std::string_view MenuTypeToString(MenuType menu_type) {
  switch (menu_type) {
    case MenuType::kShelf:
      return "shelf";
    case MenuType::kAppList:
      return "applist";
  }
}

MenuType MenuTypeFromString(std::string_view menu_type) {
  if (base::EqualsCaseInsensitiveASCII(menu_type, "shelf")) {
    return MenuType::kShelf;
  }
  if (base::EqualsCaseInsensitiveASCII(menu_type, "applist")) {
    return MenuType::kAppList;
  }
  return MenuType::kShelf;
}

MenuItems CreateBrowserMenuItems(const Profile* profile) {
  DCHECK(profile);
  MenuItems menu_items;

  // "Normal" windows are not allowed when incognito is enforced.
  if (IncognitoModePrefs::GetAvailability(profile->GetPrefs()) !=
      policy::IncognitoModeAvailability::kForced) {
    AddCommandItem(ash::APP_CONTEXT_MENU_NEW_WINDOW, IDS_APP_LIST_NEW_WINDOW,
                   menu_items);
  }

  // Incognito windows are not allowed when incognito is disabled.
  if (!profile->IsOffTheRecord() &&
      IncognitoModePrefs::GetAvailability(profile->GetPrefs()) !=
          policy::IncognitoModeAvailability::kDisabled) {
    AddCommandItem(ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW,
                   IDS_APP_LIST_NEW_INCOGNITO_WINDOW, menu_items);
  }

  AddCommandItem(ash::SHOW_APP_INFO, IDS_APP_CONTEXT_MENU_SHOW_INFO,
                 menu_items);

  return menu_items;
}

ui::ColorId GetColorIdForMenuItemIcon() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  return ui::kColorAshSystemUIMenuIcon;
#else
  return ui::kColorMenuIcon;
#endif
}

uint32_t StringIdForUseLaunchTypeCommand(uint32_t command_id) {
  DCHECK(command_id >= ash::USE_LAUNCH_TYPE_COMMAND_START &&
         command_id < ash::USE_LAUNCH_TYPE_COMMAND_END);
  switch (command_id) {
    case ash::USE_LAUNCH_TYPE_REGULAR:
      return IDS_APP_LIST_CONTEXT_MENU_NEW_TAB;
    case ash::USE_LAUNCH_TYPE_WINDOW:
      return IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW;
    case ash::USE_LAUNCH_TYPE_TABBED_WINDOW:
      return IDS_APP_LIST_CONTEXT_MENU_NEW_TABBED_WINDOW;
    case ash::DEPRECATED_USE_LAUNCH_TYPE_PINNED:
    case ash::DEPRECATED_USE_LAUNCH_TYPE_FULLSCREEN:
      [[fallthrough]];
    default:
      NOTREACHED_IN_MIGRATION();
      return 0;
  }
}

}  // namespace apps