blob: 7a8d5966d9c663d2a88d82c62afcdff60e87a0c3 [file] [log] [blame]
// Copyright 2017 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/web_applications/web_app_menu_model.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/media/router/media_router_feature.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/extensions/extensions_container.h"
#include "chrome/browser/ui/tabs/tab_strip_model.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/browser/ui/web_applications/web_app_browser_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/omnibox/browser/location_bar_model.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/common/content_features.h"
#include "ui/base/accelerators/menu_label_accelerator_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/base/ui_base_features.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS)
#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 "ui/views/widget/widget.h"
#endif
namespace {
bool ShouldAllowOpenInChrome(Browser* browser) {
// Isolated Web Apps shouldn't be opened in Chrome.
const bool is_isolated_web_app =
browser->app_controller() &&
browser->app_controller()->IsIsolatedWebApp();
// Web Apps with enabled prevent close shouldn't be opened in Chrome.
const bool prevent_close_enabled =
browser->app_controller() &&
browser->app_controller()->IsPreventCloseEnabled();
return !is_isolated_web_app && !prevent_close_enabled;
}
void AddItemWithStringIdAndVectorIcon(ui::SimpleMenuModel* model,
int command_id,
int string_id,
const gfx::VectorIcon& vector_icon) {
return model->AddItemWithStringIdAndIcon(
command_id, string_id,
ui::ImageModel::FromVectorIcon(vector_icon, ui::kColorMenuIcon,
ui::SimpleMenuModel::kDefaultIconSize));
}
} // namespace
constexpr int WebAppMenuModel::kUninstallAppCommandId;
constexpr int WebAppMenuModel::kExtensionsMenuCommandId;
WebAppMenuModel::WebAppMenuModel(ui::AcceleratorProvider* provider,
Browser* browser)
: AppMenuModel(provider, browser) {}
WebAppMenuModel::~WebAppMenuModel() = default;
bool WebAppMenuModel::IsCommandIdEnabled(int command_id) const {
switch (command_id) {
case kUninstallAppCommandId:
return browser()->app_controller()->CanUserUninstall();
case kExtensionsMenuCommandId:
case IDC_OPEN_IN_CHROME:
case IDC_WEB_APP_UPGRADE_DIALOG:
#if BUILDFLAG(IS_CHROMEOS)
case chromeos::MoveToDesksMenuModel::kMenuCommandId:
#endif
// These commands only exist in the menu model if they should be visible
// and enabled.
return true;
default:
return AppMenuModel::IsCommandIdEnabled(command_id);
}
}
void WebAppMenuModel::ExecuteCommand(int command_id, int event_flags) {
switch (command_id) {
case kUninstallAppCommandId:
LogMenuAction(MENU_ACTION_UNINSTALL_APP);
browser()->app_controller()->Uninstall(
webapps::WebappUninstallSource::kAppMenu);
break;
case kExtensionsMenuCommandId:
browser()->window()->GetExtensionsContainer()->ToggleExtensionsMenu();
break;
case IDC_OPEN_IN_CHROME:
if (ShouldAllowOpenInChrome(browser())) {
AppMenuModel::ExecuteCommand(command_id, event_flags);
}
break;
case IDC_WEB_APP_UPGRADE_DIALOG:
CHECK(base::FeatureList::IsEnabled(
features::kWebAppPredictableAppUpdating));
LogMenuAction(MENU_ACTION_TRIGGER_APP_UPDATE_DIALOG);
browser()->app_controller()->CreateMetadataAndTriggerAppUpdateDialog(
base::TimeTicks::Now());
break;
default:
AppMenuModel::ExecuteCommand(command_id, event_flags);
break;
}
}
void WebAppMenuModel::Build() {
CHECK(browser()->app_controller());
web_app::WebAppBrowserController* app_controller =
browser()->app_controller()->AsWebAppBrowserController();
if (app_controller && app_controller->HasPendingUpdate()) {
CHECK(
base::FeatureList::IsEnabled(features::kWebAppPredictableAppUpdating));
AddSeparator(ui::SPACING_SEPARATOR);
gfx::ImageSkia icon = app_controller->GetAppMenuIcon();
ui::ImageModel update_icon;
if (!icon.isNull()) {
update_icon = ui::ImageModel::FromImageSkia(icon);
}
if (update_icon.IsEmpty()) {
update_icon = ui::ImageModel::FromVectorIcon(
kBrowserToolsUpdateChromeRefreshIcon,
ui::kColorMenuIconOnEmphasizedBackground, kDefaultIconSize);
}
AddItemWithStringIdAndIcon(IDC_WEB_APP_UPGRADE_DIALOG,
IDS_REVIEW_APP_UPDATE, update_icon);
AddSeparator(ui::SPACING_SEPARATOR);
}
AddItemWithStringIdAndVectorIcon(
this, IDC_WEB_APP_MENU_APP_INFO, IDS_APP_CONTEXT_MENU_SHOW_INFO,
browser()->GetFeatures().location_bar_model()->GetVectorIcon());
size_t app_info_index = GetItemCount() - 1;
CHECK(browser());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
bool is_isolated_web_app = browser()->app_controller()->IsIsolatedWebApp();
if (web_contents) {
std::u16string display_text =
web_app::AppBrowserController::FormatUrlOrigin(
web_contents->GetVisibleURL());
// For Isolated Web Apps the origin's host name is a non-human-readable
// string of characters, so instead of displaying the origin, the short name
// of the app will be displayed.
if (is_isolated_web_app) {
std::u16string short_name =
browser()->app_controller()->GetAppShortName();
// For Isolated Web Apps, |GetAppShortName()| must be non-empty.
display_text = short_name;
}
SetMinorText(app_info_index, display_text);
}
AddSeparator(ui::NORMAL_SEPARATOR);
if (base::FeatureList::IsEnabled(
features::kDesktopPWAsElidedExtensionsMenu) &&
browser()->window()->GetExtensionsContainer() &&
browser()->window()->GetExtensionsContainer()->HasAnyExtensions() &&
// Extensions are not supported inside Isolated Web Apps.
!browser()->app_controller()->IsIsolatedWebApp()) {
AddItemWithStringIdAndVectorIcon(this, kExtensionsMenuCommandId,
IDS_SHOW_EXTENSIONS,
vector_icons::kExtensionChromeRefreshIcon);
AddSeparator(ui::NORMAL_SEPARATOR);
}
if (browser()->app_controller() &&
browser()->app_controller()->has_tab_strip() &&
!browser()->app_controller()->ShouldHideNewTabButton()) {
AddItemWithStringIdAndVectorIcon(this, IDC_NEW_TAB, IDS_NEW_TAB,
kNewTabRefreshIcon);
}
AddItemWithStringIdAndVectorIcon(this, IDC_COPY_URL, IDS_COPY_URL,
kLinkChromeRefreshIcon);
if (ShouldAllowOpenInChrome(browser())) {
AddItemWithStringIdAndVectorIcon(this, IDC_OPEN_IN_CHROME,
IDS_OPEN_IN_CHROME, kBrowserLogoIcon);
}
#if BUILDFLAG(IS_CHROMEOS)
if (chromeos::MoveToDesksMenuDelegate::ShouldShowMoveToDesksMenu(
browser()->window()->GetNativeWindow())) {
AddSeparator(ui::NORMAL_SEPARATOR);
move_to_desks_submenu_ = std::make_unique<chromeos::MoveToDesksMenuModel>(
std::make_unique<chromeos::MoveToDesksMenuDelegate>(
views::Widget::GetWidgetForNativeWindow(
browser()->window()->GetNativeWindow())));
AddSubMenuWithStringId(chromeos::MoveToDesksMenuModel::kMenuCommandId,
IDS_MOVE_TO_DESKS_MENU,
move_to_desks_submenu_.get());
}
#endif
// Chrome OS's app list is prominent enough to not need a separate uninstall
// option in the app menu.
#if !BUILDFLAG(IS_CHROMEOS)
DCHECK(browser()->app_controller());
if (browser()->app_controller()->IsInstalled()) {
AddSeparator(ui::NORMAL_SEPARATOR);
AddItemWithIcon(kUninstallAppCommandId,
l10n_util::GetStringFUTF16(
IDS_UNINSTALL_FROM_OS_LAUNCH_SURFACE,
ui::EscapeMenuLabelAmpersands(
browser()->app_controller()->GetAppShortName())),
ui::ImageModel::FromVectorIcon(kTrashCanRefreshIcon));
}
#endif // !BUILDFLAG(IS_CHROMEOS)
AddSeparator(ui::NORMAL_SEPARATOR);
CreateZoomMenu();
AddSeparator(ui::NORMAL_SEPARATOR);
AddItemWithStringIdAndVectorIcon(this, IDC_PRINT, IDS_PRINT, kPrintMenuIcon);
CreateFindAndEditSubMenu();
if (media_router::MediaRouterEnabled(browser()->profile())) {
AddItemWithStringIdAndVectorIcon(this, IDC_ROUTE_MEDIA,
IDS_MEDIA_ROUTER_MENU_ITEM_TITLE,
kCastChromeRefreshIcon);
}
}