1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/extensions/extensions_menu_item_view.h"
6 
7 #include <utility>
8 
9 #include "base/metrics/user_metrics.h"
10 #include "base/metrics/user_metrics_action.h"
11 #include "chrome/app/vector_icons/vector_icons.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
15 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
16 #include "chrome/browser/ui/views/bubble_menu_item_factory.h"
17 #include "chrome/browser/ui/views/extensions/extension_context_menu_controller.h"
18 #include "chrome/browser/ui/views/extensions/extensions_menu_button.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/gfx/color_utils.h"
22 #include "ui/gfx/paint_vector_icon.h"
23 #include "ui/views/animation/ink_drop_host_view.h"
24 #include "ui/views/controls/button/button.h"
25 #include "ui/views/controls/button/image_button.h"
26 #include "ui/views/controls/button/image_button_factory.h"
27 #include "ui/views/layout/flex_layout.h"
28 #include "ui/views/layout/flex_layout_types.h"
29 #include "ui/views/vector_icons.h"
30 #include "ui/views/view_class_properties.h"
31 
32 namespace {
33 constexpr int kSecondaryIconSizeDp = 16;
34 // Set secondary item insets to get to square buttons.
35 constexpr gfx::Insets kSecondaryButtonInsets = gfx::Insets(
36     (ExtensionsMenuItemView::kMenuItemHeightDp - kSecondaryIconSizeDp) / 2);
37 constexpr int EXTENSION_CONTEXT_MENU = 13;
38 constexpr int EXTENSION_PINNING = 14;
39 }  // namespace
40 
41 // static
42 constexpr gfx::Size ExtensionsMenuItemView::kIconSize;
43 constexpr char ExtensionsMenuItemView::kClassName[];
44 
ExtensionsMenuItemView(Browser * browser,std::unique_ptr<ToolbarActionViewController> controller,bool allow_pinning)45 ExtensionsMenuItemView::ExtensionsMenuItemView(
46     Browser* browser,
47     std::unique_ptr<ToolbarActionViewController> controller,
48     bool allow_pinning)
49     : profile_(browser->profile()),
50       primary_action_button_(new ExtensionsMenuButton(browser,
51                                                       this,
52                                                       controller.get(),
53                                                       allow_pinning)),
54       controller_(std::move(controller)),
55       model_(ToolbarActionsModel::Get(profile_)) {
56   // Set so the extension button receives enter/exit on children to retain hover
57   // status when hovering child views.
58   SetNotifyEnterExitOnChild(true);
59 
60   context_menu_controller_ = std::make_unique<ExtensionContextMenuController>(
61       nullptr, controller_.get());
62 
63   views::FlexLayout* layout_manager_ =
64       SetLayoutManager(std::make_unique<views::FlexLayout>());
65   layout_manager_->SetOrientation(views::LayoutOrientation::kHorizontal)
66       .SetIgnoreDefaultMainAxisMargins(true);
67 
68   AddChildView(primary_action_button_);
69   primary_action_button_->SetProperty(
70       views::kFlexBehaviorKey,
71       views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
72                                views::MaximumFlexSizeRule::kUnbounded));
73 
74   if (primary_action_button_->CanShowIconInToolbar()) {
75     auto pin_button = CreateBubbleMenuItem(
76         EXTENSION_PINNING,
77         base::BindRepeating(&ExtensionsMenuItemView::PinButtonPressed,
78                             base::Unretained(this)));
79     pin_button->SetBorder(views::CreateEmptyBorder(kSecondaryButtonInsets));
80 
81     pin_button_ = pin_button.get();
82     AddChildView(std::move(pin_button));
83   }
84   UpdatePinButton();
85 
86   auto context_menu_button = CreateBubbleMenuItem(
87       EXTENSION_CONTEXT_MENU, views::Button::PressedCallback());
88   context_menu_button->SetBorder(
89       views::CreateEmptyBorder(kSecondaryButtonInsets));
90   context_menu_button->SetTooltipText(
91       l10n_util::GetStringUTF16(IDS_EXTENSIONS_MENU_CONTEXT_MENU_TOOLTIP));
92   context_menu_button->SetButtonController(
93       std::make_unique<views::MenuButtonController>(
94           context_menu_button.get(),
95           base::BindRepeating(&ExtensionsMenuItemView::ContextMenuPressed,
96                               base::Unretained(this)),
97           std::make_unique<views::Button::DefaultButtonControllerDelegate>(
98               context_menu_button.get())));
99   context_menu_button_ = AddChildView(std::move(context_menu_button));
100 }
101 
102 ExtensionsMenuItemView::~ExtensionsMenuItemView() = default;
103 
GetClassName() const104 const char* ExtensionsMenuItemView::GetClassName() const {
105   return kClassName;
106 }
107 
OnThemeChanged()108 void ExtensionsMenuItemView::OnThemeChanged() {
109   views::View::OnThemeChanged();
110   const SkColor icon_color =
111       GetAdjustedIconColor(GetNativeTheme()->GetSystemColor(
112           ui::NativeTheme::kColorId_MenuIconColor));
113 
114   if (pin_button_)
115     pin_button_->SetInkDropBaseColor(icon_color);
116   views::SetImageFromVectorIconWithColor(context_menu_button_,
117                                          kBrowserToolsIcon,
118                                          kSecondaryIconSizeDp, icon_color);
119   UpdatePinButton();
120 }
121 
UpdatePinButton()122 void ExtensionsMenuItemView::UpdatePinButton() {
123   if (!pin_button_)
124     return;
125 
126   bool is_force_pinned =
127       model_ && model_->IsActionForcePinned(controller_->GetId());
128   int pin_button_string_id = 0;
129   if (is_force_pinned)
130     pin_button_string_id = IDS_EXTENSIONS_PINNED_BY_ADMIN;
131   else if (IsPinned())
132     pin_button_string_id = IDS_EXTENSIONS_UNPIN_FROM_TOOLBAR;
133   else
134     pin_button_string_id = IDS_EXTENSIONS_PIN_TO_TOOLBAR;
135   pin_button_->SetTooltipText(l10n_util::GetStringUTF16(pin_button_string_id));
136   // Extension pinning is not available in Incognito as it leaves a trace of
137   // user activity.
138   pin_button_->SetEnabled(!is_force_pinned && !profile_->IsOffTheRecord());
139 
140   SkColor unpinned_icon_color =
141       GetAdjustedIconColor(GetNativeTheme()->GetSystemColor(
142           ui::NativeTheme::kColorId_MenuIconColor));
143   SkColor icon_color =
144       IsPinned() ? GetAdjustedIconColor(GetNativeTheme()->GetSystemColor(
145                        ui::NativeTheme::kColorId_ProminentButtonColor))
146                  : unpinned_icon_color;
147   views::SetImageFromVectorIconWithColor(
148       pin_button_, IsPinned() ? views::kUnpinIcon : views::kPinIcon,
149       kSecondaryIconSizeDp, icon_color);
150 }
151 
IsContextMenuRunning() const152 bool ExtensionsMenuItemView::IsContextMenuRunning() const {
153   return context_menu_controller_->IsMenuRunning();
154 }
155 
IsPinned() const156 bool ExtensionsMenuItemView::IsPinned() const {
157   // |model_| can be null in unit tests.
158   return model_ && model_->IsActionPinned(controller_->GetId());
159 }
160 
ContextMenuPressed()161 void ExtensionsMenuItemView::ContextMenuPressed() {
162   base::RecordAction(base::UserMetricsAction(
163       "Extensions.Toolbar.MoreActionsButtonPressedFromMenu"));
164   // TODO(crbug.com/998298): Cleanup the menu source type.
165   context_menu_controller_->ShowContextMenuForViewImpl(
166       context_menu_button_, context_menu_button_->GetMenuPosition(),
167       ui::MenuSourceType::MENU_SOURCE_MOUSE);
168 }
169 
PinButtonPressed()170 void ExtensionsMenuItemView::PinButtonPressed() {
171   base::RecordAction(
172       base::UserMetricsAction("Extensions.Toolbar.PinButtonPressed"));
173   model_->SetActionVisibility(controller_->GetId(), !IsPinned());
174 }
175 
176 ExtensionsMenuButton*
primary_action_button_for_testing()177 ExtensionsMenuItemView::primary_action_button_for_testing() {
178   return primary_action_button_;
179 }
180 
GetAdjustedIconColor(SkColor icon_color) const181 SkColor ExtensionsMenuItemView::GetAdjustedIconColor(SkColor icon_color) const {
182   const SkColor background_color = GetNativeTheme()->GetSystemColor(
183       ui::NativeTheme::kColorId_BubbleBackground);
184   if (background_color != SK_ColorTRANSPARENT) {
185     return color_utils::BlendForMinContrast(icon_color, background_color).color;
186   }
187   return icon_color;
188 }
189