blob: a1d3a3cb408105a11b8c5c2412e6af451bb04141 [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_main_page_view.h"
#include <memory>
#include <string>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/extensions/extensions_dialogs_utils.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_handler.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_item_view.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/animation/ink_drop.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/toggle_button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "extensions/common/extension_urls.h"
#endif
namespace {
using PermissionsManager = extensions::PermissionsManager;
// Updates the `toggle_button` text based on its state.
std::u16string GetSiteSettingToggleText(bool is_on) {
int label_id = is_on ? IDS_EXTENSIONS_MENU_SITE_SETTINGS_TOGGLE_ON_TOOLTIP
: IDS_EXTENSIONS_MENU_SITE_SETTINGS_TOGGLE_OFF_TOOLTIP;
return l10n_util::GetStringUTF16(label_id);
}
// Converts a view to a ExtensionMenuItemView. This cannot be used to
// *determine* if a view is an ExtensionMenuItemView (it should only be used
// when the view is known to be one). It is only used as an extra measure to
// prevent bad static casts.
ExtensionMenuItemView* GetAsMenuItem(views::View* view) {
DCHECK(views::IsViewClass<ExtensionMenuItemView>(view));
return views::AsViewClass<ExtensionMenuItemView>(view);
}
// Returns the ExtensionMenuItemView corresponding to `action_id` if
// it is a children of `parent_view`. The children of the parent view must be
// ExtensionMenuItemView, otherwise it will DCHECK.
ExtensionMenuItemView* GetMenuItem(
views::View* parent_view,
const ToolbarActionsModel::ActionId& action_id) {
for (auto* view : parent_view->children()) {
auto* item_view = GetAsMenuItem(view);
if (item_view->view_controller()->GetId() == action_id) {
return item_view;
}
}
return nullptr;
}
} // namespace
class RequestsAccessSection : public views::BoxLayoutView {
public:
RequestsAccessSection();
RequestsAccessSection(const RequestsAccessSection&) = delete;
const RequestsAccessSection& operator=(const RequestsAccessSection&) = delete;
~RequestsAccessSection() override = default;
private:
raw_ptr<views::View> extension_items_;
};
BEGIN_VIEW_BUILDER(/* No Export */, RequestsAccessSection, views::BoxLayoutView)
END_VIEW_BUILDER
DEFINE_VIEW_BUILDER(/* No Export */, RequestsAccessSection)
RequestsAccessSection::RequestsAccessSection() {
views::Builder<RequestsAccessSection>(this)
.SetOrientation(views::BoxLayout::Orientation::kVertical)
.SetVisible(false)
// TODO(crbug.com/1390952): After adding margins, compute radius from a
// variable or create a const variable.
.SetBackground(views::CreateThemedRoundedRectBackground(
kColorExtensionsMenuHighlightedBackground, 4))
.AddChildren(
// Header explaining the section.
views::Builder<views::Label>()
.SetText(l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_REQUESTS_ACCESS_SECTION_TITLE))
.SetTextContext(ChromeTextContext::CONTEXT_DIALOG_BODY_TEXT_SMALL)
.SetTextStyle(views::style::STYLE_EMPHASIZED)
.SetHorizontalAlignment(gfx::ALIGN_LEFT),
// Empty container for the extensions requesting access. Items will be
// populated later.
views::Builder<views::BoxLayoutView>()
.SetOrientation(views::BoxLayout::Orientation::kVertical)
.CopyAddressTo(&extension_items_))
.BuildChildren();
// TODO(crbug.com/1390952): Populate `extension_items_` with extensions
// requesting access.
}
ExtensionsMenuMainPageView::ExtensionsMenuMainPageView(
Browser* browser,
ExtensionsMenuHandler* navigation_handler)
: browser_(browser), navigation_handler_(navigation_handler) {
// This is set so that the extensions menu doesn't fall outside the monitor in
// a maximized window in 1024x768. See https://crbug.com/1096630.
// TODO(crbug.com/1413883): Consider making the height dynamic.
constexpr int kMaxExtensionButtonsHeightDp = 448;
views::FlexSpecification stretch_specification =
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded,
/*adjust_height_for_width =*/true)
.WithWeight(1);
views::Builder<ExtensionsMenuMainPageView>(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 section.
views::Builder<views::FlexLayoutView>()
.SetCrossAxisAlignment(views::LayoutAlignment::kStart)
.SetProperty(views::kFlexBehaviorKey, stretch_specification)
.SetVisible(true)
.AddChildren(
views::Builder<views::FlexLayoutView>()
.SetOrientation(views::LayoutOrientation::kVertical)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
.SetProperty(views::kFlexBehaviorKey,
stretch_specification)
.AddChildren(
views::Builder<views::Label>()
.SetText(l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_TITLE))
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.SetTextContext(
views::style::CONTEXT_DIALOG_TITLE)
.SetTextStyle(views::style::STYLE_SECONDARY),
views::Builder<views::Label>()
.CopyAddressTo(&subheader_subtitle_)
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.SetTextContext(views::style::CONTEXT_LABEL)
.SetTextStyle(views::style::STYLE_SECONDARY)
.SetAllowCharacterBreak(true)
.SetMultiLine(true)
.SetProperty(views::kFlexBehaviorKey,
stretch_specification)),
// TODO(crbug.com/1390952): Move webstore, setting, and toggle
// button under close button. This will be done as part of
// adding margins to the menu.
// Webstore button.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
views::Builder<views::ImageButton>(
views::CreateVectorImageButtonWithNativeTheme(
base::BindRepeating(
&chrome::ShowWebStore, browser_,
extension_urls::kExtensionsMenuUtmSource),
vector_icons::kGoogleChromeWebstoreIcon))
.SetAccessibleName(l10n_util::GetStringUTF16(
IDS_EXTENSIONS_MENU_MAIN_PAGE_OPEN_CHROME_WEBSTORE_ACCESSIBLE_NAME))
.CustomConfigure(
base::BindOnce([](views::ImageButton* view) {
view->SizeToPreferredSize();
InstallCircleHighlightPathGenerator(view);
})),
#endif
// Setting button.
views::Builder<views::ImageButton>(
views::CreateVectorImageButtonWithNativeTheme(
base::BindRepeating(
[](Browser* browser) {
chrome::ShowExtensions(browser);
},
browser_),
vector_icons::kSettingsIcon))
.SetAccessibleName(
l10n_util::GetStringUTF16(IDS_MANAGE_EXTENSIONS))
.CustomConfigure(
base::BindOnce([](views::ImageButton* view) {
view->SizeToPreferredSize();
InstallCircleHighlightPathGenerator(view);
})),
// Toggle site settings button.
views::Builder<views::ToggleButton>()
.CopyAddressTo(&site_settings_toggle_)
.SetCallback(base::BindRepeating(
&ExtensionsMenuMainPageView::OnToggleButtonPressed,
base::Unretained(this))),
// Close button.
views::Builder<views::Button>(
views::BubbleFrameView::CreateCloseButton(
base::BindRepeating(
&ExtensionsMenuHandler::CloseBubble,
base::Unretained(navigation_handler_))))),
// Contents.
views::Builder<views::Separator>(),
views::Builder<views::ScrollView>()
.ClipHeightTo(0, kMaxExtensionButtonsHeightDp)
.SetDrawOverflowIndicator(false)
.SetHorizontalScrollBarMode(
views::ScrollView::ScrollBarMode::kDisabled)
.SetContents(
views::Builder<views::BoxLayoutView>()
.SetOrientation(views::BoxLayout::Orientation::kVertical)
.AddChildren(
// Request access section.
views::Builder<RequestsAccessSection>(
std::make_unique<RequestsAccessSection>()),
// Menu items section.
views::Builder<views::BoxLayoutView>()
.CopyAddressTo(&menu_items_)
.SetOrientation(
views::BoxLayout::Orientation::kVertical))))
.BuildChildren();
}
void ExtensionsMenuMainPageView::CreateAndInsertMenuItem(
std::unique_ptr<ExtensionActionViewController> action_controller,
extensions::ExtensionId extension_id,
ExtensionMenuItemView::SiteAccessToggleState site_access_toggle_state,
ExtensionMenuItemView::SitePermissionsButtonState
site_permissions_button_state,
ExtensionMenuItemView::SitePermissionsButtonAccess
site_permissions_button_access,
int index) {
auto item = std::make_unique<ExtensionMenuItemView>(
browser_, std::move(action_controller),
// TODO(crbug.com/1390952): Create callback that grants/withhelds site
// access when toggling the site access toggle.
base::RepeatingClosure(base::NullCallback()),
base::BindRepeating(&ExtensionsMenuHandler::OpenSitePermissionsPage,
base::Unretained(navigation_handler_), extension_id));
item->Update(site_access_toggle_state, site_permissions_button_state,
site_permissions_button_access);
menu_items_->AddChildViewAt(std::move(item), index);
}
void ExtensionsMenuMainPageView::RemoveMenuItem(
const ToolbarActionsModel::ActionId& action_id) {
views::View* item = GetMenuItem(menu_items_, action_id);
menu_items_->RemoveChildViewT(item);
}
void ExtensionsMenuMainPageView::OnToggleButtonPressed() {
const url::Origin& origin =
GetActiveWebContents()->GetPrimaryMainFrame()->GetLastCommittedOrigin();
PermissionsManager::UserSiteSetting site_setting =
site_settings_toggle_->GetIsOn()
? PermissionsManager::UserSiteSetting::kCustomizeByExtension
: PermissionsManager::UserSiteSetting::kBlockAllExtensions;
PermissionsManager::Get(browser_->profile())
->UpdateUserSiteSetting(origin, site_setting);
// TODO(crbug.com/1390952): Show reload message in menu if any extension needs
// a page refresh for the update to take effect.
}
void ExtensionsMenuMainPageView::Update(std::u16string current_site,
bool is_site_settings_toggle_visible,
bool is_site_settings_toggle_on) {
subheader_subtitle_->SetText(current_site);
site_settings_toggle_->SetVisible(is_site_settings_toggle_visible);
site_settings_toggle_->SetIsOn(is_site_settings_toggle_on);
site_settings_toggle_->SetTooltipText(
GetSiteSettingToggleText(is_site_settings_toggle_on));
site_settings_toggle_->SetAccessibleName(
GetSiteSettingToggleText(is_site_settings_toggle_on));
}
std::vector<ExtensionMenuItemView*> ExtensionsMenuMainPageView::GetMenuItems()
const {
std::vector<ExtensionMenuItemView*> menu_item_views;
for (views::View* view : menu_items_->children()) {
menu_item_views.push_back(GetAsMenuItem(view));
}
return menu_item_views;
}
content::WebContents* ExtensionsMenuMainPageView::GetActiveWebContents() const {
return browser_->tab_strip_model()->GetActiveWebContents();
}
BEGIN_METADATA(ExtensionsMenuMainPageView, views::View)
END_METADATA