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