blob: 7dc5202665a869bc50280b62a560aa9a2fbc7866 [file] [log] [blame]
// Copyright 2022 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/extensions/extensions_menu_site_permissions_page_view.h"
#include <string>
#include "chrome/browser/extensions/site_permissions_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/controls/hover_button.h"
#include "chrome/browser/ui/views/extensions/extensions_dialogs_utils.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_handler.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/metadata/view_factory_internal.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
namespace {
using extensions::PermissionsManager;
// Radio buttons group id for site permissions.
constexpr int kSiteAccessButtonsId = 0;
// Indexes for each site permission radio button.
constexpr size_t kOnClickButtonIndex = 0;
constexpr size_t kOnSiteButtonIndex = 1;
constexpr size_t kOnAllSitesButtonIndex = 2;
// Returns the site access button in a site permissions `page`.
std::vector<views::RadioButton*> GetSiteAccessButtons(views::View* page) {
std::vector<views::View*> buttons;
page->GetViewsInGroup(kSiteAccessButtonsId, &buttons);
std::vector<views::RadioButton*> site_access_buttons;
site_access_buttons.reserve(buttons.size());
std::transform(buttons.begin(), buttons.end(),
std::back_inserter(site_access_buttons),
[](views::View* button) {
return views::AsViewClass<views::RadioButton>(button);
});
return site_access_buttons;
}
std::u16string GetShowRequestsToggleAccessibleName(bool is_toggle_on) {
int label_id =
is_toggle_on
? IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SHOW_REQUESTS_TOGGLE_ON
: IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SHOW_REQUESTS_TOGGLE_OFF;
return l10n_util::GetStringUTF16(label_id);
}
// Returns the radio button text for `site_access` option.
std::u16string GetSiteAccessRadioButtonText(
PermissionsManager::UserSiteAccess site_access,
std::u16string current_site = std::u16string()) {
switch (site_access) {
case PermissionsManager::UserSiteAccess::kOnClick:
return l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_ON_CLICK_TEXT);
case PermissionsManager::UserSiteAccess::kOnSite:
return l10n_util::GetStringFUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_ON_SITE_TEXT,
current_site);
case PermissionsManager::UserSiteAccess::kOnAllSites:
return l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_ON_ALL_SITES_TEXT);
default:
NOTREACHED_NORETURN();
}
}
// Returns the radio button description for `site_access` option.
std::u16string GetSiteAccessRadioButtonDescription(
PermissionsManager::UserSiteAccess site_access) {
switch (site_access) {
case PermissionsManager::UserSiteAccess::kOnClick:
return l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_ON_CLICK_DESCRIPTION);
case PermissionsManager::UserSiteAccess::kOnSite:
return l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_ON_SITE_DESCRIPTION);
case PermissionsManager::UserSiteAccess::kOnAllSites:
return l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_ON_ALL_SITES_DESCRIPTION);
default:
NOTREACHED_NORETURN();
}
}
// Returns the button index for `site_access`.
int GetSiteAccessButtonIndex(PermissionsManager::UserSiteAccess site_access) {
switch (site_access) {
case PermissionsManager::UserSiteAccess::kOnClick:
return kOnClickButtonIndex;
case PermissionsManager::UserSiteAccess::kOnSite:
return kOnSiteButtonIndex;
case PermissionsManager::UserSiteAccess::kOnAllSites:
return kOnAllSitesButtonIndex;
}
}
} // namespace
ExtensionsMenuSitePermissionsPageView::ExtensionsMenuSitePermissionsPageView(
Browser* browser,
extensions::ExtensionId extension_id,
ExtensionsMenuHandler* menu_handler)
: browser_(browser), extension_id_(extension_id) {
// TODO(crbug.com/1390952): Same stretch specification as
// ExtensionsMenuMainPageView. Move to a shared file.
views::FlexSpecification stretch_specification =
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded,
/*adjust_height_for_width =*/true)
.WithWeight(1);
ChromeLayoutProvider* const provider = ChromeLayoutProvider::Get();
const int icon_size =
provider->GetDistanceMetric(DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE);
const int icon_label_spacing =
provider->GetDistanceMetric(views::DISTANCE_RELATED_LABEL_HORIZONTAL);
const auto create_radio_button_builder =
[=](PermissionsManager::UserSiteAccess site_access) {
return views::Builder<views::BoxLayoutView>()
.SetOrientation(views::BoxLayout::Orientation::kVertical)
.AddChildren(
views::Builder<views::RadioButton>()
.SetText(GetSiteAccessRadioButtonText(site_access))
.SetGroup(kSiteAccessButtonsId)
.SetImageLabelSpacing(icon_label_spacing)
.SetCallback(base::BindRepeating(
&ExtensionsMenuHandler::OnSiteAccessSelected,
base::Unretained(menu_handler), extension_id,
site_access)),
views::Builder<views::Label>()
.SetText(GetSiteAccessRadioButtonDescription(site_access))
.SetTextStyle(views::style::STYLE_SECONDARY)
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.SetMultiLine(true)
.SetBorder(views::CreateEmptyBorder(
gfx::Insets::VH(0, icon_label_spacing + icon_size))));
};
views::Builder<ExtensionsMenuSitePermissionsPageView>(this)
.SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical))
// TODO(crbug.com/1390952): Add margins after adding the menu
// items, to make sure all items are aligned.
.AddChildren(
// Subheader.
views::Builder<views::FlexLayoutView>()
.SetCrossAxisAlignment(views::LayoutAlignment::kStart)
.SetProperty(views::kFlexBehaviorKey, stretch_specification)
.AddChildren(
// Back button.
views::Builder<views::ImageButton>(
views::CreateVectorImageButtonWithNativeTheme(
base::BindRepeating(
&ExtensionsMenuHandler::OpenMainPage,
base::Unretained(menu_handler)),
vector_icons::kArrowBackIcon))
.SetTooltipText(
l10n_util::GetStringUTF16(IDS_ACCNAME_BACK))
.SetAccessibleName(
l10n_util::GetStringUTF16(IDS_ACCNAME_BACK))
.CustomConfigure(
base::BindOnce([](views::ImageButton* view) {
view->SizeToPreferredSize();
InstallCircleHighlightPathGenerator(view);
})),
// Extension name.
views::Builder<views::FlexLayoutView>()
.SetOrientation(views::LayoutOrientation::kHorizontal)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
.SetProperty(views::kFlexBehaviorKey,
stretch_specification)
.AddChildren(
views::Builder<views::ImageView>().CopyAddressTo(
&extension_icon_),
views::Builder<views::Label>().CopyAddressTo(
&extension_name_))),
// Content.
views::Builder<views::BoxLayoutView>()
.SetOrientation(views::BoxLayout::Orientation::kVertical)
.AddChildren(
// Site access section.
views::Builder<views::Separator>(),
views::Builder<views::Label>()
.SetText(l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SITE_ACCESS_LABEL))
.SetHorizontalAlignment(gfx::ALIGN_LEFT),
create_radio_button_builder(
PermissionsManager::UserSiteAccess::kOnClick),
create_radio_button_builder(
PermissionsManager::UserSiteAccess::kOnSite),
create_radio_button_builder(
PermissionsManager::UserSiteAccess::kOnAllSites),
// Requests in toolbar toggle.
views::Builder<views::Separator>(),
views::Builder<views::FlexLayoutView>()
.SetCrossAxisAlignment(views::LayoutAlignment::kStart)
.SetProperty(views::kFlexBehaviorKey,
stretch_specification)
.AddChildren(
views::Builder<views::Label>().SetText(
l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SHOW_REQUESTS_LABEL)),
views::Builder<views::ToggleButton>()
.CopyAddressTo(&show_requests_toggle_)
.SetCallback(base::BindRepeating(
&ExtensionsMenuSitePermissionsPageView::
OnShowRequestsTogglePressed,
base::Unretained(this)))),
// Settings button.
views::Builder<views::Separator>(),
views::Builder<HoverButton>(std::make_unique<HoverButton>(
base::BindRepeating(
[](Browser* browser,
extensions::ExtensionId extension_id) {
chrome::ShowExtensions(browser, extension_id);
},
browser, extension_id_),
/*icon_view=*/nullptr,
l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_SITE_PERMISSIONS_PAGE_SETTINGS_BUTTON),
/*subtitle=*/std::u16string(),
std::make_unique<views::ImageView>(
ui::ImageModel::FromVectorIcon(
vector_icons::kLaunchIcon,
ui::kColorIconSecondary))))))
.BuildChildren();
}
void ExtensionsMenuSitePermissionsPageView::Update(
const std::u16string& extension_name,
const ui::ImageModel& extension_icon,
const std::u16string& current_site,
PermissionsManager::UserSiteAccess user_site_access,
bool is_show_requests_toggle_on,
bool is_on_site_enabled,
bool is_on_all_sites_enabled) {
extension_icon_->SetImage(extension_icon);
extension_name_->SetText(extension_name);
// Update the site access buttons with new `user_site_access` and
// `current_site`.
int new_selected_index = GetSiteAccessButtonIndex(user_site_access);
std::vector<views::RadioButton*> site_access_buttons =
GetSiteAccessButtons(this);
for (int i = 0; i < static_cast<int>(site_access_buttons.size()); ++i) {
site_access_buttons[i]->SetChecked(i == new_selected_index);
if (i == kOnSiteButtonIndex) {
site_access_buttons[i]->SetText(GetSiteAccessRadioButtonText(
PermissionsManager::UserSiteAccess::kOnSite, current_site));
}
}
// Enable the site access buttons accordingly. The extension is guaranteed to
// at least have "on click" enabled when this page is opened.
site_access_buttons[kOnSiteButtonIndex]->SetEnabled(is_on_site_enabled);
site_access_buttons[kOnAllSitesButtonIndex]->SetEnabled(
is_on_all_sites_enabled);
UpdateShowRequestsToggle(is_show_requests_toggle_on);
}
void ExtensionsMenuSitePermissionsPageView::UpdateShowRequestsToggle(
bool is_on) {
show_requests_toggle_->SetIsOn(is_on);
show_requests_toggle_->SetAccessibleName(
GetShowRequestsToggleAccessibleName(is_on));
}
void ExtensionsMenuSitePermissionsPageView::OnShowRequestsTogglePressed() {
extensions::SitePermissionsHelper(browser_->profile())
.SetShowAccessRequestsInToolbar(extension_id_,
show_requests_toggle_->GetIsOn());
}
views::RadioButton*
ExtensionsMenuSitePermissionsPageView::GetSiteAccessButtonForTesting(
PermissionsManager::UserSiteAccess site_access) {
std::vector<views::RadioButton*> site_access_buttons =
GetSiteAccessButtons(this);
return site_access_buttons[GetSiteAccessButtonIndex(site_access)];
}
BEGIN_METADATA(ExtensionsMenuSitePermissionsPageView, views::View)
END_METADATA