blob: 9670f822123884ecd73ce78f99f1911b6fa68a0e [file] [log] [blame]
// Copyright 2013 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/ui/views/frame/system_menu_model_builder.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/menus/simple_menu_model.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/multi_user/multi_user_window_manager.h"
#include "ash/shell.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/frame/desks/move_to_desks_menu_delegate.h"
#include "chromeos/ui/frame/desks/move_to_desks_menu_model.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/widget/widget.h"
#endif
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_CHROMEOS)
#include "ui/ozone/public/ozone_platform.h"
#endif
#if BUILDFLAG(ENABLE_GLIC)
#include "chrome/browser/glic/public/glic_enabling.h"
#endif
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SystemMenuModelBuilder,
kToggleVerticalTabsElementId);
SystemMenuModelBuilder::SystemMenuModelBuilder(
ui::AcceleratorProvider* provider,
Browser* browser)
: menu_delegate_(provider, browser) {}
SystemMenuModelBuilder::~SystemMenuModelBuilder() = default;
void SystemMenuModelBuilder::Init() {
ui::SimpleMenuModel* model = new ui::SimpleMenuModel(&menu_delegate_);
menu_model_.reset(model);
BuildMenu(model);
#if BUILDFLAG(IS_WIN)
// On Windows we put the menu items in the system menu (not at the end). Doing
// this necessitates adding a trailing separator.
model->AddSeparator(ui::NORMAL_SEPARATOR);
#endif
}
void SystemMenuModelBuilder::BuildMenu(ui::SimpleMenuModel* model) {
// We add the menu items in reverse order so that insertion_index never needs
// to change.
if (browser()->is_type_normal()) {
BuildSystemMenuForBrowserWindow(model);
} else {
BuildSystemMenuForAppOrPopupWindow(model);
}
}
void SystemMenuModelBuilder::BuildSystemMenuForBrowserWindow(
ui::SimpleMenuModel* model) {
#if BUILDFLAG(IS_LINUX)
model->AddItemWithStringId(IDC_MINIMIZE_WINDOW, IDS_MINIMIZE_WINDOW_MENU);
model->AddItemWithStringId(IDC_MAXIMIZE_WINDOW, IDS_MAXIMIZE_WINDOW_MENU);
model->AddItemWithStringId(IDC_RESTORE_WINDOW, IDS_RESTORE_WINDOW_MENU);
model->AddSeparator(ui::NORMAL_SEPARATOR);
#endif
model->AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
model->AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB);
if (features::IsTabGroupMenuMoreEntryPointsEnabled()) {
model->AddItemWithStringId(IDC_GROUP_UNGROUPED_TABS,
IDS_GROUP_UNGROUPED_TABS);
}
model->AddItemWithStringId(IDC_BOOKMARK_ALL_TABS, IDS_BOOKMARK_ALL_TABS);
model->AddItemWithStringId(IDC_NAME_WINDOW, IDS_NAME_WINDOW);
#if BUILDFLAG(ENABLE_GLIC)
#if BUILDFLAG(IS_WIN)
// On Windows we can not remove an item when showing the menu. So only add
// the glic toggle option if glic is enabled when building the menu.
if (glic::GlicEnabling::IsEnabledForProfile(browser()->profile())) {
#endif // BUILDFLAG(IS_WIN)
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_GLIC_TOGGLE_PIN, IDS_GLIC_PIN);
#if BUILDFLAG(IS_WIN)
}
#endif // BUILDFLAG(IS_WIN)
#endif // BUILDFLAG(ENABLE_GLIC)
if (tabs::IsVerticalTabsFeatureEnabled()) {
model->AddSeparator(ui::NORMAL_SEPARATOR);
if (browser()
->browser_window_features()
->vertical_tab_strip_state_controller()
->ShouldDisplayVerticalTabs()) {
model->AddItemWithStringId(IDC_TOGGLE_VERTICAL_TABS,
IDS_SWITCH_TO_HORIZONTAL_TAB);
} else {
model->AddItemWithStringId(IDC_TOGGLE_VERTICAL_TABS,
IDS_SWITCH_TO_VERTICAL_TAB);
}
model->SetElementIdentifierAt(model->GetItemCount() - 1,
kToggleVerticalTabsElementId);
}
if (chrome::CanOpenTaskManager()) {
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_TASK_MANAGER_CONTEXT_MENU, IDS_TASK_MANAGER);
}
#if BUILDFLAG(IS_LINUX)
model->AddSeparator(ui::NORMAL_SEPARATOR);
bool supports_server_side_decorations = true;
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_CHROMEOS)
supports_server_side_decorations =
ui::OzonePlatform::GetInstance()
->GetPlatformRuntimeProperties()
.supports_server_side_window_decorations;
#endif
if (supports_server_side_decorations) {
model->AddCheckItemWithStringId(IDC_USE_SYSTEM_TITLE_BAR,
IDS_SHOW_WINDOW_DECORATIONS_MENU);
}
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE_WINDOW_MENU);
#endif
#if BUILDFLAG(IS_CHROMEOS)
AppendMoveToDesksMenu(model);
#endif
AppendTeleportMenu(model);
// If it's a regular browser window with tabs, we don't add any more items,
// since it already has menus (Page, Chrome).
}
void SystemMenuModelBuilder::BuildSystemMenuForAppOrPopupWindow(
ui::SimpleMenuModel* model) {
model->AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK);
model->AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD);
model->AddItemWithStringId(IDC_RELOAD, IDS_APP_MENU_RELOAD);
if (!web_app::AppBrowserController::IsWebApp(browser())) {
bool is_captive_portal_signin = false;
#if BUILDFLAG(IS_CHROMEOS)
is_captive_portal_signin =
browser()->profile()->IsOffTheRecord() &&
browser()->profile()->GetOTRProfileID().IsCaptivePortal();
#endif
if (!is_captive_portal_signin) {
model->AddSeparator(ui::NORMAL_SEPARATOR);
if (browser()->is_type_app() || browser()->is_type_app_popup()) {
model->AddItemWithStringId(IDC_NEW_TAB, IDS_APP_MENU_NEW_WEB_PAGE);
} else {
model->AddItemWithStringId(IDC_SHOW_AS_TAB, IDS_SHOW_AS_TAB);
}
}
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_CUT, IDS_CUT);
model->AddItemWithStringId(IDC_COPY, IDS_COPY);
model->AddItemWithStringId(IDC_PASTE, IDS_PASTE);
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_FIND, IDS_FIND);
model->AddItemWithStringId(IDC_PRINT, IDS_PRINT);
zoom_menu_contents_ =
std::make_unique<ui::SimpleMenuModel>(&menu_delegate_);
zoom_menu_contents_->AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS);
zoom_menu_contents_->AddItemWithStringId(IDC_ZOOM_NORMAL, IDS_ZOOM_NORMAL);
zoom_menu_contents_->AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS);
model->AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_ZOOM_MENU,
zoom_menu_contents_.get());
}
bool should_show_task_manager =
(browser()->is_type_app() || browser()->is_type_app_popup()) &&
chrome::CanOpenTaskManager();
#if BUILDFLAG(IS_CHROMEOS)
// Hide TaskManager option for the app if it is locked for OnTask. Only
// relevant for non-web browser scenarios.
if (browser()->IsLockedForOnTask()) {
should_show_task_manager = false;
}
#endif
if (should_show_task_manager) {
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
}
#if BUILDFLAG(IS_LINUX)
model->AddSeparator(ui::NORMAL_SEPARATOR);
model->AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE);
#endif
#if BUILDFLAG(IS_CHROMEOS)
AppendMoveToDesksMenu(model);
#endif
AppendTeleportMenu(model);
}
#if BUILDFLAG(IS_CHROMEOS)
void SystemMenuModelBuilder::AppendMoveToDesksMenu(ui::SimpleMenuModel* model) {
auto* const browser = menu_delegate_.browser();
gfx::NativeWindow window = browser->window()->GetNativeWindow();
// Do not show the move to desks menu if the app is locked for OnTask. Only
// relevant for non-web browser scenarios.
if (browser->IsLockedForOnTask() ||
!chromeos::MoveToDesksMenuDelegate::ShouldShowMoveToDesksMenu(window)) {
return;
}
model->AddSeparator(ui::NORMAL_SEPARATOR);
move_to_desks_model_ = std::make_unique<chromeos::MoveToDesksMenuModel>(
std::make_unique<chromeos::MoveToDesksMenuDelegate>(
views::Widget::GetWidgetForNativeWindow(window)));
model->AddSubMenuWithStringId(chromeos::MoveToDesksMenuModel::kMenuCommandId,
IDS_MOVE_TO_DESKS_MENU,
move_to_desks_model_.get());
}
#endif
void SystemMenuModelBuilder::AppendTeleportMenu(ui::SimpleMenuModel* model) {
#if BUILDFLAG(IS_CHROMEOS)
DCHECK(browser()->window());
// Avoid appending the teleport menu for the settings window. This window's
// presentation is unique: it's a normal browser window with an app-like
// frame, which doesn't have a user icon badge. Thus if teleported it's not
// clear what user it applies to. Rather than bother to implement badging just
// for this rare case, simply prevent the user from teleporting the window.
if (chrome::SettingsWindowManager::GetInstance()->IsSettingsBrowser(
browser())) {
return;
}
// Don't show the menu for incognito windows.
if (browser()->profile()->IsOffTheRecord()) {
return;
}
// To show the menu we need at least two logged in users.
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
const user_manager::UserList logged_in_users =
user_manager->GetLRULoggedInUsers();
if (logged_in_users.size() <= 1u) {
return;
}
// If this does not belong to a profile or there is no window, or the window
// is not owned by anyone, we don't show the menu addition.
auto* window_manager = ash::Shell::Get()->multi_user_window_manager();
const AccountId account_id =
multi_user_util::GetAccountIdFromProfile(browser()->profile());
aura::Window* window = browser()->window()->GetNativeWindow();
if (!account_id.is_valid() || !window ||
!window_manager->GetWindowOwner(window).is_valid()) {
return;
}
model->AddSeparator(ui::NORMAL_SEPARATOR);
int command_id = IDC_VISIT_DESKTOP_OF_LRU_USER_NEXT;
for (size_t user_index = 1; user_index < logged_in_users.size();
++user_index) {
if (command_id > IDC_VISIT_DESKTOP_OF_LRU_USER_LAST) {
break;
}
const user_manager::User* user_info = logged_in_users[user_index];
model->AddItem(
command_id,
l10n_util::GetStringFUTF16(
IDS_VISIT_DESKTOP_OF_LRU_USER, user_info->GetDisplayName(),
base::ASCIIToUTF16(user_info->GetDisplayEmail())));
++command_id;
}
#endif
}