1 // Copyright 2014 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.
5 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
7 #include "base/auto_reset.h"
8 #include "chrome/browser/extensions/extension_action_manager.h"
9 #include "chrome/browser/extensions/extension_util.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/sessions/session_tab_helper.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_window.h"
14 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
17 #include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
18 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
19 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h"
20 #include "components/crx_file/id_util.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/runtime_data.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/feature_switch.h"
25 #include "grit/theme_resources.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/image/image_skia.h"
31 using WeakToolbarActions
= std::vector
<ToolbarActionViewController
*>;
33 // Matches ToolbarView::kStandardSpacing;
34 const int kLeftPadding
= 3;
35 const int kRightPadding
= kLeftPadding
;
36 const int kItemSpacing
= kLeftPadding
;
37 const int kOverflowLeftPadding
= kItemSpacing
;
38 const int kOverflowRightPadding
= kItemSpacing
;
40 enum DimensionType
{ WIDTH
, HEIGHT
};
42 // Returns the width or height of the toolbar action icon size.
43 int GetIconDimension(DimensionType type
) {
44 static bool initialized
= false;
45 static int icon_height
= 0;
46 static int icon_width
= 0;
49 gfx::ImageSkia
* skia
=
50 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
52 icon_height
= skia
->height();
53 icon_width
= skia
->width();
55 return type
== WIDTH
? icon_width
: icon_height
;
58 // Takes a reference vector |reference| of length n, where n is less than or
59 // equal to the length of |to_sort|, and rearranges |to_sort| so that
60 // |to_sort|'s first n elements match the n elements of |reference| (the order
61 // of any remaining elements in |to_sort| is unspecified).
62 // |equal| is used to compare the elements of |to_sort| and |reference|.
63 // This allows us to sort a vector to match another vector of two different
64 // types without needing to construct a more cumbersome comparator class.
65 // |FunctionType| should equate to (something similar to)
66 // bool Equal(const Type1&, const Type2&), but we can't enforce this
67 // because of MSVC compilation limitations.
68 template<typename Type1
, typename Type2
, typename FunctionType
>
69 void SortContainer(std::vector
<Type1
>* to_sort
,
70 const std::vector
<Type2
>& reference
,
72 DCHECK_GE(to_sort
->size(), reference
.size()) <<
73 "|to_sort| must contain all elements in |reference|.";
74 if (reference
.empty())
76 // Run through the each element and compare it to the reference. If something
77 // is out of place, find the correct spot for it.
78 for (size_t i
= 0; i
< reference
.size() - 1; ++i
) {
79 if (!equal(to_sort
->at(i
), reference
[i
])) {
80 // Find the correct index (it's guaranteed to be after our current
81 // index, since everything up to this point is correct), and swap.
83 while (!equal(to_sort
->at(j
), reference
[i
])) {
85 DCHECK_LE(j
, to_sort
->size()) <<
86 "Item in |reference| not found in |to_sort|.";
88 std::swap(to_sort
->at(i
), to_sort
->at(j
));
96 bool ToolbarActionsBar::disable_animations_for_testing_
= false;
99 bool ToolbarActionsBar::pop_out_actions_to_run_
= false;
102 bool ToolbarActionsBar::send_overflowed_action_changes_
= true;
104 // A class to implement an optional tab ordering that "pops out" actions that
105 // want to run on a particular page, as part of our experimentation with how to
106 // best signal that actions like extension page actions want to run.
107 // TODO(devlin): Once we finally settle on the right behavior, determine if
109 class ToolbarActionsBar::TabOrderHelper
110 : public TabStripModelObserver
{
112 TabOrderHelper(ToolbarActionsBar
* toolbar
,
114 extensions::ExtensionToolbarModel
* model
);
115 ~TabOrderHelper() override
;
117 // Returns the number of extra icons that should appear on the given |tab_id|
118 // because of actions that are going to pop out.
119 size_t GetExtraIconCount(int tab_id
);
121 // Returns the item order of actions for the tab with the given
123 WeakToolbarActions
GetActionOrder(content::WebContents
* web_contents
);
125 // Notifies the TabOrderHelper that a given |action| does/doesn't want to run
126 // on the tab indicated by |tab_id|.
127 void SetActionWantsToRun(ToolbarActionViewController
* action
,
131 // Notifies the TabOrderHelper that a given |action| has been removed.
132 void ActionRemoved(ToolbarActionViewController
* action
);
134 // Handles a resize, including updating any actions that want to act and
135 // updating the model.
136 void HandleResize(size_t resized_count
, int tab_id
);
138 // Handles a drag and drop, including updating any actions that want to act
139 // and updating the model.
140 void HandleDragDrop(int dragged
,
142 ToolbarActionsBar::DragType drag_type
,
145 void notify_overflow_bar(ToolbarActionsBar
* overflow_bar
,
146 bool should_notify
) {
147 // There's a possibility that a new overflow bar can be constructed before
148 // the first is fully destroyed. Only un-register the |overflow_bar_| if
149 // it's the one making the request.
151 overflow_bar_
= overflow_bar
;
152 else if (overflow_bar_
== overflow_bar
)
153 overflow_bar_
= nullptr;
157 // TabStripModelObserver:
158 void TabInsertedAt(content::WebContents
* web_contents
,
160 bool foreground
) override
;
161 void TabDetachedAt(content::WebContents
* web_contents
, int index
) override
;
162 void ActiveTabChanged(content::WebContents
* old_contents
,
163 content::WebContents
* new_contents
,
165 int reason
) override
;
166 void TabStripModelDeleted() override
;
168 // Notifies the main |toolbar_| and, if present, the |overflow_bar_| that
169 // actions need to be reordered.
170 void NotifyReorderActions();
172 // The set of tabs for the given action (the key) is currently "popped out".
173 // "Popped out" actions are those that were in the overflow menu normally, but
174 // want to run and are moved to the main bar so the user can see them.
175 std::map
<ToolbarActionViewController
*, std::set
<int>> popped_out_in_tabs_
;
177 // The set of tab ids that have been checked for whether actions need to be
178 // popped out or not.
179 std::set
<int> tabs_checked_for_pop_out_
;
181 // The owning ToolbarActionsBar.
182 ToolbarActionsBar
* toolbar_
;
184 // The overflow bar, if one is present.
185 ToolbarActionsBar
* overflow_bar_
;
187 // The associated toolbar model.
188 extensions::ExtensionToolbarModel
* model_
;
190 // A scoped tab strip observer so we can clean up |tabs_checked_for_popout_|.
191 ScopedObserver
<TabStripModel
, TabStripModelObserver
> tab_strip_observer_
;
193 DISALLOW_COPY_AND_ASSIGN(TabOrderHelper
);
196 ToolbarActionsBar::TabOrderHelper::TabOrderHelper(
197 ToolbarActionsBar
* toolbar
,
199 extensions::ExtensionToolbarModel
* model
)
201 overflow_bar_(nullptr),
203 tab_strip_observer_(this) {
204 tab_strip_observer_
.Add(browser
->tab_strip_model());
207 ToolbarActionsBar::TabOrderHelper::~TabOrderHelper() {
210 size_t ToolbarActionsBar::TabOrderHelper::GetExtraIconCount(int tab_id
) {
211 size_t extra_icons
= 0;
212 const WeakToolbarActions
& toolbar_actions
= toolbar_
->toolbar_actions();
213 for (ToolbarActionViewController
* action
: toolbar_actions
) {
214 auto actions_tabs
= popped_out_in_tabs_
.find(action
);
215 if (actions_tabs
!= popped_out_in_tabs_
.end() &&
216 actions_tabs
->second
.count(tab_id
))
222 void ToolbarActionsBar::TabOrderHelper::HandleResize(size_t resized_count
,
224 int extra
= GetExtraIconCount(tab_id
);
225 size_t tab_icon_count
= model_
->visible_icon_count() + extra
;
226 bool reorder_necessary
= false;
227 const WeakToolbarActions
& toolbar_actions
= toolbar_
->toolbar_actions();
228 if (resized_count
< tab_icon_count
) {
229 for (int i
= resized_count
; i
< extra
; ++i
) {
230 // If an extension that was popped out to act is overflowed, then it
231 // should no longer be popped out, and it also doesn't count for adjusting
232 // the visible count (since it wasn't really out to begin with).
233 if (popped_out_in_tabs_
[toolbar_actions
[i
]].count(tab_id
)) {
234 reorder_necessary
= true;
235 popped_out_in_tabs_
[toolbar_actions
[i
]].erase(tab_id
);
240 // If the user increases the toolbar size while actions that popped out are
241 // visible, we need to re-arrange the icons in other windows to be
242 // consistent with what the user sees.
243 // That is, if the normal order is A, B, [C, D] (with C and D hidden), C
244 // pops out to act, and then the user increases the size of the toolbar,
245 // the user sees uncovering D (since C is already out). This is what should
246 // happen in all windows.
247 for (size_t i
= tab_icon_count
; i
< resized_count
; ++i
) {
248 if (toolbar_actions
[i
]->GetId() !=
249 model_
->toolbar_items()[i
- extra
]->id())
250 model_
->MoveExtensionIcon(toolbar_actions
[i
]->GetId(), i
- extra
);
254 resized_count
-= extra
;
255 model_
->SetVisibleIconCount(resized_count
);
256 if (reorder_necessary
)
257 NotifyReorderActions();
260 void ToolbarActionsBar::TabOrderHelper::HandleDragDrop(
263 ToolbarActionsBar::DragType drag_type
,
265 const WeakToolbarActions
& toolbar_actions
= toolbar_
->toolbar_actions();
266 ToolbarActionViewController
* action
= toolbar_actions
[dragged_index
];
269 case ToolbarActionsBar::DRAG_TO_OVERFLOW
:
270 // If the user moves an action back into overflow, then we don't adjust
271 // the base visible count, but do stop popping that action out.
272 if (popped_out_in_tabs_
[action
].count(tab_id
))
273 popped_out_in_tabs_
[action
].erase(tab_id
);
277 case ToolbarActionsBar::DRAG_TO_MAIN
:
280 case ToolbarActionsBar::DRAG_TO_SAME
:
281 // If the user moves an action that had popped out to be on the toolbar,
282 // then we treat it as "pinning" the action, and adjust the base visible
283 // count to accommodate.
284 if (popped_out_in_tabs_
[action
].count(tab_id
)) {
286 popped_out_in_tabs_
[action
].erase(tab_id
);
291 // If there are any actions that are in front of the dropped index only
292 // because they were popped out, decrement the dropped index.
293 for (int i
= 0; i
< dropped_index
; ++i
) {
294 if (i
!= dragged_index
&&
295 model_
->GetIndexForId(toolbar_actions
[i
]->GetId()) >= dropped_index
)
299 model_
->MoveExtensionIcon(action
->GetId(), dropped_index
);
302 model_
->SetVisibleIconCount(model_
->visible_icon_count() + delta
);
305 void ToolbarActionsBar::TabOrderHelper::SetActionWantsToRun(
306 ToolbarActionViewController
* action
,
309 bool is_overflowed
= model_
->GetIndexForId(action
->GetId()) >=
310 static_cast<int>(model_
->visible_icon_count());
311 bool reorder_necessary
= false;
312 if (wants_to_run
&& is_overflowed
) {
313 popped_out_in_tabs_
[action
].insert(tab_id
);
314 reorder_necessary
= true;
315 } else if (!wants_to_run
&& popped_out_in_tabs_
[action
].count(tab_id
)) {
316 popped_out_in_tabs_
[action
].erase(tab_id
);
317 reorder_necessary
= true;
319 if (reorder_necessary
)
320 NotifyReorderActions();
323 void ToolbarActionsBar::TabOrderHelper::ActionRemoved(
324 ToolbarActionViewController
* action
) {
325 popped_out_in_tabs_
.erase(action
);
328 WeakToolbarActions
ToolbarActionsBar::TabOrderHelper::GetActionOrder(
329 content::WebContents
* web_contents
) {
330 WeakToolbarActions toolbar_actions
= toolbar_
->toolbar_actions();
331 // First, make sure that we've checked any actions that want to run.
332 int tab_id
= SessionTabHelper::IdForTab(web_contents
);
333 if (!tabs_checked_for_pop_out_
.count(tab_id
)) {
334 tabs_checked_for_pop_out_
.insert(tab_id
);
335 for (ToolbarActionViewController
* toolbar_action
: toolbar_actions
) {
336 if (toolbar_action
->WantsToRun(web_contents
))
337 popped_out_in_tabs_
[toolbar_action
].insert(tab_id
);
341 // Then, shift any actions that want to run to the front.
342 size_t insert_at
= 0;
343 // Rotate any actions that want to run to the boundary between visible and
344 // overflowed actions.
345 for (WeakToolbarActions::iterator iter
=
346 toolbar_actions
.begin() + model_
->visible_icon_count();
347 iter
!= toolbar_actions
.end(); ++iter
) {
348 if (popped_out_in_tabs_
[(*iter
)].count(tab_id
)) {
349 std::rotate(toolbar_actions
.begin() + insert_at
, iter
, iter
+ 1);
354 return toolbar_actions
;
357 void ToolbarActionsBar::TabOrderHelper::TabInsertedAt(
358 content::WebContents
* web_contents
,
362 NotifyReorderActions();
365 void ToolbarActionsBar::TabOrderHelper::TabDetachedAt(
366 content::WebContents
* web_contents
,
368 int tab_id
= SessionTabHelper::IdForTab(web_contents
);
369 for (auto& tabs
: popped_out_in_tabs_
)
370 tabs
.second
.erase(tab_id
);
371 tabs_checked_for_pop_out_
.erase(tab_id
);
374 void ToolbarActionsBar::TabOrderHelper::ActiveTabChanged(
375 content::WebContents
* old_contents
,
376 content::WebContents
* new_contents
,
379 // When we do a bulk-refresh by switching tabs, we don't animate the
380 // difference. We only animate when it's a change driven by the action or the
382 base::AutoReset
<bool> animation_reset(&toolbar_
->suppress_animation_
, true);
383 NotifyReorderActions();
386 void ToolbarActionsBar::TabOrderHelper::TabStripModelDeleted() {
387 tab_strip_observer_
.RemoveAll();
390 void ToolbarActionsBar::TabOrderHelper::NotifyReorderActions() {
391 // Reorder the reference toolbar first (since we use its actions in
392 // GetActionOrder()).
393 toolbar_
->ReorderActions();
395 overflow_bar_
->ReorderActions();
398 ToolbarActionsBar::PlatformSettings::PlatformSettings(bool in_overflow_mode
)
399 : left_padding(in_overflow_mode
? kOverflowLeftPadding
: kLeftPadding
),
400 right_padding(in_overflow_mode
? kOverflowRightPadding
: kRightPadding
),
401 item_spacing(kItemSpacing
),
402 icons_per_overflow_menu_row(1),
403 chevron_enabled(!extensions::FeatureSwitch::extension_action_redesign()->
407 ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate
* delegate
,
409 ToolbarActionsBar
* main_bar
)
410 : delegate_(delegate
),
412 model_(extensions::ExtensionToolbarModel::Get(browser_
->profile())),
414 platform_settings_(main_bar
!= nullptr),
415 model_observer_(this),
416 suppress_layout_(false),
417 suppress_animation_(true),
418 overflowed_action_wants_to_run_(false) {
419 if (model_
) // |model_| can be null in unittests.
420 model_observer_
.Add(model_
);
422 if (pop_out_actions_to_run_
) {
423 if (in_overflow_mode())
424 main_bar_
->tab_order_helper_
->notify_overflow_bar(this, true);
426 tab_order_helper_
.reset(new TabOrderHelper(this, browser_
, model_
));
430 ToolbarActionsBar::~ToolbarActionsBar() {
431 // We don't just call DeleteActions() here because it makes assumptions about
432 // the order of deletion between the views and the ToolbarActionsBar.
433 DCHECK(toolbar_actions_
.empty()) <<
434 "Must call DeleteActions() before destruction.";
435 if (in_overflow_mode() && pop_out_actions_to_run_
)
436 main_bar_
->tab_order_helper_
->notify_overflow_bar(this, false);
440 int ToolbarActionsBar::IconWidth(bool include_padding
) {
441 return GetIconDimension(WIDTH
) + (include_padding
? kItemSpacing
: 0);
445 int ToolbarActionsBar::IconHeight() {
446 return GetIconDimension(HEIGHT
);
449 gfx::Size
ToolbarActionsBar::GetPreferredSize() const {
450 int icon_count
= GetIconCount();
451 if (in_overflow_mode()) {
452 // In overflow, we always have a preferred size of a full row (even if we
453 // don't use it), and always of at least one row. The parent may decide to
454 // show us even when empty, e.g. as a drag target for dragging in icons from
455 // the main container.
456 int row_count
= ((std::max(0, icon_count
- 1)) /
457 platform_settings_
.icons_per_overflow_menu_row
) + 1;
459 IconCountToWidth(platform_settings_
.icons_per_overflow_menu_row
),
460 row_count
* IconHeight());
463 // If there are no actions to show (and this isn't an overflow container),
464 // then don't show the container at all.
465 if (toolbar_actions_
.empty())
468 return gfx::Size(IconCountToWidth(icon_count
), IconHeight());
471 int ToolbarActionsBar::GetMinimumWidth() const {
472 if (!platform_settings_
.chevron_enabled
|| toolbar_actions_
.empty())
474 return kLeftPadding
+ delegate_
->GetChevronWidth() + kRightPadding
;
477 int ToolbarActionsBar::GetMaximumWidth() const {
478 return IconCountToWidth(-1);
481 int ToolbarActionsBar::IconCountToWidth(int icons
) const {
483 icons
= toolbar_actions_
.size();
484 bool display_chevron
=
485 platform_settings_
.chevron_enabled
&&
486 static_cast<size_t>(icons
) < toolbar_actions_
.size();
487 if (icons
== 0 && !display_chevron
)
488 return platform_settings_
.left_padding
;
489 int icons_size
= (icons
== 0) ? 0 :
490 (icons
* IconWidth(true)) - platform_settings_
.item_spacing
;
491 int chevron_size
= display_chevron
? delegate_
->GetChevronWidth() : 0;
492 int padding
= platform_settings_
.left_padding
+
493 platform_settings_
.right_padding
;
494 return icons_size
+ chevron_size
+ padding
;
497 size_t ToolbarActionsBar::WidthToIconCount(int pixels
) const {
498 // Check for widths large enough to show the entire icon set.
499 if (pixels
>= IconCountToWidth(-1))
500 return toolbar_actions_
.size();
502 // We reserve space for the padding on either side of the toolbar...
503 int available_space
= pixels
-
504 (platform_settings_
.left_padding
+ platform_settings_
.right_padding
);
505 // ... and, if the chevron is enabled, the chevron.
506 if (platform_settings_
.chevron_enabled
)
507 available_space
-= delegate_
->GetChevronWidth();
509 // Now we add an extra between-item padding value so the space can be divided
510 // evenly by (size of icon with padding).
511 return static_cast<size_t>(std::max(
512 0, available_space
+ platform_settings_
.item_spacing
) / IconWidth(true));
515 size_t ToolbarActionsBar::GetIconCount() const {
519 size_t extra_icons
= 0;
520 if (tab_order_helper_
) {
521 extra_icons
= tab_order_helper_
->GetExtraIconCount(
522 SessionTabHelper::IdForTab(
523 browser_
->tab_strip_model()->GetActiveWebContents()));
526 size_t visible_icons
= in_overflow_mode() ?
527 toolbar_actions_
.size() - main_bar_
->GetIconCount() :
528 model_
->visible_icon_count() + extra_icons
;
531 // Good time for some sanity checks: We should never try to display more
532 // icons than we have, and we should always have a view per item in the model.
533 // (The only exception is if this is in initialization.)
534 if (!toolbar_actions_
.empty() && !suppress_layout_
&&
535 model_
->extensions_initialized()) {
536 size_t num_extension_actions
= 0u;
537 for (ToolbarActionViewController
* action
: toolbar_actions_
) {
538 // No component action should ever have a valid extension id, so we can
539 // use this to check the extension amount.
540 // TODO(devlin): Fix this to just check model size when the model also
541 // includes component actions.
542 if (crx_file::id_util::IdIsValid(action
->GetId()))
543 ++num_extension_actions
;
545 DCHECK_LE(visible_icons
, num_extension_actions
);
546 DCHECK_EQ(model_
->toolbar_items().size(), num_extension_actions
);
550 return visible_icons
;
553 void ToolbarActionsBar::CreateActions() {
554 DCHECK(toolbar_actions_
.empty());
555 // We wait for the extension system to be initialized before we add any
556 // actions, as they rely on the extension system to function.
557 if (!model_
|| !model_
->extensions_initialized())
561 // We don't redraw the view while creating actions.
562 base::AutoReset
<bool> layout_resetter(&suppress_layout_
, true);
564 // Extension actions come first.
565 extensions::ExtensionActionManager
* action_manager
=
566 extensions::ExtensionActionManager::Get(browser_
->profile());
567 const extensions::ExtensionList
& toolbar_items
= model_
->toolbar_items();
568 for (const scoped_refptr
<const extensions::Extension
>& extension
:
570 toolbar_actions_
.push_back(new ExtensionActionViewController(
573 action_manager
->GetExtensionAction(*extension
)));
576 // Component actions come second, and are suppressed if the extension
577 // actions are being highlighted.
578 if (!model_
->is_highlighting()) {
579 ScopedVector
<ToolbarActionViewController
> component_actions
=
580 ComponentToolbarActionsFactory::GetInstance()->
581 GetComponentToolbarActions();
582 DCHECK(component_actions
.empty() ||
583 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled());
584 toolbar_actions_
.insert(toolbar_actions_
.end(),
585 component_actions
.begin(),
586 component_actions
.end());
587 component_actions
.weak_clear();
590 if (!toolbar_actions_
.empty())
593 for (size_t i
= 0; i
< toolbar_actions_
.size(); ++i
)
594 delegate_
->AddViewForAction(toolbar_actions_
[i
], i
);
597 // Once the actions are created, we should animate the changes.
598 suppress_animation_
= false;
601 void ToolbarActionsBar::DeleteActions() {
602 delegate_
->RemoveAllViews();
603 toolbar_actions_
.clear();
606 void ToolbarActionsBar::Update() {
607 if (toolbar_actions_
.empty())
608 return; // Nothing to do.
611 // Don't layout until the end.
612 base::AutoReset
<bool> layout_resetter(&suppress_layout_
, true);
613 for (ToolbarActionViewController
* action
: toolbar_actions_
)
614 action
->UpdateState();
617 ReorderActions(); // Also triggers a draw.
620 void ToolbarActionsBar::SetOverflowRowWidth(int width
) {
621 DCHECK(in_overflow_mode());
622 platform_settings_
.icons_per_overflow_menu_row
=
623 std::max((width
- kItemSpacing
) / IconWidth(true), 1);
626 void ToolbarActionsBar::OnResizeComplete(int width
) {
627 DCHECK(!in_overflow_mode()); // The user can't resize the overflow container.
628 size_t resized_count
= WidthToIconCount(width
);
629 // Save off the desired number of visible icons. We do this now instead of
630 // at the end of the animation so that even if the browser is shut down
631 // while animating, the right value will be restored on next run.
632 if (tab_order_helper_
) {
633 tab_order_helper_
->HandleResize(
635 SessionTabHelper::IdForTab(GetCurrentWebContents()));
637 model_
->SetVisibleIconCount(resized_count
);
641 void ToolbarActionsBar::OnDragDrop(int dragged_index
,
643 DragType drag_type
) {
644 // All drag-and-drop commands should go to the main bar.
645 if (in_overflow_mode()) {
646 main_bar_
->OnDragDrop(dragged_index
, dropped_index
, drag_type
);
650 if (tab_order_helper_
) {
651 tab_order_helper_
->HandleDragDrop(
655 SessionTabHelper::IdForTab(GetCurrentWebContents()));
658 if (drag_type
== DRAG_TO_OVERFLOW
)
660 else if (drag_type
== DRAG_TO_MAIN
)
662 model_
->MoveExtensionIcon(toolbar_actions_
[dragged_index
]->GetId(),
665 model_
->SetVisibleIconCount(model_
->visible_icon_count() + delta
);
669 void ToolbarActionsBar::ToolbarExtensionAdded(
670 const extensions::Extension
* extension
,
672 DCHECK(GetActionForId(extension
->id()) == nullptr) <<
673 "Asked to add a toolbar action view for an extension that already exists";
675 toolbar_actions_
.insert(
676 toolbar_actions_
.begin() + index
,
677 new ExtensionActionViewController(
680 extensions::ExtensionActionManager::Get(browser_
->profile())->
681 GetExtensionAction(*extension
)));
683 delegate_
->AddViewForAction(toolbar_actions_
[index
], index
);
685 // If we are still initializing the container, don't bother animating.
686 if (!model_
->extensions_initialized())
689 // We may need to resize (e.g. to show the new icon, or the chevron). We don't
690 // need to check if the extension is upgrading here, because ResizeDelegate()
691 // checks to see if the container is already the proper size, and because
692 // if the action is newly incognito enabled, even though it's a reload, it's
693 // a new extension to this toolbar.
694 // We suppress the chevron during animation because, if we're expanding to
695 // show a new icon, we don't want to have the chevron visible only for the
696 // duration of the animation.
697 ResizeDelegate(gfx::Tween::LINEAR
, true);
700 void ToolbarActionsBar::ToolbarExtensionRemoved(
701 const extensions::Extension
* extension
) {
702 ToolbarActions::iterator iter
= toolbar_actions_
.begin();
703 while (iter
!= toolbar_actions_
.end() && (*iter
)->GetId() != extension
->id())
706 if (iter
== toolbar_actions_
.end())
709 delegate_
->RemoveViewForAction(*iter
);
710 if (tab_order_helper_
)
711 tab_order_helper_
->ActionRemoved(*iter
);
712 toolbar_actions_
.erase(iter
);
714 // If the extension is being upgraded we don't want the bar to shrink
715 // because the icon is just going to get re-added to the same location.
716 // There is an exception if this is an off-the-record profile, and the
717 // extension is no longer incognito-enabled.
718 if (!extensions::ExtensionSystem::Get(browser_
->profile())->runtime_data()->
719 IsBeingUpgraded(extension
->id()) ||
720 (browser_
->profile()->IsOffTheRecord() &&
721 !extensions::util::IsIncognitoEnabled(extension
->id(),
722 browser_
->profile()))) {
723 if (toolbar_actions_
.size() > model_
->visible_icon_count()) {
724 // If we have more icons than we can show, then we must not be changing
725 // the container size (since we either removed an icon from the main
726 // area and one from the overflow list will have shifted in, or we
727 // removed an entry directly from the overflow list).
728 delegate_
->Redraw(false);
730 delegate_
->SetChevronVisibility(false);
731 // Either we went from overflow to no-overflow, or we shrunk the no-
732 // overflow container by 1. Either way the size changed, so animate.
733 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
738 void ToolbarActionsBar::ToolbarExtensionMoved(
739 const extensions::Extension
* extension
,
741 DCHECK(index
>= 0 && index
< static_cast<int>(toolbar_actions_
.size()));
742 // Unfortunately, |index| doesn't really mean a lot to us, because this
743 // window's toolbar could be different (if actions are popped out). Just
744 // do a full reorder.
748 void ToolbarActionsBar::ToolbarExtensionUpdated(
749 const extensions::Extension
* extension
) {
750 ToolbarActionViewController
* action
= GetActionForId(extension
->id());
751 // There might not be a view in cases where we are highlighting or if we
752 // haven't fully initialized the actions.
754 content::WebContents
* web_contents
= GetCurrentWebContents();
755 action
->UpdateState();
757 if (tab_order_helper_
) {
758 tab_order_helper_
->SetActionWantsToRun(
760 SessionTabHelper::IdForTab(web_contents
),
761 action
->WantsToRun(web_contents
));
765 SetOverflowedActionWantsToRun();
768 bool ToolbarActionsBar::ShowExtensionActionPopup(
769 const extensions::Extension
* extension
,
770 bool grant_active_tab
) {
771 // Don't override another popup, and only show in the active window.
772 if (delegate_
->IsPopupRunning() || !browser_
->window()->IsActive())
775 ToolbarActionViewController
* action
= GetActionForId(extension
->id());
776 return action
&& action
->ExecuteAction(grant_active_tab
);
779 void ToolbarActionsBar::ToolbarVisibleCountChanged() {
780 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
781 SetOverflowedActionWantsToRun();
784 void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type
,
785 bool suppress_chevron
) {
786 int desired_width
= GetPreferredSize().width();
787 if (desired_width
!= delegate_
->GetWidth()) {
788 delegate_
->ResizeAndAnimate(tween_type
, desired_width
, suppress_chevron
);
789 } else if (delegate_
->IsAnimating()) {
790 // It's possible that we're right where we're supposed to be in terms of
791 // width, but that we're also currently resizing. If this is the case, end
792 // the current animation with the current width.
793 delegate_
->StopAnimating();
795 // We may already be at the right size (this can happen frequently with
796 // overflow, where we have a fixed width, and in tests, where we skip
797 // animations). If this is the case, we still need to Redraw(), because the
798 // icons within the toolbar may have changed (e.g. if we removed one
799 // action and added a different one in quick succession).
800 delegate_
->Redraw(false);
804 void ToolbarActionsBar::ToolbarHighlightModeChanged(bool is_highlighting
) {
805 // It's a bit of a pain that we delete and recreate everything here, but given
806 // everything else going on (the lack of highlight, [n] more extensions
807 // appearing, etc), it's not worth the extra complexity to create and insert
808 // only the new actions.
811 // Resize the delegate. We suppress the chevron so that we don't risk showing
812 // it only for the duration of the animation.
813 ResizeDelegate(gfx::Tween::LINEAR
, true);
816 void ToolbarActionsBar::OnToolbarModelInitialized() {
817 // We shouldn't have any actions before the model is initialized.
818 DCHECK(toolbar_actions_
.empty());
820 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
823 Browser
* ToolbarActionsBar::GetBrowser() {
827 void ToolbarActionsBar::ReorderActions() {
828 if (toolbar_actions_
.empty())
831 // First, reset the order to that of the model.
832 auto compare
= [](ToolbarActionViewController
* const& action
,
833 const scoped_refptr
<const extensions::Extension
>& ext
) {
834 return action
->GetId() == ext
->id();
836 SortContainer(&toolbar_actions_
.get(), model_
->toolbar_items(), compare
);
838 // Only adjust the order if the model isn't highlighting a particular
839 // subset (and the specialized tab order is enabled).
840 TabOrderHelper
* tab_order_helper
= in_overflow_mode() ?
841 main_bar_
->tab_order_helper_
.get() : tab_order_helper_
.get();
842 if (!model_
->is_highlighting() && tab_order_helper
) {
843 WeakToolbarActions new_order
=
844 tab_order_helper
->GetActionOrder(GetCurrentWebContents());
845 auto compare
= [](ToolbarActionViewController
* const& first
,
846 ToolbarActionViewController
* const& second
) {
847 return first
->GetId() == second
->GetId();
850 &toolbar_actions_
.get(), new_order
, compare
);
853 // Our visible browser actions may have changed - re-Layout() and check the
854 // size (if we aren't suppressing the layout).
855 if (!suppress_layout_
) {
856 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
857 delegate_
->Redraw(true);
860 SetOverflowedActionWantsToRun();
863 void ToolbarActionsBar::SetOverflowedActionWantsToRun() {
864 if (in_overflow_mode())
866 bool overflowed_action_wants_to_run
= false;
867 content::WebContents
* web_contents
= GetCurrentWebContents();
868 for (size_t i
= GetIconCount(); i
< toolbar_actions_
.size(); ++i
) {
869 if (toolbar_actions_
[i
]->WantsToRun(web_contents
)) {
870 overflowed_action_wants_to_run
= true;
875 if (overflowed_action_wants_to_run_
!= overflowed_action_wants_to_run
) {
876 overflowed_action_wants_to_run_
= overflowed_action_wants_to_run
;
877 if (send_overflowed_action_changes_
)
878 delegate_
->OnOverflowedActionWantsToRunChanged(
879 overflowed_action_wants_to_run_
);
883 ToolbarActionViewController
* ToolbarActionsBar::GetActionForId(
884 const std::string
& id
) {
885 for (ToolbarActionViewController
* action
: toolbar_actions_
) {
886 if (action
->GetId() == id
)
892 content::WebContents
* ToolbarActionsBar::GetCurrentWebContents() {
893 return browser_
->tab_strip_model()->GetActiveWebContents();