| // Copyright 2019 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_item_view.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/color/chrome_color_id.h" |
| #include "chrome/browser/ui/extensions/extensions_menu_view_model.h" |
| #include "chrome/browser/ui/toolbar/toolbar_action_view_model.h" |
| #include "chrome/browser/ui/toolbar/toolbar_actions_model.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/extension_context_menu_controller.h" |
| #include "chrome/browser/ui/views/extensions/extensions_menu_button.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/common/extension_features.h" |
| #include "ui/accessibility/ax_enums.mojom-shared.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/base/mojom/menu_source_type.mojom.h" |
| #include "ui/color/color_id.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/gfx/vector_icon_utils.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/animation/ink_drop.h" |
| #include "ui/views/animation/ink_drop_host.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/menu_button_controller.h" |
| #include "ui/views/controls/button/toggle_button.h" |
| #include "ui/views/controls/combobox/combobox.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/layout/flex_layout_types.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/vector_icons.h" |
| #include "ui/views/view_class_properties.h" |
| |
| namespace { |
| |
| constexpr int EXTENSION_CONTEXT_MENU = 13; |
| constexpr int EXTENSION_PINNING = 14; |
| |
| std::u16string GetPinButtonTooltip(bool is_force_pinned, bool is_pinned) { |
| int tooltip_id = IDS_EXTENSIONS_PIN_TO_TOOLBAR; |
| if (is_force_pinned) { |
| tooltip_id = IDS_EXTENSIONS_PINNED_BY_ADMIN; |
| } else if (is_pinned) { |
| tooltip_id = IDS_EXTENSIONS_UNPIN_FROM_TOOLBAR; |
| } |
| return l10n_util::GetStringUTF16(tooltip_id); |
| } |
| |
| std::u16string GetPinButtonAccessibleName( |
| bool is_force_pinned, |
| bool is_pinned, |
| const std::u16string& extension_name) { |
| int tooltip_id = IDS_EXTENSIONS_PIN_TO_TOOLBAR_ACCESSIBLE_NAME; |
| if (is_force_pinned) { |
| tooltip_id = IDS_EXTENSIONS_PINNED_BY_ADMIN_ACCESSIBLE_NAME; |
| } else if (is_pinned) { |
| tooltip_id = IDS_EXTENSIONS_UNPIN_FROM_TOOLBAR_ACCESSIBLE_NAME; |
| } |
| return l10n_util::GetStringFUTF16(tooltip_id, extension_name); |
| } |
| |
| std::u16string GetContextMenuAccessibleName( |
| bool is_pinned, |
| const std::u16string& extension_name) { |
| int tooltip_id = |
| is_pinned |
| ? IDS_EXTENSIONS_MENU_EXTENSION_CONTEXT_MENU_BUTTON_PINNED_ACCESSIBLE_NAME |
| : IDS_EXTENSIONS_MENU_EXTENSION_CONTEXT_MENU_BUTTON_ACCESSIBLE_NAME; |
| return l10n_util::GetStringFUTF16(tooltip_id, extension_name); |
| } |
| |
| std::u16string GetPinButtonPressedAccText(bool is_pinned) { |
| return l10n_util::GetStringUTF16(is_pinned ? IDS_EXTENSION_PINNED |
| : IDS_EXTENSION_UNPINNED); |
| } |
| |
| std::u16string GetSiteAccessToggleTooltip(bool is_on) { |
| return l10n_util::GetStringUTF16( |
| is_on ? IDS_EXTENSIONS_MENU_EXTENSION_SITE_ACCESS_TOGGLE_ON_TOOLTIP |
| : IDS_EXTENSIONS_MENU_EXTENSION_SITE_ACCESS_TOGGLE_OFF_TOOLTIP); |
| } |
| |
| std::u16string GetSitePermissionsButtonText( |
| ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess |
| button_access) { |
| int label_id; |
| switch (button_access) { |
| case ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess:: |
| kNone: |
| label_id = IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_NONE; |
| break; |
| case ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess:: |
| kOnClick: |
| label_id = IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_ON_CLICK; |
| break; |
| case ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess:: |
| kOnSite: |
| label_id = IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_ON_SITE; |
| break; |
| case ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess:: |
| kOnAllSites: |
| label_id = |
| IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_ON_ALL_SITES; |
| break; |
| } |
| return l10n_util::GetStringUTF16(label_id); |
| } |
| |
| std::u16string GetSitePermissionsButtonTooltip( |
| bool is_enterprise, |
| ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess |
| button_access) { |
| if (is_enterprise) { |
| return l10n_util::GetStringUTF16( |
| IDS_EXTENSIONS_MENU_MAIN_PAGE_ENTERPRISE_EXTENSION_SITE_ACCESS_TOOLTIP); |
| } |
| |
| if (button_access != ExtensionsMenuViewModel::MenuItemInfo:: |
| SitePermissionsButtonAccess::kNone) { |
| return l10n_util::GetStringUTF16( |
| IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_TOOLTIP); |
| } |
| |
| // No tooltip is shown. |
| return std::u16string(); |
| } |
| |
| std::u16string GetSitePermissionsButtonAccName( |
| bool is_enterprise, |
| ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonAccess |
| button_access, |
| std::u16string& button_text) { |
| if (is_enterprise) { |
| return l10n_util::GetStringFUTF16( |
| IDS_EXTENSIONS_MENU_MAIN_PAGE_ENTERPRISE_EXTENSION_SITE_ACCESS_ACCESSIBLE_NAME, |
| button_text); |
| } |
| |
| if (button_access != ExtensionsMenuViewModel::MenuItemInfo:: |
| SitePermissionsButtonAccess::kNone) { |
| return l10n_util::GetStringFUTF16( |
| IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_ACCESSIBLE_NAME, |
| button_text); |
| } |
| |
| return button_text; |
| } |
| |
| views::Builder<HoverButton> GetSitePermissionsButtonBuilder( |
| views::Button::PressedCallback callback, |
| bool is_enterprise, |
| int small_icon_size, |
| int icon_size, |
| int icon_label_spacing, |
| int button_icon_label_spacing) { |
| auto button_builder = |
| views::Builder<HoverButton>( |
| std::make_unique<HoverButton>(std::move(callback), std::u16string())) |
| .SetLabelStyle(views::style::STYLE_BODY_5) |
| .SetEnabledTextColors(kColorExtensionsMenuSecondaryText) |
| .SetTextColor(views::Button::ButtonState::STATE_DISABLED, |
| kColorExtensionsMenuSecondaryText) |
| .SetImageLabelSpacing(button_icon_label_spacing) |
| // Align the main and secondary row text by adding the primary |
| // action button's icon size as margin. |
| .SetProperty(views::kMarginsKey, gfx::Insets::VH(0, icon_size)) |
| // Border should be the same as the space between icon and |
| // label in the primary action button. |
| .SetBorder( |
| views::CreateEmptyBorder(gfx::Insets::VH(0, icon_label_spacing))); |
| |
| if (is_enterprise) { |
| // Add left-aligned business icon for enterprise extensions. |
| button_builder.SetHorizontalAlignment(gfx::ALIGN_LEFT) |
| .SetImageModel(views::Button::ButtonState::STATE_NORMAL, |
| ui::ImageModel::FromVectorIcon( |
| vector_icons::kBusinessChromeRefreshIcon, |
| kColorExtensionMenuIcon, small_icon_size)); |
| } else { |
| // Add right-aligned arrow icon for non-enterprise extensions when the |
| // button is not disabled. |
| auto arrow_icon = ui::ImageModel::FromVectorIcon( |
| vector_icons::kSubmenuArrowChromeRefreshIcon, kColorExtensionMenuIcon, |
| small_icon_size); |
| |
| button_builder.SetHorizontalAlignment(gfx::ALIGN_RIGHT) |
| .SetImageModel(views::Button::ButtonState::STATE_NORMAL, arrow_icon) |
| .SetImageModel(views::Button::ButtonState::STATE_DISABLED, |
| ui::ImageModel()); |
| } |
| |
| return button_builder; |
| } |
| |
| } // namespace |
| |
| DEFINE_ELEMENT_IDENTIFIER_VALUE(kExtensionMenuItemViewElementId); |
| |
| ExtensionMenuItemView::ExtensionMenuItemView( |
| Browser* browser, |
| std::unique_ptr<ToolbarActionViewModel> view_model, |
| bool allow_pinning) |
| : browser_(browser), |
| view_model_(std::move(view_model)), |
| model_(ToolbarActionsModel::Get(browser_->profile())) { |
| CHECK(!base::FeatureList::IsEnabled( |
| extensions_features::kExtensionsMenuAccessControl)); |
| |
| const int icon_size = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE); |
| |
| auto builder = |
| views::Builder<ExtensionMenuItemView>(this) |
| // Set so the extension button receives enter/exit on children to |
| // retain hover status when hovering child views. |
| .SetNotifyEnterExitOnChild(true) |
| .SetOrientation(views::LayoutOrientation::kHorizontal) |
| .SetIgnoreDefaultMainAxisMargins(true) |
| .AddChildren( |
| views::Builder<ExtensionsMenuButton>( |
| std::make_unique<ExtensionsMenuButton>(browser_, |
| view_model_.get())) |
| .CopyAddressTo(&primary_action_button_) |
| .SetProperty(views::kFlexBehaviorKey, |
| views::FlexSpecification( |
| views::LayoutOrientation::kHorizontal, |
| views::MinimumFlexSizeRule::kScaleToZero, |
| views::MaximumFlexSizeRule::kUnbounded)), |
| views::Builder<HoverButton>( |
| std::make_unique<HoverButton>( |
| views::Button::PressedCallback(), std::u16string())) |
| .CopyAddressTo(&context_menu_button_) |
| .SetID(EXTENSION_CONTEXT_MENU) |
| .SetBorder(views::CreateEmptyBorder( |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_EXTENSIONS_MENU_BUTTON_MARGIN))) |
| .SetTooltipText(l10n_util::GetStringUTF16( |
| IDS_EXTENSIONS_MENU_CONTEXT_MENU_TOOLTIP)) |
| .SetAccessibleName(l10n_util::GetStringFUTF16( |
| IDS_EXTENSIONS_MENU_CONTEXT_MENU_TOOLTIP_ACCESSIBLE_NAME, |
| view_model_->GetActionName())) |
| .SetImageModel(views::Button::STATE_NORMAL, |
| ui::ImageModel::FromVectorIcon( |
| kBrowserToolsChromeRefreshIcon, |
| kColorExtensionMenuIcon, icon_size)) |
| .SetImageModel( |
| views::Button::STATE_DISABLED, |
| ui::ImageModel::FromVectorIcon( |
| kBrowserToolsChromeRefreshIcon, |
| kColorExtensionMenuIconDisabled, icon_size))); |
| |
| if (allow_pinning) { |
| // Pin button should be in between `primary_action_button_` and |
| // `context_menu_button_`. |
| int index = 1; |
| builder.AddChildAt( |
| views::Builder<HoverButton>( |
| std::make_unique<HoverButton>( |
| base::BindRepeating(&ExtensionMenuItemView::OnPinButtonPressed, |
| base::Unretained(this)), |
| std::u16string())) |
| .CopyAddressTo(&pin_button_) |
| .SetID(EXTENSION_PINNING) |
| .SetBorder(views::CreateEmptyBorder( |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_EXTENSIONS_MENU_BUTTON_MARGIN))), |
| index); |
| |
| // By default, the button's accessible description is set to the button's |
| // tooltip text. For the pin button, we only want the accessible name to be |
| // read on accessibility mode since it includes the tooltip text. Thus we |
| // set the accessible description. |
| pin_button_->GetViewAccessibility().SetDescription( |
| std::u16string(), |
| ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty); |
| |
| bool is_pinned = model_ && model_->IsActionPinned(view_model_->GetId()); |
| bool is_force_pinned = |
| model_ && model_->IsActionForcePinned(view_model_->GetId()); |
| UpdatePinButton(is_force_pinned, is_pinned); |
| } |
| |
| std::move(builder).BuildChildren(); |
| |
| SetupContextMenuButton(); |
| } |
| |
| ExtensionMenuItemView::ExtensionMenuItemView( |
| Browser* browser, |
| bool is_enterprise, |
| std::unique_ptr<ToolbarActionViewModel> view_model, |
| base::RepeatingCallback<void(bool)> site_access_toggle_callback, |
| views::Button::PressedCallback site_permissions_button_callback) |
| : browser_(browser), |
| view_model_(std::move(view_model)), |
| model_(ToolbarActionsModel::Get(browser_->profile())) { |
| CHECK(base::FeatureList::IsEnabled( |
| extensions_features::kExtensionsMenuAccessControl)); |
| |
| ChromeLayoutProvider* const provider = ChromeLayoutProvider::Get(); |
| const int icon_size = |
| provider->GetDistanceMetric(DISTANCE_EXTENSIONS_MENU_EXTENSION_ICON_SIZE); |
| const int small_icon_size = provider->GetDistanceMetric( |
| DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SMALL_SIZE); |
| const int icon_label_spacing = |
| provider->GetDistanceMetric(views::DISTANCE_RELATED_LABEL_HORIZONTAL); |
| const int button_icon_label_spacing = |
| provider->GetDistanceMetric(DISTANCE_EXTENSIONS_MENU_LABEL_ICON_SPACING); |
| const int horizontal_spacing = |
| provider->GetDistanceMetric(DISTANCE_RELATED_LABEL_HORIZONTAL_LIST); |
| const gfx::Insets icon_padding = |
| provider->GetInsetsMetric(views::INSETS_VECTOR_IMAGE_BUTTON); |
| |
| views::Builder<ExtensionMenuItemView>(this) |
| // Set so the extension button receives enter/exit on children to |
| // retain hover status when hovering child views. |
| .SetNotifyEnterExitOnChild(true) |
| .SetOrientation(views::LayoutOrientation::kVertical) |
| .SetCrossAxisAlignment(views::LayoutAlignment::kStretch) |
| .SetProperty(views::kElementIdentifierKey, |
| kExtensionMenuItemViewElementId) |
| .AddChildren( |
| // Main row. |
| views::Builder<views::FlexLayoutView>() |
| .SetOrientation(views::LayoutOrientation::kHorizontal) |
| .SetIgnoreDefaultMainAxisMargins(true) |
| .AddChildren( |
| // Primary action button. |
| views::Builder<ExtensionsMenuButton>( |
| std::make_unique<ExtensionsMenuButton>(browser_, |
| view_model_.get())) |
| .CopyAddressTo(&primary_action_button_) |
| .SetTitleTextStyle(views::style::STYLE_BODY_3_EMPHASIS, |
| ui::kColorDialogBackground, |
| kColorExtensionsMenuText) |
| .SetProperty(views::kFlexBehaviorKey, |
| views::FlexSpecification( |
| views::LayoutOrientation::kHorizontal, |
| views::MinimumFlexSizeRule::kScaleToZero, |
| views::MaximumFlexSizeRule::kUnbounded)), |
| // Site access toggle. |
| views::Builder<views::ToggleButton>() |
| .CopyAddressTo(&site_access_toggle_) |
| .SetProperty( |
| views::kMarginsKey, |
| gfx::Insets::TLBR(0, horizontal_spacing, 0, 0)) |
| .SetAccessibleName(l10n_util::GetStringFUTF16( |
| IDS_EXTENSIONS_MENU_EXTENSION_SITE_ACCESS_TOGGLE_ACCESSIBLE_NAME, |
| view_model_->GetActionName())) |
| .SetCallback(base::BindRepeating( |
| [](views::ToggleButton* toggle_button, |
| base::RepeatingCallback<void(bool)> |
| site_access_toggle_callback) { |
| site_access_toggle_callback.Run( |
| toggle_button->GetIsOn()); |
| }, |
| site_access_toggle_, site_access_toggle_callback)), |
| // Context menu button. |
| views::Builder<HoverButton>( |
| std::make_unique<HoverButton>( |
| views::Button::PressedCallback(), std::u16string())) |
| .CopyAddressTo(&context_menu_button_) |
| .SetID(EXTENSION_CONTEXT_MENU) |
| .SetProperty( |
| views::kMarginsKey, |
| gfx::Insets::TLBR(0, horizontal_spacing, 0, 0)) |
| // Override the hover button border since we are |
| // adding vertical spacing in between menu items. |
| // Instead, set the border to be the padding around the |
| // icon when hovering. |
| .SetBorder(views::CreateEmptyBorder(icon_padding)) |
| .SetTooltipText(l10n_util::GetStringUTF16( |
| IDS_EXTENSIONS_MENU_EXTENSION_CONTEXT_MENU_BUTTON_TOOLTIP)) |
| // TODO(crbug.com/40857680): Context menu button can |
| // be an ImageButton instead of HoverButton since we |
| // manually add a circle highlight. Change this once |
| // kExtensionsMenuAccessControl is rolled out and we |
| // remove the older menu implementation, which relies |
| // on `context_menu_button_` being a HoverButton. |
| .CustomConfigure(base::BindOnce([](HoverButton* view) { |
| InstallCircleHighlightPathGenerator(view); |
| }))), |
| // Secondary row. |
| views::Builder<views::FlexLayoutView>().AddChildren( |
| GetSitePermissionsButtonBuilder( |
| std::move(site_permissions_button_callback), is_enterprise, |
| small_icon_size, icon_size, icon_label_spacing, |
| button_icon_label_spacing) |
| .CopyAddressTo(&site_permissions_button_))) |
| .BuildChildren(); |
| |
| SetupContextMenuButton(); |
| |
| // By default, the button's accessible description is set to the button's |
| // tooltip text. This is the accepted workaround to ensure only accessible |
| // name is announced by a screenreader rather than tooltip text and |
| // accessible name. |
| site_access_toggle_->GetViewAccessibility().SetDescription( |
| std::u16string(), ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty); |
| site_permissions_button_->GetViewAccessibility().SetDescription( |
| std::u16string(), ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty); |
| |
| // Add rounded corners to the site permissions button. |
| site_permissions_button_->SetFocusRingCornerRadius( |
| views::LayoutProvider::Get()->GetCornerRadiusMetric( |
| views::ShapeContextTokens::kExtensionsMenuButtonRadius)); |
| } |
| |
| ExtensionMenuItemView::~ExtensionMenuItemView() = default; |
| |
| void ExtensionMenuItemView::Update( |
| ExtensionsMenuViewModel::MenuItemInfo menu_item) { |
| if (!base::FeatureList::IsEnabled( |
| extensions_features::kExtensionsMenuAccessControl)) { |
| return; |
| } |
| |
| bool is_toggle_on = |
| menu_item.site_access_toggle_state == |
| ExtensionsMenuViewModel::MenuItemInfo::SiteAccessToggleState::kOn; |
| site_access_toggle_->SetVisible( |
| menu_item.site_access_toggle_state != |
| ExtensionsMenuViewModel::MenuItemInfo::SiteAccessToggleState::kHidden); |
| site_access_toggle_->SetIsOn(is_toggle_on); |
| site_access_toggle_->SetTooltipText(GetSiteAccessToggleTooltip(is_toggle_on)); |
| |
| site_permissions_button_->SetVisible( |
| menu_item.site_permissions_button_state != |
| ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonState:: |
| kHidden); |
| site_permissions_button_->SetEnabled( |
| menu_item.site_permissions_button_state == |
| ExtensionsMenuViewModel::MenuItemInfo::SitePermissionsButtonState:: |
| kEnabled); |
| std::u16string site_permissions_text = |
| GetSitePermissionsButtonText(menu_item.site_permissions_button_access); |
| site_permissions_button_->SetText(site_permissions_text); |
| site_permissions_button_->SetTooltipText(GetSitePermissionsButtonTooltip( |
| menu_item.is_enterprise, menu_item.site_permissions_button_access)); |
| site_permissions_button_->GetViewAccessibility().SetName( |
| GetSitePermissionsButtonAccName(menu_item.is_enterprise, |
| menu_item.site_permissions_button_access, |
| site_permissions_text)); |
| |
| // Update button size after changing its contents so it fits in the menu |
| // item row. |
| site_permissions_button_->PreferredSizeChanged(); |
| } |
| |
| void ExtensionMenuItemView::UpdatePinButton(bool is_force_pinned, |
| bool is_pinned) { |
| if (!pin_button_) { |
| return; |
| } |
| |
| pin_button_->SetTooltipText(GetPinButtonTooltip(is_force_pinned, is_pinned)); |
| pin_button_->GetViewAccessibility().SetName(GetPinButtonAccessibleName( |
| is_force_pinned, is_pinned, view_model_->GetActionName())); |
| // Extension pinning is not available in Incognito as it leaves a trace of |
| // user activity. |
| pin_button_->SetEnabled(!is_force_pinned && |
| !browser_->profile()->IsOffTheRecord()); |
| |
| // Update the icon based on whether the extension is pinned. |
| const gfx::VectorIcon& icon = is_pinned ? kKeepOffIcon : kKeepIcon; |
| const ui::ColorId icon_color_id = |
| is_pinned ? kColorExtensionMenuPinButtonIcon : kColorExtensionMenuIcon; |
| const ui::ColorId disabled_icon_color_id = |
| is_pinned ? kColorExtensionMenuPinButtonIconDisabled |
| : kColorExtensionMenuIconDisabled; |
| const int icon_size = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE); |
| |
| pin_button_->SetImageModel( |
| views::Button::STATE_NORMAL, |
| ui::ImageModel::FromVectorIcon(icon, icon_color_id, icon_size)); |
| pin_button_->SetImageModel( |
| views::Button::STATE_DISABLED, |
| ui::ImageModel::FromVectorIcon(icon, disabled_icon_color_id, icon_size)); |
| } |
| |
| void ExtensionMenuItemView::UpdateContextMenuButton(bool is_action_pinned) { |
| CHECK(base::FeatureList::IsEnabled( |
| extensions_features::kExtensionsMenuAccessControl)); |
| |
| const int icon_size = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_EXTENSIONS_MENU_BUTTON_ICON_SIZE); |
| auto three_dot_icon = ui::ImageModel::FromVectorIcon( |
| kBrowserToolsChromeRefreshIcon, kColorExtensionMenuIcon, icon_size); |
| |
| // Show a pin button for the context menu normal state icon when the action is |
| // pinned in the toolbar. All other states should look, and behave, the same. |
| context_menu_button_->SetImageModel( |
| views::Button::STATE_NORMAL, |
| is_action_pinned ? ui::ImageModel::FromVectorIcon( |
| kKeepIcon, kColorExtensionMenuIcon, icon_size) |
| : three_dot_icon); |
| context_menu_button_->SetImageModel(views::Button::STATE_HOVERED, |
| three_dot_icon); |
| context_menu_button_->SetImageModel(views::Button::STATE_PRESSED, |
| three_dot_icon); |
| context_menu_button_->GetViewAccessibility().SetName( |
| GetContextMenuAccessibleName(is_action_pinned, |
| view_model_->GetActionName())); |
| } |
| |
| void ExtensionMenuItemView::SetupContextMenuButton() { |
| // Add a controller to the context menu |
| context_menu_controller_ = std::make_unique<ExtensionContextMenuController>( |
| view_model_.get(), this, |
| extensions::ExtensionContextMenuModel::ContextMenuSource::kMenuItem); |
| |
| context_menu_button_->SetButtonController( |
| std::make_unique<views::MenuButtonController>( |
| context_menu_button_.get(), |
| base::BindRepeating(&ExtensionMenuItemView::OnContextMenuPressed, |
| base::Unretained(this)), |
| std::make_unique<views::Button::DefaultButtonControllerDelegate>( |
| context_menu_button_.get()))); |
| |
| // By default, the button's accessible description is set to the button's |
| // tooltip text. This is the accepted workaround to ensure only accessible |
| // name is announced by a screenreader rather than tooltip text and |
| // accessible name. |
| context_menu_button_->GetViewAccessibility().SetDescription( |
| std::u16string(), ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty); |
| |
| if (base::FeatureList::IsEnabled( |
| extensions_features::kExtensionsMenuAccessControl)) { |
| bool is_action_pinned = model_->IsActionPinned(view_model_->GetId()); |
| UpdateContextMenuButton(is_action_pinned); |
| } |
| } |
| |
| void ExtensionMenuItemView::OnContextMenuPressed() { |
| base::RecordAction(base::UserMetricsAction( |
| "Extensions.Toolbar.MoreActionsButtonPressedFromMenu")); |
| // TODO(crbug.com/41478477): Cleanup the menu source type. |
| context_menu_controller_->ShowContextMenuForViewImpl( |
| context_menu_button_, context_menu_button_->GetMenuPosition(), |
| ui::mojom::MenuSourceType::kMouse); |
| } |
| |
| void ExtensionMenuItemView::OnPinButtonPressed() { |
| CHECK(model_); |
| base::RecordAction( |
| base::UserMetricsAction("Extensions.Toolbar.PinButtonPressed")); |
| // Toggle action visibility. |
| bool new_action_visibility = !model_->IsActionPinned(view_model_->GetId()); |
| model_->SetActionVisibility(view_model_->GetId(), new_action_visibility); |
| GetViewAccessibility().AnnounceText( |
| GetPinButtonPressedAccText(new_action_visibility)); |
| } |
| |
| bool ExtensionMenuItemView::IsContextMenuRunningForTesting() const { |
| return context_menu_controller_->IsMenuRunning(); |
| } |
| |
| ExtensionsMenuButton* |
| ExtensionMenuItemView::primary_action_button_for_testing() { |
| return primary_action_button_; |
| } |
| |
| views::ToggleButton* ExtensionMenuItemView::site_access_toggle_for_testing() { |
| return site_access_toggle_; |
| } |
| |
| HoverButton* ExtensionMenuItemView::context_menu_button_for_testing() { |
| return context_menu_button_; |
| } |
| |
| HoverButton* ExtensionMenuItemView::pin_button_for_testing() { |
| return pin_button_; |
| } |
| |
| HoverButton* ExtensionMenuItemView::site_permissions_button_for_testing() { |
| return site_permissions_button_; |
| } |
| |
| void ExtensionMenuItemView::OnContextMenuShown() { |
| // Nothing to do. |
| } |
| |
| void ExtensionMenuItemView::OnContextMenuClosed() { |
| // Nothing to do. |
| } |
| |
| BEGIN_METADATA(ExtensionMenuItemView) |
| END_METADATA |