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 "base/location.h"
9 #include "base/profiler/scoped_tracker.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/extensions/extension_action_manager.h"
13 #include "chrome/browser/extensions/extension_message_bubble_controller.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_tab_helper.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_window.h"
19 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
20 #include "chrome/browser/ui/extensions/extension_message_bubble_factory.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
23 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
24 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h"
25 #include "chrome/common/pref_names.h"
26 #include "components/crx_file/id_util.h"
27 #include "components/pref_registry/pref_registry_syncable.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/browser/runtime_data.h"
31 #include "extensions/common/extension.h"
32 #include "extensions/common/feature_switch.h"
33 #include "grit/theme_resources.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/image/image_skia.h"
39 using WeakToolbarActions
= std::vector
<ToolbarActionViewController
*>;
41 // Matches ToolbarView::kStandardSpacing;
42 const int kLeftPadding
= 3;
43 const int kRightPadding
= kLeftPadding
;
44 const int kItemSpacing
= kLeftPadding
;
45 const int kOverflowLeftPadding
= kItemSpacing
;
46 const int kOverflowRightPadding
= kItemSpacing
;
48 enum DimensionType
{ WIDTH
, HEIGHT
};
50 // Returns the width or height of the toolbar action icon size.
51 int GetIconDimension(DimensionType type
) {
52 static bool initialized
= false;
53 static int icon_height
= 0;
54 static int icon_width
= 0;
57 gfx::ImageSkia
* skia
=
58 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
60 icon_height
= skia
->height();
61 icon_width
= skia
->width();
63 return type
== WIDTH
? icon_width
: icon_height
;
66 // Takes a reference vector |reference| of length n, where n is less than or
67 // equal to the length of |to_sort|, and rearranges |to_sort| so that
68 // |to_sort|'s first n elements match the n elements of |reference| (the order
69 // of any remaining elements in |to_sort| is unspecified).
70 // |equal| is used to compare the elements of |to_sort| and |reference|.
71 // This allows us to sort a vector to match another vector of two different
72 // types without needing to construct a more cumbersome comparator class.
73 // |FunctionType| should equate to (something similar to)
74 // bool Equal(const Type1&, const Type2&), but we can't enforce this
75 // because of MSVC compilation limitations.
76 template<typename Type1
, typename Type2
, typename FunctionType
>
77 void SortContainer(std::vector
<Type1
>* to_sort
,
78 const std::vector
<Type2
>& reference
,
80 DCHECK_GE(to_sort
->size(), reference
.size()) <<
81 "|to_sort| must contain all elements in |reference|.";
82 if (reference
.empty())
84 // Run through the each element and compare it to the reference. If something
85 // is out of place, find the correct spot for it.
86 for (size_t i
= 0; i
< reference
.size() - 1; ++i
) {
87 if (!equal(to_sort
->at(i
), reference
[i
])) {
88 // Find the correct index (it's guaranteed to be after our current
89 // index, since everything up to this point is correct), and swap.
91 while (!equal(to_sort
->at(j
), reference
[i
])) {
93 DCHECK_LT(j
, to_sort
->size()) <<
94 "Item in |reference| not found in |to_sort|.";
96 std::swap(to_sort
->at(i
), to_sort
->at(j
));
104 bool ToolbarActionsBar::disable_animations_for_testing_
= false;
107 bool ToolbarActionsBar::send_overflowed_action_changes_
= true;
109 ToolbarActionsBar::PlatformSettings::PlatformSettings(bool in_overflow_mode
)
110 : left_padding(in_overflow_mode
? kOverflowLeftPadding
: kLeftPadding
),
111 right_padding(in_overflow_mode
? kOverflowRightPadding
: kRightPadding
),
112 item_spacing(kItemSpacing
),
113 icons_per_overflow_menu_row(1),
114 chevron_enabled(!extensions::FeatureSwitch::extension_action_redesign()->
118 ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate
* delegate
,
120 ToolbarActionsBar
* main_bar
)
121 : delegate_(delegate
),
123 model_(ToolbarActionsModel::Get(browser_
->profile())),
125 platform_settings_(main_bar
!= nullptr),
126 popup_owner_(nullptr),
127 model_observer_(this),
128 suppress_layout_(false),
129 suppress_animation_(true),
130 overflowed_action_wants_to_run_(false),
131 checked_extension_bubble_(false),
132 popped_out_action_(nullptr),
133 weak_ptr_factory_(this) {
134 if (model_
) // |model_| can be null in unittests.
135 model_observer_
.Add(model_
);
138 ToolbarActionsBar::~ToolbarActionsBar() {
139 // We don't just call DeleteActions() here because it makes assumptions about
140 // the order of deletion between the views and the ToolbarActionsBar.
141 DCHECK(toolbar_actions_
.empty()) <<
142 "Must call DeleteActions() before destruction.";
146 int ToolbarActionsBar::IconWidth(bool include_padding
) {
147 return GetIconDimension(WIDTH
) + (include_padding
? kItemSpacing
: 0);
151 int ToolbarActionsBar::IconHeight() {
152 return GetIconDimension(HEIGHT
);
156 void ToolbarActionsBar::RegisterProfilePrefs(
157 user_prefs::PrefRegistrySyncable
* registry
) {
158 registry
->RegisterBooleanPref(
159 prefs::kToolbarIconSurfacingBubbleAcknowledged
,
161 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
162 registry
->RegisterInt64Pref(prefs::kToolbarIconSurfacingBubbleLastShowTime
,
166 gfx::Size
ToolbarActionsBar::GetPreferredSize() const {
167 int icon_count
= GetIconCount();
168 if (in_overflow_mode()) {
169 // In overflow, we always have a preferred size of a full row (even if we
170 // don't use it), and always of at least one row. The parent may decide to
171 // show us even when empty, e.g. as a drag target for dragging in icons from
172 // the main container.
173 int row_count
= ((std::max(0, icon_count
- 1)) /
174 platform_settings_
.icons_per_overflow_menu_row
) + 1;
176 IconCountToWidth(platform_settings_
.icons_per_overflow_menu_row
),
177 row_count
* IconHeight());
180 // If there are no actions to show (and this isn't an overflow container),
181 // then don't show the container at all.
182 if (toolbar_actions_
.empty())
185 return gfx::Size(IconCountToWidth(icon_count
), IconHeight());
188 int ToolbarActionsBar::GetMinimumWidth() const {
189 if (!platform_settings_
.chevron_enabled
|| toolbar_actions_
.empty())
191 return kLeftPadding
+ delegate_
->GetChevronWidth() + kRightPadding
;
194 int ToolbarActionsBar::GetMaximumWidth() const {
195 return IconCountToWidth(-1);
198 int ToolbarActionsBar::IconCountToWidth(int icons
) const {
200 icons
= toolbar_actions_
.size();
201 bool display_chevron
=
202 platform_settings_
.chevron_enabled
&&
203 static_cast<size_t>(icons
) < toolbar_actions_
.size();
204 if (icons
== 0 && !display_chevron
)
205 return platform_settings_
.left_padding
;
206 int icons_size
= (icons
== 0) ? 0 :
207 (icons
* IconWidth(true)) - platform_settings_
.item_spacing
;
208 int chevron_size
= display_chevron
? delegate_
->GetChevronWidth() : 0;
209 int padding
= platform_settings_
.left_padding
+
210 platform_settings_
.right_padding
;
211 return icons_size
+ chevron_size
+ padding
;
214 size_t ToolbarActionsBar::WidthToIconCount(int pixels
) const {
215 // Check for widths large enough to show the entire icon set.
216 if (pixels
>= IconCountToWidth(-1))
217 return toolbar_actions_
.size();
219 // We reserve space for the padding on either side of the toolbar...
220 int available_space
= pixels
-
221 (platform_settings_
.left_padding
+ platform_settings_
.right_padding
);
222 // ... and, if the chevron is enabled, the chevron.
223 if (platform_settings_
.chevron_enabled
)
224 available_space
-= delegate_
->GetChevronWidth();
226 // Now we add an extra between-item padding value so the space can be divided
227 // evenly by (size of icon with padding).
228 return static_cast<size_t>(std::max(
229 0, available_space
+ platform_settings_
.item_spacing
) / IconWidth(true));
232 size_t ToolbarActionsBar::GetIconCount() const {
236 int pop_out_modifier
= 0;
237 // If there is a popped out action, it could affect the number of visible
238 // icons - but only if it wouldn't otherwise be visible.
239 if (popped_out_action_
) {
240 size_t popped_out_index
=
241 std::find(toolbar_actions_
.begin(),
242 toolbar_actions_
.end(),
243 popped_out_action_
) - toolbar_actions_
.begin();
244 pop_out_modifier
= popped_out_index
>= model_
->visible_icon_count() ? 1 : 0;
247 // We purposefully do not account for any "popped out" actions in overflow
248 // mode. This is because the popup cannot be showing while the overflow menu
249 // is open, so there's no concern there. Also, if the user has a popped out
250 // action, and immediately opens the overflow menu, we *want* the action there
251 // (since it will close the popup, but do so asynchronously, and we don't
252 // want to "slide" the action back in.
253 size_t visible_icons
= in_overflow_mode() ?
254 toolbar_actions_
.size() - model_
->visible_icon_count() :
255 model_
->visible_icon_count() + pop_out_modifier
;
258 // Good time for some sanity checks: We should never try to display more
259 // icons than we have, and we should always have a view per item in the model.
260 // (The only exception is if this is in initialization.)
261 if (!toolbar_actions_
.empty() && !suppress_layout_
&&
262 model_
->actions_initialized()) {
263 DCHECK_LE(visible_icons
, toolbar_actions_
.size());
264 DCHECK_EQ(model_
->toolbar_items().size(), toolbar_actions_
.size());
268 return visible_icons
;
271 gfx::Rect
ToolbarActionsBar::GetFrameForIndex(size_t index
) const {
272 size_t start_index
= in_overflow_mode() ?
273 toolbar_actions_
.size() - GetIconCount() : 0u;
275 // If the index is for an action that is before range we show (i.e., is for
276 // a button that's on the main bar, and this is the overflow), send back an
278 if (index
< start_index
)
281 size_t relative_index
= index
- start_index
;
282 int icons_per_overflow_row
= platform_settings().icons_per_overflow_menu_row
;
283 size_t row_index
= in_overflow_mode() ?
284 relative_index
/ icons_per_overflow_row
: 0;
285 size_t index_in_row
= in_overflow_mode() ?
286 relative_index
% icons_per_overflow_row
: relative_index
;
288 return gfx::Rect(platform_settings().left_padding
+
289 index_in_row
* IconWidth(true),
290 row_index
* IconHeight(),
295 std::vector
<ToolbarActionViewController
*>
296 ToolbarActionsBar::GetActions() const {
297 std::vector
<ToolbarActionViewController
*> actions
= toolbar_actions_
.get();
299 // If there is an action that should be popped out, and it's not visible by
300 // default, make it the final action in the list.
301 if (popped_out_action_
) {
303 std::find(actions
.begin(), actions
.end(), popped_out_action_
) -
305 DCHECK_NE(actions
.size(), index
);
306 size_t visible
= GetIconCount();
307 if (index
>= visible
) {
308 size_t rindex
= actions
.size() - index
- 1;
309 std::rotate(actions
.rbegin() + rindex
,
310 actions
.rbegin() + rindex
+ 1,
311 actions
.rend() - visible
+ 1);
318 void ToolbarActionsBar::CreateActions() {
319 DCHECK(toolbar_actions_
.empty());
320 // If the model isn't initialized, wait for it.
321 if (!model_
|| !model_
->actions_initialized())
325 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
327 tracked_objects::ScopedTracker
tracking_profile1(
328 FROM_HERE_WITH_EXPLICIT_FUNCTION("ToolbarActionsBar::CreateActions1"));
329 // We don't redraw the view while creating actions.
330 base::AutoReset
<bool> layout_resetter(&suppress_layout_
, true);
332 // Get the toolbar actions.
333 toolbar_actions_
= model_
->CreateActions(browser_
, this);
334 if (!model_
->is_highlighting()) {
335 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
337 tracked_objects::ScopedTracker
tracking_profile2(
338 FROM_HERE_WITH_EXPLICIT_FUNCTION(
339 "ToolbarActionsBar::CreateActions2"));
342 if (!toolbar_actions_
.empty()) {
343 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
345 tracked_objects::ScopedTracker
tracking_profile3(
346 FROM_HERE_WITH_EXPLICIT_FUNCTION(
347 "ToolbarActionsBar::CreateActions3"));
351 tracked_objects::ScopedTracker
tracking_profile4(
352 FROM_HERE_WITH_EXPLICIT_FUNCTION("ToolbarActionsBar::CreateActions4"));
354 for (size_t i
= 0; i
< toolbar_actions_
.size(); ++i
)
355 delegate_
->AddViewForAction(toolbar_actions_
[i
], i
);
358 // Once the actions are created, we should animate the changes.
359 suppress_animation_
= false;
361 // CreateActions() can be called multiple times, so we need to make sure we
362 // haven't already shown the bubble.
363 // Extension bubbles can also highlight a subset of actions, so don't show the
364 // bubble if the toolbar is already highlighting a different set.
365 if (!checked_extension_bubble_
&& !is_highlighting()) {
366 checked_extension_bubble_
= true;
367 // CreateActions() can be called as part of the browser window set up, which
368 // we need to let finish before showing the actions.
369 scoped_ptr
<extensions::ExtensionMessageBubbleController
> controller
=
370 ExtensionMessageBubbleFactory(browser_
).GetController();
372 base::ThreadTaskRunnerHandle::Get()->PostTask(
373 FROM_HERE
, base::Bind(&ToolbarActionsBar::MaybeShowExtensionBubble
,
374 weak_ptr_factory_
.GetWeakPtr(),
375 base::Passed(controller
.Pass())));
380 void ToolbarActionsBar::DeleteActions() {
382 delegate_
->RemoveAllViews();
383 toolbar_actions_
.clear();
386 void ToolbarActionsBar::Update() {
387 if (toolbar_actions_
.empty())
388 return; // Nothing to do.
391 // Don't layout until the end.
392 base::AutoReset
<bool> layout_resetter(&suppress_layout_
, true);
393 for (ToolbarActionViewController
* action
: toolbar_actions_
)
394 action
->UpdateState();
397 ReorderActions(); // Also triggers a draw.
400 bool ToolbarActionsBar::ShowToolbarActionPopup(const std::string
& action_id
,
401 bool grant_active_tab
) {
402 // Don't override another popup, and only show in the active window.
403 if (popup_owner() || !browser_
->window()->IsActive())
406 ToolbarActionViewController
* action
= GetActionForId(action_id
);
407 return action
&& action
->ExecuteAction(grant_active_tab
);
410 void ToolbarActionsBar::SetOverflowRowWidth(int width
) {
411 DCHECK(in_overflow_mode());
412 platform_settings_
.icons_per_overflow_menu_row
=
413 std::max((width
- kItemSpacing
) / IconWidth(true), 1);
416 void ToolbarActionsBar::OnResizeComplete(int width
) {
417 DCHECK(!in_overflow_mode()); // The user can't resize the overflow container.
418 size_t resized_count
= WidthToIconCount(width
);
419 // Save off the desired number of visible icons. We do this now instead of
420 // at the end of the animation so that even if the browser is shut down
421 // while animating, the right value will be restored on next run.
422 model_
->SetVisibleIconCount(resized_count
);
425 void ToolbarActionsBar::OnDragDrop(int dragged_index
,
427 DragType drag_type
) {
428 // All drag-and-drop commands should go to the main bar.
429 if (in_overflow_mode()) {
430 main_bar_
->OnDragDrop(dragged_index
, dropped_index
, drag_type
);
435 if (drag_type
== DRAG_TO_OVERFLOW
)
437 else if (drag_type
== DRAG_TO_MAIN
)
439 model_
->MoveActionIcon(toolbar_actions_
[dragged_index
]->GetId(),
442 model_
->SetVisibleIconCount(model_
->visible_icon_count() + delta
);
445 void ToolbarActionsBar::OnAnimationEnded() {
446 // Check if we were waiting for animation to complete to either show a
447 // message bubble, or to show a popup.
448 if (pending_extension_bubble_controller_
) {
449 MaybeShowExtensionBubble(pending_extension_bubble_controller_
.Pass());
450 } else if (!popped_out_closure_
.is_null()) {
451 popped_out_closure_
.Run();
452 popped_out_closure_
.Reset();
456 bool ToolbarActionsBar::IsActionVisibleOnMainBar(
457 const ToolbarActionViewController
* action
) const {
458 if (in_overflow_mode())
459 return main_bar_
->IsActionVisibleOnMainBar(action
);
461 size_t index
= std::find(toolbar_actions_
.begin(),
462 toolbar_actions_
.end(),
463 action
) - toolbar_actions_
.begin();
464 return index
< GetIconCount() || action
== popped_out_action_
;
467 void ToolbarActionsBar::PopOutAction(ToolbarActionViewController
* controller
,
468 const base::Closure
& closure
) {
469 DCHECK(!in_overflow_mode()) << "Only the main bar can pop out actions.";
470 DCHECK(!popped_out_action_
) << "Only one action can be popped out at a time!";
471 bool needs_redraw
= !IsActionVisibleOnMainBar(controller
);
472 popped_out_action_
= controller
;
474 // We suppress animation for this draw, because we need the action to get
475 // into position immediately, since it's about to show its popup.
476 base::AutoReset
<bool> layout_resetter(&suppress_animation_
, false);
477 delegate_
->Redraw(true);
480 ResizeDelegate(gfx::Tween::LINEAR
, false);
481 if (!delegate_
->IsAnimating()) {
482 // Don't call the closure re-entrantly.
483 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
, closure
);
485 popped_out_closure_
= closure
;
489 void ToolbarActionsBar::UndoPopOut() {
490 DCHECK(!in_overflow_mode()) << "Only the main bar can pop out actions.";
491 DCHECK(popped_out_action_
);
492 ToolbarActionViewController
* controller
= popped_out_action_
;
493 popped_out_action_
= nullptr;
494 popped_out_closure_
.Reset();
495 if (!IsActionVisibleOnMainBar(controller
))
496 delegate_
->Redraw(true);
497 ResizeDelegate(gfx::Tween::LINEAR
, false);
500 void ToolbarActionsBar::SetPopupOwner(
501 ToolbarActionViewController
* popup_owner
) {
502 // We should never be setting a popup owner when one already exists, and
503 // never unsetting one when one wasn't set.
504 DCHECK((!popup_owner_
&& popup_owner
) ||
505 (popup_owner_
&& !popup_owner
));
506 popup_owner_
= popup_owner
;
509 void ToolbarActionsBar::HideActivePopup() {
511 popup_owner_
->HidePopup();
512 DCHECK(!popup_owner_
);
515 ToolbarActionViewController
* ToolbarActionsBar::GetMainControllerForAction(
516 ToolbarActionViewController
* action
) {
517 return in_overflow_mode() ?
518 main_bar_
->GetActionForId(action
->GetId()) : action
;
521 void ToolbarActionsBar::MaybeShowExtensionBubble(
522 scoped_ptr
<extensions::ExtensionMessageBubbleController
> controller
) {
523 controller
->HighlightExtensionsIfNecessary(); // Safe to call multiple times.
524 if (delegate_
->IsAnimating()) {
525 // If the toolbar is animating, we can't effectively anchor the bubble,
526 // so wait until animation stops.
527 pending_extension_bubble_controller_
= controller
.Pass();
529 const std::vector
<std::string
>& affected_extensions
=
530 controller
->GetExtensionIdList();
531 ToolbarActionViewController
* anchor_action
= nullptr;
532 for (const std::string
& id
: affected_extensions
) {
533 anchor_action
= GetActionForId(id
);
537 delegate_
->ShowExtensionMessageBubble(controller
.Pass(), anchor_action
);
541 void ToolbarActionsBar::OnToolbarActionAdded(const std::string
& action_id
,
543 DCHECK(GetActionForId(action_id
) == nullptr)
544 << "Asked to add a toolbar action view for an extension that already "
547 // TODO(devlin): This is a minor layering violation and the model should pass
548 // in an action directly.
549 const extensions::Extension
* extension
=
550 extensions::ExtensionRegistry::Get(browser_
->profile())
551 ->enabled_extensions()
553 // Only extensions should be added after initialization.
556 toolbar_actions_
.insert(
557 toolbar_actions_
.begin() + index
,
558 new ExtensionActionViewController(
561 extensions::ExtensionActionManager::Get(browser_
->profile())->
562 GetExtensionAction(*extension
),
565 delegate_
->AddViewForAction(toolbar_actions_
[index
], index
);
567 // If we are still initializing the container, don't bother animating.
568 if (!model_
->actions_initialized())
571 // We may need to resize (e.g. to show the new icon, or the chevron). We don't
572 // need to check if the extension is upgrading here, because ResizeDelegate()
573 // checks to see if the container is already the proper size, and because
574 // if the action is newly incognito enabled, even though it's a reload, it's
575 // a new extension to this toolbar.
576 // We suppress the chevron during animation because, if we're expanding to
577 // show a new icon, we don't want to have the chevron visible only for the
578 // duration of the animation.
579 ResizeDelegate(gfx::Tween::LINEAR
, true);
582 void ToolbarActionsBar::OnToolbarActionRemoved(const std::string
& action_id
) {
583 ToolbarActions::iterator iter
= toolbar_actions_
.begin();
584 while (iter
!= toolbar_actions_
.end() && (*iter
)->GetId() != action_id
)
587 if (iter
== toolbar_actions_
.end())
590 // The action should outlive the UI element (which is owned by the delegate),
591 // so we can't delete it just yet. But we should remove it from the list of
592 // actions so that any width calculations are correct.
593 scoped_ptr
<ToolbarActionViewController
> removed_action(*iter
);
594 toolbar_actions_
.weak_erase(iter
);
595 delegate_
->RemoveViewForAction(removed_action
.get());
596 removed_action
.reset();
598 // If the extension is being upgraded we don't want the bar to shrink
599 // because the icon is just going to get re-added to the same location.
600 // There is an exception if this is an off-the-record profile, and the
601 // extension is no longer incognito-enabled.
602 if (!extensions::ExtensionSystem::Get(browser_
->profile())
604 ->IsBeingUpgraded(action_id
) ||
605 (browser_
->profile()->IsOffTheRecord() &&
606 !extensions::util::IsIncognitoEnabled(action_id
, browser_
->profile()))) {
607 if (toolbar_actions_
.size() > model_
->visible_icon_count()) {
608 // If we have more icons than we can show, then we must not be changing
609 // the container size (since we either removed an icon from the main
610 // area and one from the overflow list will have shifted in, or we
611 // removed an entry directly from the overflow list).
612 delegate_
->Redraw(false);
614 delegate_
->SetChevronVisibility(false);
615 // Either we went from overflow to no-overflow, or we shrunk the no-
616 // overflow container by 1. Either way the size changed, so animate.
617 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
621 SetOverflowedActionWantsToRun();
624 void ToolbarActionsBar::OnToolbarActionMoved(const std::string
& action_id
,
626 DCHECK(index
>= 0 && index
< static_cast<int>(toolbar_actions_
.size()));
627 // Unfortunately, |index| doesn't really mean a lot to us, because this
628 // window's toolbar could be different (if actions are popped out). Just
629 // do a full reorder.
633 void ToolbarActionsBar::OnToolbarActionUpdated(const std::string
& action_id
) {
634 ToolbarActionViewController
* action
= GetActionForId(action_id
);
635 // There might not be a view in cases where we are highlighting or if we
636 // haven't fully initialized the actions.
638 action
->UpdateState();
639 SetOverflowedActionWantsToRun();
643 void ToolbarActionsBar::OnToolbarVisibleCountChanged() {
644 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
645 SetOverflowedActionWantsToRun();
648 void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type
,
649 bool suppress_chevron
) {
650 int desired_width
= GetPreferredSize().width();
651 if (desired_width
!= delegate_
->GetWidth()) {
652 delegate_
->ResizeAndAnimate(tween_type
, desired_width
, suppress_chevron
);
653 } else if (delegate_
->IsAnimating()) {
654 // It's possible that we're right where we're supposed to be in terms of
655 // width, but that we're also currently resizing. If this is the case, end
656 // the current animation with the current width.
657 delegate_
->StopAnimating();
659 // We may already be at the right size (this can happen frequently with
660 // overflow, where we have a fixed width, and in tests, where we skip
661 // animations). If this is the case, we still need to Redraw(), because the
662 // icons within the toolbar may have changed (e.g. if we removed one
663 // action and added a different one in quick succession).
664 delegate_
->Redraw(false);
668 void ToolbarActionsBar::OnToolbarHighlightModeChanged(bool is_highlighting
) {
669 if (!model_
->actions_initialized())
671 // It's a bit of a pain that we delete and recreate everything here, but given
672 // everything else going on (the lack of highlight, [n] more extensions
673 // appearing, etc), it's not worth the extra complexity to create and insert
674 // only the new actions.
677 // Resize the delegate. We suppress the chevron so that we don't risk showing
678 // it only for the duration of the animation.
679 ResizeDelegate(gfx::Tween::LINEAR
, true);
682 void ToolbarActionsBar::OnToolbarModelInitialized() {
683 // We shouldn't have any actions before the model is initialized.
684 DCHECK(toolbar_actions_
.empty());
687 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337 is
689 tracked_objects::ScopedTracker
tracking_profile(
690 FROM_HERE_WITH_EXPLICIT_FUNCTION(
691 "ToolbarActionsBar::OnToolbarModelInitialized"));
692 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
695 void ToolbarActionsBar::ReorderActions() {
696 if (toolbar_actions_
.empty())
699 // First, reset the order to that of the model.
700 auto compare
= [](ToolbarActionViewController
* const& action
,
701 const ToolbarActionsModel::ToolbarItem
& item
) {
702 return action
->GetId() == item
.id
;
704 SortContainer(&toolbar_actions_
.get(), model_
->toolbar_items(), compare
);
706 // Our visible browser actions may have changed - re-Layout() and check the
707 // size (if we aren't suppressing the layout).
708 if (!suppress_layout_
) {
709 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
710 delegate_
->Redraw(true);
713 SetOverflowedActionWantsToRun();
716 void ToolbarActionsBar::SetOverflowedActionWantsToRun() {
717 if (in_overflow_mode())
719 bool overflowed_action_wants_to_run
= false;
720 content::WebContents
* web_contents
= GetCurrentWebContents();
721 for (size_t i
= GetIconCount(); i
< toolbar_actions_
.size(); ++i
) {
722 if (toolbar_actions_
[i
]->WantsToRun(web_contents
)) {
723 overflowed_action_wants_to_run
= true;
728 if (overflowed_action_wants_to_run_
!= overflowed_action_wants_to_run
) {
729 overflowed_action_wants_to_run_
= overflowed_action_wants_to_run
;
730 if (send_overflowed_action_changes_
)
731 delegate_
->OnOverflowedActionWantsToRunChanged(
732 overflowed_action_wants_to_run_
);
736 ToolbarActionViewController
* ToolbarActionsBar::GetActionForId(
737 const std::string
& action_id
) {
738 for (ToolbarActionViewController
* action
: toolbar_actions_
) {
739 if (action
->GetId() == action_id
)
745 content::WebContents
* ToolbarActionsBar::GetCurrentWebContents() {
746 return browser_
->tab_strip_model()->GetActiveWebContents();