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_toolbar_container.h"
6 
7 #include "base/numerics/ranges.h"
8 #include "build/build_config.h"
9 #include "chrome/browser/ui/browser.h"
10 #include "chrome/browser/ui/browser_window.h"
11 #include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
12 #include "chrome/browser/ui/layout_constants.h"
13 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
14 #include "chrome/browser/ui/view_ids.h"
15 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
16 #include "chrome/browser/ui/views/extensions/extensions_menu_view.h"
17 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
18 #include "chrome/browser/ui/views/frame/browser_view.h"
19 #include "chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h"
20 #include "chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h"
21 #include "ui/views/layout/animating_layout_manager.h"
22 #include "ui/views/layout/flex_layout.h"
23 #include "ui/views/layout/flex_layout_types.h"
24 #include "ui/views/view_class_properties.h"
25 
26 struct ExtensionsToolbarContainer::DropInfo {
27   DropInfo(ToolbarActionsModel::ActionId action_id, size_t index);
28 
29   // The id for the action being dragged.
30   ToolbarActionsModel::ActionId action_id;
31 
32   // The (0-indexed) icon before the action will be dropped.
33   size_t index;
34 };
35 
DropInfo(ToolbarActionsModel::ActionId action_id,size_t index)36 ExtensionsToolbarContainer::DropInfo::DropInfo(
37     ToolbarActionsModel::ActionId action_id,
38     size_t index)
39     : action_id(action_id), index(index) {}
40 
ExtensionsToolbarContainer(Browser * browser,DisplayMode display_mode)41 ExtensionsToolbarContainer::ExtensionsToolbarContainer(Browser* browser,
42                                                        DisplayMode display_mode)
43     : ToolbarIconContainerView(/*uses_highlight=*/true),
44       browser_(browser),
45       model_(ToolbarActionsModel::Get(browser_->profile())),
46       model_observer_(this),
47       extensions_button_(new ExtensionsToolbarButton(browser_, this)),
48       display_mode_(display_mode) {
49   // The container shouldn't show unless / until we have extensions available.
50   SetVisible(false);
51 
52   model_observer_.Add(model_);
53   // Do not flip the Extensions icon in RTL.
54   extensions_button_->SetFlipCanvasOnPaintForRTLUI(false);
55 
56   const views::FlexSpecification hide_icon_flex_specification =
57       views::FlexSpecification(views::LayoutOrientation::kHorizontal,
58                                views::MinimumFlexSizeRule::kPreferredSnapToZero,
59                                views::MaximumFlexSizeRule::kPreferred)
60           .WithWeight(0);
61   switch (display_mode) {
62     case DisplayMode::kNormal:
63       // In normal mode, the menu icon is always shown.
64       extensions_button_->SetProperty(views::kFlexBehaviorKey,
65                                       views::FlexSpecification());
66       break;
67     case DisplayMode::kCompact:
68       // In compact mode, the menu icon can be hidden but has the highest
69       // priority.
70       extensions_button_->SetProperty(
71           views::kFlexBehaviorKey, hide_icon_flex_specification.WithOrder(1));
72       break;
73   }
74   extensions_button_->SetID(VIEW_ID_EXTENSIONS_MENU_BUTTON);
75   AddMainButton(extensions_button_);
76   target_layout_manager()
77       ->SetFlexAllocationOrder(views::FlexAllocationOrder::kReverse)
78       .SetDefault(views::kFlexBehaviorKey,
79                   hide_icon_flex_specification.WithOrder(3));
80   CreateActions();
81 
82   // TODO(pbos): Consider splitting out tab-strip observing into another class.
83   // Triggers for Extensions-related bubbles should preferably be separate from
84   // the container where they are shown.
85   browser_->tab_strip_model()->AddObserver(this);
86 }
87 
~ExtensionsToolbarContainer()88 ExtensionsToolbarContainer::~ExtensionsToolbarContainer() {
89   // The child views hold pointers to the |actions_|, and thus need to be
90   // destroyed before them.
91   RemoveAllChildViews(true);
92 
93   // Create a copy of the anchored widgets, since |anchored_widgets_| will
94   // be modified by closing them.
95   std::vector<views::Widget*> widgets;
96   widgets.reserve(anchored_widgets_.size());
97   for (const auto& anchored_widget : anchored_widgets_)
98     widgets.push_back(anchored_widget.widget);
99   for (auto* widget : widgets)
100     widget->Close();
101   // The widgets should close synchronously (resulting in OnWidgetClosing()),
102   // so |anchored_widgets_| should now be empty.
103   DCHECK(anchored_widgets_.empty());
104   CHECK(!views::WidgetObserver::IsInObserverList());
105 }
106 
UpdateAllIcons()107 void ExtensionsToolbarContainer::UpdateAllIcons() {
108   extensions_button_->UpdateIcon();
109   for (const auto& action : actions_)
110     action->UpdateState();
111 }
112 
GetViewForId(const std::string & id)113 ToolbarActionView* ExtensionsToolbarContainer::GetViewForId(
114     const std::string& id) {
115   const auto it = icons_.find(id);
116   return (it == icons_.end()) ? nullptr : it->second;
117 }
118 
ShowWidgetForExtension(views::Widget * widget,const std::string & extension_id)119 void ExtensionsToolbarContainer::ShowWidgetForExtension(
120     views::Widget* widget,
121     const std::string& extension_id) {
122   anchored_widgets_.push_back({widget, extension_id});
123   widget->AddObserver(this);
124   UpdateIconVisibility(extension_id);
125   animating_layout_manager()->PostOrQueueAction(base::BindOnce(
126       &ExtensionsToolbarContainer::AnchorAndShowWidgetImmediately,
127       weak_ptr_factory_.GetWeakPtr(), widget));
128 }
129 
130 views::Widget*
GetAnchoredWidgetForExtensionForTesting(const std::string & extension_id)131 ExtensionsToolbarContainer::GetAnchoredWidgetForExtensionForTesting(
132     const std::string& extension_id) {
133   auto iter = std::find_if(anchored_widgets_.begin(), anchored_widgets_.end(),
134                            [extension_id](const auto& info) {
135                              return info.extension_id == extension_id;
136                            });
137   return iter->widget;
138 }
139 
ShouldForceVisibility(const std::string & extension_id) const140 bool ExtensionsToolbarContainer::ShouldForceVisibility(
141     const std::string& extension_id) const {
142   if (popped_out_action_ && popped_out_action_->GetId() == extension_id)
143     return true;
144 
145   if (extension_with_open_context_menu_id_.has_value() &&
146       extension_with_open_context_menu_id_.value() == extension_id) {
147     return true;
148   }
149 
150   for (const auto& anchored_widget : anchored_widgets_) {
151     if (anchored_widget.extension_id == extension_id)
152       return true;
153   }
154 
155   return false;
156 }
157 
UpdateIconVisibility(const std::string & extension_id)158 void ExtensionsToolbarContainer::UpdateIconVisibility(
159     const std::string& extension_id) {
160   ToolbarActionView* const action_view = GetViewForId(extension_id);
161   if (!action_view)
162     return;
163 
164   // Popped out action uses a flex rule that causes it to always be visible
165   // regardless of space; default for actions is to drop out when there is
166   // insufficient space. So if an action is being forced visible, it should have
167   // a rule that gives it higher priority, and if it does not, it should use the
168   // default.
169   const bool must_show = ShouldForceVisibility(extension_id);
170   if (must_show) {
171     switch (display_mode_) {
172       case DisplayMode::kNormal:
173         // In normal mode, the icon's visibility is forced.
174         action_view->SetProperty(views::kFlexBehaviorKey,
175                                  views::FlexSpecification());
176         break;
177       case DisplayMode::kCompact:
178         // In compact mode, the icon can still drop out, but receives precedence
179         // over other actions.
180         action_view->SetProperty(
181             views::kFlexBehaviorKey,
182             views::FlexSpecification(
183                 views::MinimumFlexSizeRule::kPreferredSnapToZero,
184                 views::MaximumFlexSizeRule::kPreferred)
185                 .WithWeight(0)
186                 .WithOrder(2));
187         break;
188     }
189   } else {
190     action_view->ClearProperty(views::kFlexBehaviorKey);
191   }
192 
193   if (must_show ||
194       (CanShowIconInToolbar() && model_->IsActionPinned(extension_id)))
195     animating_layout_manager()->FadeIn(action_view);
196   else
197     animating_layout_manager()->FadeOut(action_view);
198 }
199 
AnchorAndShowWidgetImmediately(views::Widget * widget)200 void ExtensionsToolbarContainer::AnchorAndShowWidgetImmediately(
201     views::Widget* widget) {
202   auto iter = std::find_if(
203       anchored_widgets_.begin(), anchored_widgets_.end(),
204       [widget](const auto& info) { return info.widget == widget; });
205 
206   if (iter == anchored_widgets_.end()) {
207     // This should mean that the Widget destructed before we got to showing it.
208     // |widget| is invalid here and should not be shown.
209     return;
210   }
211 
212   // TODO(pbos): Make extension removal close associated widgets. Right now, it
213   // seems possible that:
214   // * ShowWidgetForExtension starts
215   // * Extension gets removed
216   // * AnchorAndShowWidgetImmediately runs.
217   // Revisit how to handle that, likely the Widget should Close on removal which
218   // would remove the AnchoredWidget entry.
219 
220   views::View* const anchor_view = GetViewForId(iter->extension_id);
221   widget->widget_delegate()->AsBubbleDialogDelegate()->SetAnchorView(
222       anchor_view && anchor_view->GetVisible() ? anchor_view
223                                                : extensions_button_);
224   widget->Show();
225 }
226 
GetActionForId(const std::string & action_id)227 ToolbarActionViewController* ExtensionsToolbarContainer::GetActionForId(
228     const std::string& action_id) {
229   for (const auto& action : actions_) {
230     if (action->GetId() == action_id)
231       return action.get();
232   }
233   return nullptr;
234 }
235 
GetPoppedOutAction() const236 ToolbarActionViewController* ExtensionsToolbarContainer::GetPoppedOutAction()
237     const {
238   return popped_out_action_;
239 }
240 
OnContextMenuShown(ToolbarActionViewController * extension)241 void ExtensionsToolbarContainer::OnContextMenuShown(
242     ToolbarActionViewController* extension) {
243   // Only update the extension's toolbar visibility if the context menu is being
244   // shown from an extension visible in the toolbar.
245   if (!ExtensionsMenuView::IsShowing()) {
246 #if defined(OS_MAC)
247     // TODO(crbug/1065584): Remove hiding active popup here once this bug is
248     // fixed.
249     HideActivePopup();
250 #endif
251     extension_with_open_context_menu_id_ = extension->GetId();
252     UpdateIconVisibility(extension_with_open_context_menu_id_.value());
253   }
254 }
255 
OnContextMenuClosed(ToolbarActionViewController * extension)256 void ExtensionsToolbarContainer::OnContextMenuClosed(
257     ToolbarActionViewController* extension) {
258   // |extension_with_open_context_menu_id_| does not have a value when a context
259   // menu is being shown from within the extensions menu.
260   if (extension_with_open_context_menu_id_.has_value()) {
261     base::Optional<extensions::ExtensionId> const
262         extension_with_open_context_menu = extension_with_open_context_menu_id_;
263     extension_with_open_context_menu_id_.reset();
264     UpdateIconVisibility(extension_with_open_context_menu.value());
265   }
266 }
267 
IsActionVisibleOnToolbar(const ToolbarActionViewController * action) const268 bool ExtensionsToolbarContainer::IsActionVisibleOnToolbar(
269     const ToolbarActionViewController* action) const {
270   const std::string& extension_id = action->GetId();
271   return ShouldForceVisibility(extension_id) ||
272          model_->IsActionPinned(extension_id);
273 }
274 
275 extensions::ExtensionContextMenuModel::ButtonVisibility
GetActionVisibility(const ToolbarActionViewController * action) const276 ExtensionsToolbarContainer::GetActionVisibility(
277     const ToolbarActionViewController* action) const {
278   extensions::ExtensionContextMenuModel::ButtonVisibility visibility =
279       extensions::ExtensionContextMenuModel::PINNED;
280 
281   if (ShouldForceVisibility(action->GetId()) &&
282       !model_->IsActionPinned(action->GetId())) {
283     visibility = extensions::ExtensionContextMenuModel::TRANSITIVELY_VISIBLE;
284   } else if (!IsActionVisibleOnToolbar(action)) {
285     visibility = extensions::ExtensionContextMenuModel::UNPINNED;
286   }
287   return visibility;
288 }
289 
UndoPopOut()290 void ExtensionsToolbarContainer::UndoPopOut() {
291   DCHECK(popped_out_action_);
292   ToolbarActionViewController* const popped_out_action = popped_out_action_;
293   popped_out_action_ = nullptr;
294   UpdateIconVisibility(popped_out_action->GetId());
295 }
296 
SetPopupOwner(ToolbarActionViewController * popup_owner)297 void ExtensionsToolbarContainer::SetPopupOwner(
298     ToolbarActionViewController* popup_owner) {
299   // We should never be setting a popup owner when one already exists, and
300   // never unsetting one when one wasn't set.
301   DCHECK((popup_owner_ != nullptr) ^ (popup_owner != nullptr));
302   popup_owner_ = popup_owner;
303 }
304 
HideActivePopup()305 void ExtensionsToolbarContainer::HideActivePopup() {
306   if (popup_owner_)
307     popup_owner_->HidePopup();
308   DCHECK(!popup_owner_);
309 }
310 
CloseOverflowMenuIfOpen()311 bool ExtensionsToolbarContainer::CloseOverflowMenuIfOpen() {
312   if (ExtensionsMenuView::IsShowing()) {
313     ExtensionsMenuView::Hide();
314     return true;
315   }
316   return false;
317 }
318 
PopOutAction(ToolbarActionViewController * action,bool is_sticky,const base::Closure & closure)319 void ExtensionsToolbarContainer::PopOutAction(
320     ToolbarActionViewController* action,
321     bool is_sticky,
322     const base::Closure& closure) {
323   // TODO(pbos): Highlight popout differently.
324   DCHECK(!popped_out_action_);
325   popped_out_action_ = action;
326   UpdateIconVisibility(action->GetId());
327   animating_layout_manager()->PostOrQueueAction(closure);
328 }
329 
ShowToolbarActionPopupForAPICall(const std::string & action_id)330 bool ExtensionsToolbarContainer::ShowToolbarActionPopupForAPICall(
331     const std::string& action_id) {
332   // Don't override another popup, and only show in the active window.
333   if (popped_out_action_ || !browser_->window()->IsActive())
334     return false;
335 
336   ToolbarActionViewController* action = GetActionForId(action_id);
337   // Since this was triggered by an API call, we never want to grant activeTab
338   // to the extension.
339   constexpr bool kGrantActiveTab = false;
340   return action && action->ExecuteAction(
341                        kGrantActiveTab,
342                        ToolbarActionViewController::InvocationSource::kApi);
343 }
344 
ShowToolbarActionBubble(std::unique_ptr<ToolbarActionsBarBubbleDelegate> controller)345 void ExtensionsToolbarContainer::ShowToolbarActionBubble(
346     std::unique_ptr<ToolbarActionsBarBubbleDelegate> controller) {
347   const std::string extension_id = controller->GetAnchorActionId();
348 
349   views::View* const anchor_view = GetViewForId(extension_id);
350 
351   views::Widget* const widget = views::BubbleDialogDelegateView::CreateBubble(
352       std::make_unique<ToolbarActionsBarBubbleViews>(
353           anchor_view ? anchor_view : extensions_button_,
354           anchor_view != nullptr, std::move(controller)));
355 
356   ShowWidgetForExtension(widget, extension_id);
357 }
358 
ShowToolbarActionBubbleAsync(std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble)359 void ExtensionsToolbarContainer::ShowToolbarActionBubbleAsync(
360     std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) {
361   ShowToolbarActionBubble(std::move(bubble));
362 }
363 
OnTabStripModelChanged(TabStripModel * tab_strip_model,const TabStripModelChange & change,const TabStripSelectionChange & selection)364 void ExtensionsToolbarContainer::OnTabStripModelChanged(
365     TabStripModel* tab_strip_model,
366     const TabStripModelChange& change,
367     const TabStripSelectionChange& selection) {
368   if (tab_strip_model->empty() || !selection.active_tab_changed())
369     return;
370 
371   extensions::MaybeShowExtensionControlledNewTabPage(browser_,
372                                                      selection.new_contents);
373 }
374 
OnToolbarActionAdded(const ToolbarActionsModel::ActionId & action_id,int index)375 void ExtensionsToolbarContainer::OnToolbarActionAdded(
376     const ToolbarActionsModel::ActionId& action_id,
377     int index) {
378   CreateActionForId(action_id);
379   ReorderViews();
380   UpdateContainerVisibility();
381 }
382 
OnToolbarActionRemoved(const ToolbarActionsModel::ActionId & action_id)383 void ExtensionsToolbarContainer::OnToolbarActionRemoved(
384     const ToolbarActionsModel::ActionId& action_id) {
385   // TODO(pbos): Handle extension upgrades, see ToolbarActionsBar. Arguably this
386   // could be handled inside the model and be invisible to the container when
387   // permissions are unchanged.
388 
389   auto iter = std::find_if(
390       actions_.begin(), actions_.end(),
391       [action_id](const auto& item) { return item->GetId() == action_id; });
392   DCHECK(iter != actions_.end());
393   // Ensure the action outlives the UI element to perform any cleanup.
394   std::unique_ptr<ToolbarActionViewController> controller = std::move(*iter);
395   actions_.erase(iter);
396   // Undo the popout, if necessary. Actions expect to not be popped out while
397   // destroying.
398   if (popped_out_action_ == controller.get())
399     UndoPopOut();
400 
401   RemoveChildViewT(GetViewForId(action_id));
402   icons_.erase(action_id);
403 
404   UpdateContainerVisibility();
405 }
406 
OnToolbarActionMoved(const ToolbarActionsModel::ActionId & action_id,int index)407 void ExtensionsToolbarContainer::OnToolbarActionMoved(
408     const ToolbarActionsModel::ActionId& action_id,
409     int index) {}
410 
OnToolbarActionLoadFailed()411 void ExtensionsToolbarContainer::OnToolbarActionLoadFailed() {}
412 
OnToolbarActionUpdated(const ToolbarActionsModel::ActionId & action_id)413 void ExtensionsToolbarContainer::OnToolbarActionUpdated(
414     const ToolbarActionsModel::ActionId& action_id) {
415   ToolbarActionViewController* action = GetActionForId(action_id);
416   if (action)
417     action->UpdateState();
418 }
419 
OnToolbarVisibleCountChanged()420 void ExtensionsToolbarContainer::OnToolbarVisibleCountChanged() {}
421 
OnToolbarHighlightModeChanged(bool is_highlighting)422 void ExtensionsToolbarContainer::OnToolbarHighlightModeChanged(
423     bool is_highlighting) {
424   NOTREACHED()
425       << "Action highlighting is not supported with the extensions menu";
426 }
427 
OnToolbarModelInitialized()428 void ExtensionsToolbarContainer::OnToolbarModelInitialized() {
429   CreateActions();
430 }
431 
OnToolbarPinnedActionsChanged()432 void ExtensionsToolbarContainer::OnToolbarPinnedActionsChanged() {
433   for (const auto& it : icons_)
434     UpdateIconVisibility(it.first);
435   ReorderViews();
436 }
437 
ReorderViews()438 void ExtensionsToolbarContainer::ReorderViews() {
439   const auto& pinned_action_ids = model_->pinned_action_ids();
440   for (size_t i = 0; i < pinned_action_ids.size(); ++i)
441     ReorderChildView(GetViewForId(pinned_action_ids[i]), i);
442 
443   if (drop_info_.get())
444     ReorderChildView(GetViewForId(drop_info_->action_id), drop_info_->index);
445 
446   // The extension button is always last.
447   ReorderChildView(extensions_button_, -1);
448 }
449 
CreateActions()450 void ExtensionsToolbarContainer::CreateActions() {
451   DCHECK(icons_.empty());
452   DCHECK(actions_.empty());
453 
454   // If the model isn't initialized, wait for it.
455   if (!model_->actions_initialized())
456     return;
457 
458   for (const auto& action_id : model_->action_ids())
459     CreateActionForId(action_id);
460 
461   ReorderViews();
462   UpdateContainerVisibility();
463 }
464 
CreateActionForId(const ToolbarActionsModel::ActionId & action_id)465 void ExtensionsToolbarContainer::CreateActionForId(
466     const ToolbarActionsModel::ActionId& action_id) {
467   actions_.push_back(
468       model_->CreateActionForId(browser_, this, false, action_id));
469   auto icon = std::make_unique<ToolbarActionView>(actions_.back().get(), this);
470   // Set visibility before adding to prevent extraneous animation.
471   icon->SetVisible(CanShowIconInToolbar() && model_->IsActionPinned(action_id));
472   ObserveButton(icon.get());
473   icons_.insert({action_id, AddChildView(std::move(icon))});
474 }
475 
GetCurrentWebContents()476 content::WebContents* ExtensionsToolbarContainer::GetCurrentWebContents() {
477   return browser_->tab_strip_model()->GetActiveWebContents();
478 }
479 
ShownInsideMenu() const480 bool ExtensionsToolbarContainer::ShownInsideMenu() const {
481   return false;
482 }
483 
CanShowIconInToolbar() const484 bool ExtensionsToolbarContainer::CanShowIconInToolbar() const {
485   // Pinning extensions is not available in PWAs.
486   return !browser_->app_controller();
487 }
488 
OnToolbarActionViewDragDone()489 void ExtensionsToolbarContainer::OnToolbarActionViewDragDone() {}
490 
GetOverflowReferenceView() const491 views::LabelButton* ExtensionsToolbarContainer::GetOverflowReferenceView()
492     const {
493   return extensions_button_;
494 }
495 
GetToolbarActionSize()496 gfx::Size ExtensionsToolbarContainer::GetToolbarActionSize() {
497   constexpr gfx::Size kDefaultSize(28, 28);
498   BrowserView* const browser_view =
499       BrowserView::GetBrowserViewForBrowser(browser_);
500   return browser_view
501              ? browser_view->toolbar_button_provider()->GetToolbarButtonSize()
502              : kDefaultSize;
503 }
504 
WriteDragDataForView(View * sender,const gfx::Point & press_pt,ui::OSExchangeData * data)505 void ExtensionsToolbarContainer::WriteDragDataForView(
506     View* sender,
507     const gfx::Point& press_pt,
508     ui::OSExchangeData* data) {
509   DCHECK(data);
510 
511   auto it = std::find_if(model_->pinned_action_ids().cbegin(),
512                          model_->pinned_action_ids().cend(),
513                          [this, sender](const std::string& action_id) {
514                            return GetViewForId(action_id) == sender;
515                          });
516   DCHECK(it != model_->pinned_action_ids().cend());
517 
518   size_t index = it - model_->pinned_action_ids().cbegin();
519 
520   ToolbarActionView* extension_view = GetViewForId(*it);
521   data->provider().SetDragImage(GetExtensionIcon(extension_view),
522                                 press_pt.OffsetFromOrigin());
523   // Fill in the remaining info.
524   BrowserActionDragData drag_data(extension_view->view_controller()->GetId(),
525                                   index);
526   drag_data.Write(browser_->profile(), data);
527 }
528 
GetDragOperationsForView(View * sender,const gfx::Point & p)529 int ExtensionsToolbarContainer::GetDragOperationsForView(View* sender,
530                                                          const gfx::Point& p) {
531   return ui::DragDropTypes::DRAG_MOVE;
532 }
533 
CanStartDragForView(View * sender,const gfx::Point & press_pt,const gfx::Point & p)534 bool ExtensionsToolbarContainer::CanStartDragForView(View* sender,
535                                                      const gfx::Point& press_pt,
536                                                      const gfx::Point& p) {
537   if (!CanShowIconInToolbar())
538     return false;
539 
540   // Only pinned extensions should be draggable.
541   auto it = std::find_if(model_->pinned_action_ids().cbegin(),
542                          model_->pinned_action_ids().cend(),
543                          [this, sender](const std::string& action_id) {
544                            return GetViewForId(action_id) == sender;
545                          });
546   return it != model_->pinned_action_ids().cend();
547 }
548 
GetDropFormats(int * formats,std::set<ui::ClipboardFormatType> * format_types)549 bool ExtensionsToolbarContainer::GetDropFormats(
550     int* formats,
551     std::set<ui::ClipboardFormatType>* format_types) {
552   return BrowserActionDragData::GetDropFormats(format_types);
553 }
554 
AreDropTypesRequired()555 bool ExtensionsToolbarContainer::AreDropTypesRequired() {
556   return BrowserActionDragData::AreDropTypesRequired();
557 }
558 
CanDrop(const OSExchangeData & data)559 bool ExtensionsToolbarContainer::CanDrop(const OSExchangeData& data) {
560   return BrowserActionDragData::CanDrop(data, browser_->profile());
561 }
562 
OnDragUpdated(const ui::DropTargetEvent & event)563 int ExtensionsToolbarContainer::OnDragUpdated(
564     const ui::DropTargetEvent& event) {
565   BrowserActionDragData data;
566   if (!data.Read(event.data()))
567     return ui::DragDropTypes::DRAG_NONE;
568   size_t before_icon = 0;
569   // Figure out where to display the icon during dragging transition.
570 
571   // First, since we want to update the dragged extension's position from before
572   // an icon to after it when the event passes the midpoint between two icons.
573   // This will convert the event coordinate into the index of the icon we want
574   // to display the dragged extension before. We also mirror the event.x() so
575   // that our calculations are consistent with left-to-right.
576   const int offset_into_icon_area = GetMirroredXInView(event.x());
577   const int before_icon_unclamped = WidthToIconCount(offset_into_icon_area);
578 
579   int visible_icons = model_->pinned_action_ids().size();
580 
581   // Because the user can drag outside the container bounds, we need to clamp
582   // to the valid range. Note that the maximum allowable value is
583   // |visible_icons|, not (|visible_icons| - 1), because we represent the
584   // dragged extension being past the last icon as being "before the (last + 1)
585   // icon".
586   before_icon = base::ClampToRange(before_icon_unclamped, 0, visible_icons);
587 
588   if (!drop_info_.get() || drop_info_->index != before_icon) {
589     drop_info_ = std::make_unique<DropInfo>(data.id(), before_icon);
590     SetExtensionIconVisibility(drop_info_->action_id, false);
591     ReorderViews();
592   }
593 
594   return ui::DragDropTypes::DRAG_MOVE;
595 }
596 
OnDragExited()597 void ExtensionsToolbarContainer::OnDragExited() {
598   const ToolbarActionsModel::ActionId dragged_extension_id =
599       drop_info_->action_id;
600   drop_info_.reset();
601   ReorderViews();
602   animating_layout_manager()->PostOrQueueAction(base::BindOnce(
603       &ExtensionsToolbarContainer::SetExtensionIconVisibility,
604       weak_ptr_factory_.GetWeakPtr(), dragged_extension_id, true));
605 }
606 
OnPerformDrop(const ui::DropTargetEvent & event)607 int ExtensionsToolbarContainer::OnPerformDrop(
608     const ui::DropTargetEvent& event) {
609   BrowserActionDragData data;
610   if (!data.Read(event.data()))
611     return ui::DragDropTypes::DRAG_NONE;
612 
613   model_->MovePinnedAction(drop_info_->action_id, drop_info_->index);
614 
615   OnDragExited();  // Perform clean up after dragging.
616   return ui::DragDropTypes::DRAG_MOVE;
617 }
618 
GetClassName() const619 const char* ExtensionsToolbarContainer::GetClassName() const {
620   return "ExtensionsToolbarContainer";
621 }
622 
OnWidgetClosing(views::Widget * widget)623 void ExtensionsToolbarContainer::OnWidgetClosing(views::Widget* widget) {
624   auto iter = std::find_if(
625       anchored_widgets_.begin(), anchored_widgets_.end(),
626       [widget](const auto& info) { return info.widget == widget; });
627   DCHECK(iter != anchored_widgets_.end());
628   iter->widget->RemoveObserver(this);
629   const std::string extension_id = std::move(iter->extension_id);
630   anchored_widgets_.erase(iter);
631   UpdateIconVisibility(extension_id);
632 }
633 
OnWidgetDestroying(views::Widget * widget)634 void ExtensionsToolbarContainer::OnWidgetDestroying(views::Widget* widget) {
635   OnWidgetClosing(widget);
636 }
637 
WidthToIconCount(int x_offset)638 size_t ExtensionsToolbarContainer::WidthToIconCount(int x_offset) {
639   const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
640   size_t unclamped_count =
641       std::max((x_offset + element_padding) /
642                    (GetToolbarActionSize().width() + element_padding),
643                0);
644   return std::min(unclamped_count, actions_.size());
645 }
646 
GetExtensionIcon(ToolbarActionView * extension_view)647 gfx::ImageSkia ExtensionsToolbarContainer::GetExtensionIcon(
648     ToolbarActionView* extension_view) {
649   return extension_view->view_controller()
650       ->GetIcon(GetCurrentWebContents(), GetToolbarActionSize())
651       .AsImageSkia();
652 }
653 
SetExtensionIconVisibility(ToolbarActionsModel::ActionId id,bool visible)654 void ExtensionsToolbarContainer::SetExtensionIconVisibility(
655     ToolbarActionsModel::ActionId id,
656     bool visible) {
657   auto it = std::find_if(model_->pinned_action_ids().cbegin(),
658                          model_->pinned_action_ids().cend(),
659                          [this, id](const std::string& action_id) {
660                            return GetViewForId(action_id) == GetViewForId(id);
661                          });
662   ToolbarActionView* extension_view = GetViewForId(*it);
663   extension_view->SetImageModel(
664       views::Button::STATE_NORMAL,
665       visible ? ui::ImageModel::FromImageSkia(GetExtensionIcon(extension_view))
666               : ui::ImageModel());
667 }
668 
UpdateContainerVisibility()669 void ExtensionsToolbarContainer::UpdateContainerVisibility() {
670   // The container (and extensions-menu button) should be visible if we have at
671   // least one extension.
672   SetVisible(!actions_.empty());
673 }
674