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;
106 ToolbarActionsBar::PlatformSettings::PlatformSettings(bool in_overflow_mode
)
107 : left_padding(in_overflow_mode
? kOverflowLeftPadding
: kLeftPadding
),
108 right_padding(in_overflow_mode
? kOverflowRightPadding
: kRightPadding
),
109 item_spacing(kItemSpacing
),
110 icons_per_overflow_menu_row(1),
111 chevron_enabled(!extensions::FeatureSwitch::extension_action_redesign()->
115 ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate
* delegate
,
117 ToolbarActionsBar
* main_bar
)
118 : delegate_(delegate
),
120 model_(ToolbarActionsModel::Get(browser_
->profile())),
122 platform_settings_(main_bar
!= nullptr),
123 popup_owner_(nullptr),
124 model_observer_(this),
125 suppress_layout_(false),
126 suppress_animation_(true),
127 checked_extension_bubble_(false),
128 is_drag_in_progress_(false),
129 popped_out_action_(nullptr),
130 weak_ptr_factory_(this) {
131 if (model_
) // |model_| can be null in unittests.
132 model_observer_
.Add(model_
);
135 ToolbarActionsBar::~ToolbarActionsBar() {
136 // We don't just call DeleteActions() here because it makes assumptions about
137 // the order of deletion between the views and the ToolbarActionsBar.
138 DCHECK(toolbar_actions_
.empty()) <<
139 "Must call DeleteActions() before destruction.";
143 int ToolbarActionsBar::IconWidth(bool include_padding
) {
144 return GetIconDimension(WIDTH
) + (include_padding
? kItemSpacing
: 0);
148 int ToolbarActionsBar::IconHeight() {
149 return GetIconDimension(HEIGHT
);
153 void ToolbarActionsBar::RegisterProfilePrefs(
154 user_prefs::PrefRegistrySyncable
* registry
) {
155 registry
->RegisterBooleanPref(
156 prefs::kToolbarIconSurfacingBubbleAcknowledged
,
158 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
159 registry
->RegisterInt64Pref(prefs::kToolbarIconSurfacingBubbleLastShowTime
,
163 gfx::Size
ToolbarActionsBar::GetPreferredSize() const {
164 int icon_count
= GetIconCount();
165 if (in_overflow_mode()) {
166 // In overflow, we always have a preferred size of a full row (even if we
167 // don't use it), and always of at least one row. The parent may decide to
168 // show us even when empty, e.g. as a drag target for dragging in icons from
169 // the main container.
170 int row_count
= ((std::max(0, icon_count
- 1)) /
171 platform_settings_
.icons_per_overflow_menu_row
) + 1;
173 IconCountToWidth(platform_settings_
.icons_per_overflow_menu_row
),
174 row_count
* IconHeight());
177 // If there are no actions to show (and this isn't an overflow container),
178 // then don't show the container at all.
179 if (toolbar_actions_
.empty())
182 return gfx::Size(IconCountToWidth(icon_count
), IconHeight());
185 int ToolbarActionsBar::GetMinimumWidth() const {
186 if (!platform_settings_
.chevron_enabled
|| toolbar_actions_
.empty())
188 return kLeftPadding
+ delegate_
->GetChevronWidth() + kRightPadding
;
191 int ToolbarActionsBar::GetMaximumWidth() const {
192 return IconCountToWidth(-1);
195 int ToolbarActionsBar::IconCountToWidth(int icons
) const {
197 icons
= toolbar_actions_
.size();
198 bool display_chevron
=
199 platform_settings_
.chevron_enabled
&&
200 static_cast<size_t>(icons
) < toolbar_actions_
.size();
201 if (icons
== 0 && !display_chevron
)
202 return platform_settings_
.left_padding
;
203 int icons_size
= (icons
== 0) ? 0 :
204 (icons
* IconWidth(true)) - platform_settings_
.item_spacing
;
205 int chevron_size
= display_chevron
? delegate_
->GetChevronWidth() : 0;
206 int padding
= platform_settings_
.left_padding
+
207 platform_settings_
.right_padding
;
208 return icons_size
+ chevron_size
+ padding
;
211 size_t ToolbarActionsBar::WidthToIconCount(int pixels
) const {
212 // Check for widths large enough to show the entire icon set.
213 if (pixels
>= IconCountToWidth(-1))
214 return toolbar_actions_
.size();
216 // We reserve space for the padding on either side of the toolbar...
217 int available_space
= pixels
-
218 (platform_settings_
.left_padding
+ platform_settings_
.right_padding
);
219 // ... and, if the chevron is enabled, the chevron.
220 if (platform_settings_
.chevron_enabled
)
221 available_space
-= delegate_
->GetChevronWidth();
223 // Now we add an extra between-item padding value so the space can be divided
224 // evenly by (size of icon with padding).
225 return static_cast<size_t>(std::max(
226 0, available_space
+ platform_settings_
.item_spacing
) / IconWidth(true));
229 size_t ToolbarActionsBar::GetIconCount() const {
233 int pop_out_modifier
= 0;
234 // If there is a popped out action, it could affect the number of visible
235 // icons - but only if it wouldn't otherwise be visible.
236 if (popped_out_action_
) {
237 size_t popped_out_index
=
238 std::find(toolbar_actions_
.begin(),
239 toolbar_actions_
.end(),
240 popped_out_action_
) - toolbar_actions_
.begin();
241 pop_out_modifier
= popped_out_index
>= model_
->visible_icon_count() ? 1 : 0;
244 // We purposefully do not account for any "popped out" actions in overflow
245 // mode. This is because the popup cannot be showing while the overflow menu
246 // is open, so there's no concern there. Also, if the user has a popped out
247 // action, and immediately opens the overflow menu, we *want* the action there
248 // (since it will close the popup, but do so asynchronously, and we don't
249 // want to "slide" the action back in.
250 size_t visible_icons
= in_overflow_mode() ?
251 toolbar_actions_
.size() - model_
->visible_icon_count() :
252 model_
->visible_icon_count() + pop_out_modifier
;
255 // Good time for some sanity checks: We should never try to display more
256 // icons than we have, and we should always have a view per item in the model.
257 // (The only exception is if this is in initialization.)
258 if (!toolbar_actions_
.empty() && !suppress_layout_
&&
259 model_
->actions_initialized()) {
260 DCHECK_LE(visible_icons
, toolbar_actions_
.size());
261 DCHECK_EQ(model_
->toolbar_items().size(), toolbar_actions_
.size());
265 return visible_icons
;
268 size_t ToolbarActionsBar::GetStartIndexInBounds() const {
269 return in_overflow_mode() ? main_bar_
->GetEndIndexInBounds() : 0;
272 size_t ToolbarActionsBar::GetEndIndexInBounds() const {
273 // The end index for the main bar is however many icons can fit with the given
274 // width. We take the width-after-animation here so that we don't have to
275 // constantly adjust both this and the overflow as the size changes (the
276 // animations are small and fast enough that this doesn't cause problems).
277 return in_overflow_mode()
278 ? toolbar_actions_
.size()
279 : WidthToIconCount(delegate_
->GetWidth(
280 ToolbarActionsBarDelegate::GET_WIDTH_AFTER_ANIMATION
));
283 bool ToolbarActionsBar::NeedsOverflow() const {
284 DCHECK(!in_overflow_mode());
285 return GetEndIndexInBounds() != toolbar_actions_
.size() ||
286 is_drag_in_progress_
;
289 gfx::Rect
ToolbarActionsBar::GetFrameForIndex(
290 size_t index
) const {
291 size_t start_index
= GetStartIndexInBounds();
293 // If the index is for an action that is before range we show (i.e., is for
294 // a button that's on the main bar, and this is the overflow), send back an
296 if (index
< start_index
)
299 size_t relative_index
= index
- start_index
;
300 int icons_per_overflow_row
= platform_settings().icons_per_overflow_menu_row
;
301 size_t row_index
= in_overflow_mode() ?
302 relative_index
/ icons_per_overflow_row
: 0;
303 size_t index_in_row
= in_overflow_mode() ?
304 relative_index
% icons_per_overflow_row
: relative_index
;
306 return gfx::Rect(platform_settings().left_padding
+
307 index_in_row
* IconWidth(true),
308 row_index
* IconHeight(),
313 std::vector
<ToolbarActionViewController
*>
314 ToolbarActionsBar::GetActions() const {
315 std::vector
<ToolbarActionViewController
*> actions
= toolbar_actions_
.get();
317 // If there is an action that should be popped out, and it's not visible by
318 // default, make it the final action in the list.
319 if (popped_out_action_
) {
321 std::find(actions
.begin(), actions
.end(), popped_out_action_
) -
323 DCHECK_NE(actions
.size(), index
);
324 size_t visible
= GetIconCount();
325 if (index
>= visible
) {
326 size_t rindex
= actions
.size() - index
- 1;
327 std::rotate(actions
.rbegin() + rindex
,
328 actions
.rbegin() + rindex
+ 1,
329 actions
.rend() - visible
+ 1);
336 void ToolbarActionsBar::CreateActions() {
337 DCHECK(toolbar_actions_
.empty());
338 // If the model isn't initialized, wait for it.
339 if (!model_
|| !model_
->actions_initialized())
343 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
345 tracked_objects::ScopedTracker
tracking_profile1(
346 FROM_HERE_WITH_EXPLICIT_FUNCTION("ToolbarActionsBar::CreateActions1"));
347 // We don't redraw the view while creating actions.
348 base::AutoReset
<bool> layout_resetter(&suppress_layout_
, true);
350 // Get the toolbar actions.
351 toolbar_actions_
= model_
->CreateActions(browser_
, this);
352 if (!model_
->is_highlighting()) {
353 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
355 tracked_objects::ScopedTracker
tracking_profile2(
356 FROM_HERE_WITH_EXPLICIT_FUNCTION(
357 "ToolbarActionsBar::CreateActions2"));
360 if (!toolbar_actions_
.empty()) {
361 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
363 tracked_objects::ScopedTracker
tracking_profile3(
364 FROM_HERE_WITH_EXPLICIT_FUNCTION(
365 "ToolbarActionsBar::CreateActions3"));
369 tracked_objects::ScopedTracker
tracking_profile4(
370 FROM_HERE_WITH_EXPLICIT_FUNCTION("ToolbarActionsBar::CreateActions4"));
372 for (size_t i
= 0; i
< toolbar_actions_
.size(); ++i
)
373 delegate_
->AddViewForAction(toolbar_actions_
[i
], i
);
376 // Once the actions are created, we should animate the changes.
377 suppress_animation_
= false;
379 // CreateActions() can be called multiple times, so we need to make sure we
380 // haven't already shown the bubble.
381 // Extension bubbles can also highlight a subset of actions, so don't show the
382 // bubble if the toolbar is already highlighting a different set.
383 if (!checked_extension_bubble_
&& !is_highlighting()) {
384 checked_extension_bubble_
= true;
385 // CreateActions() can be called as part of the browser window set up, which
386 // we need to let finish before showing the actions.
387 scoped_ptr
<extensions::ExtensionMessageBubbleController
> controller
=
388 ExtensionMessageBubbleFactory(browser_
).GetController();
390 base::ThreadTaskRunnerHandle::Get()->PostTask(
391 FROM_HERE
, base::Bind(&ToolbarActionsBar::MaybeShowExtensionBubble
,
392 weak_ptr_factory_
.GetWeakPtr(),
393 base::Passed(controller
.Pass())));
398 void ToolbarActionsBar::DeleteActions() {
400 delegate_
->RemoveAllViews();
401 toolbar_actions_
.clear();
404 void ToolbarActionsBar::Update() {
405 if (toolbar_actions_
.empty())
406 return; // Nothing to do.
409 // Don't layout until the end.
410 base::AutoReset
<bool> layout_resetter(&suppress_layout_
, true);
411 for (ToolbarActionViewController
* action
: toolbar_actions_
)
412 action
->UpdateState();
415 ReorderActions(); // Also triggers a draw.
418 bool ToolbarActionsBar::ShowToolbarActionPopup(const std::string
& action_id
,
419 bool grant_active_tab
) {
420 // Don't override another popup, and only show in the active window.
421 if (popup_owner() || !browser_
->window()->IsActive())
424 ToolbarActionViewController
* action
= GetActionForId(action_id
);
425 return action
&& action
->ExecuteAction(grant_active_tab
);
428 void ToolbarActionsBar::SetOverflowRowWidth(int width
) {
429 DCHECK(in_overflow_mode());
430 platform_settings_
.icons_per_overflow_menu_row
=
431 std::max((width
- kItemSpacing
) / IconWidth(true), 1);
434 void ToolbarActionsBar::OnResizeComplete(int width
) {
435 DCHECK(!in_overflow_mode()); // The user can't resize the overflow container.
436 size_t resized_count
= WidthToIconCount(width
);
437 // Save off the desired number of visible icons. We do this now instead of
438 // at the end of the animation so that even if the browser is shut down
439 // while animating, the right value will be restored on next run.
440 model_
->SetVisibleIconCount(resized_count
);
443 void ToolbarActionsBar::OnDragStarted() {
444 // All drag-and-drop commands should go to the main bar.
445 ToolbarActionsBar
* main_bar
= in_overflow_mode() ? main_bar_
: this;
446 DCHECK(!main_bar
->is_drag_in_progress_
);
447 main_bar
->is_drag_in_progress_
= true;
450 void ToolbarActionsBar::OnDragEnded() {
451 // All drag-and-drop commands should go to the main bar.
452 ToolbarActionsBar
* main_bar
= in_overflow_mode() ? main_bar_
: this;
453 DCHECK(main_bar
->is_drag_in_progress_
);
454 main_bar
->is_drag_in_progress_
= false;
457 void ToolbarActionsBar::OnDragDrop(int dragged_index
,
459 DragType drag_type
) {
460 if (in_overflow_mode()) {
461 // All drag-and-drop commands should go to the main bar.
462 main_bar_
->OnDragDrop(dragged_index
, dropped_index
, drag_type
);
466 is_drag_in_progress_
= false;
468 if (drag_type
== DRAG_TO_OVERFLOW
)
470 else if (drag_type
== DRAG_TO_MAIN
)
472 model_
->MoveActionIcon(toolbar_actions_
[dragged_index
]->GetId(),
475 model_
->SetVisibleIconCount(model_
->visible_icon_count() + delta
);
478 void ToolbarActionsBar::OnAnimationEnded() {
479 // Check if we were waiting for animation to complete to either show a
480 // message bubble, or to show a popup.
481 if (pending_extension_bubble_controller_
) {
482 MaybeShowExtensionBubble(pending_extension_bubble_controller_
.Pass());
483 } else if (!popped_out_closure_
.is_null()) {
484 popped_out_closure_
.Run();
485 popped_out_closure_
.Reset();
489 bool ToolbarActionsBar::IsActionVisibleOnMainBar(
490 const ToolbarActionViewController
* action
) const {
491 if (in_overflow_mode())
492 return main_bar_
->IsActionVisibleOnMainBar(action
);
494 size_t index
= std::find(toolbar_actions_
.begin(),
495 toolbar_actions_
.end(),
496 action
) - toolbar_actions_
.begin();
497 return index
< GetIconCount() || action
== popped_out_action_
;
500 void ToolbarActionsBar::PopOutAction(ToolbarActionViewController
* controller
,
501 const base::Closure
& closure
) {
502 DCHECK(!in_overflow_mode()) << "Only the main bar can pop out actions.";
503 DCHECK(!popped_out_action_
) << "Only one action can be popped out at a time!";
504 bool needs_redraw
= !IsActionVisibleOnMainBar(controller
);
505 popped_out_action_
= controller
;
507 // We suppress animation for this draw, because we need the action to get
508 // into position immediately, since it's about to show its popup.
509 base::AutoReset
<bool> layout_resetter(&suppress_animation_
, false);
510 delegate_
->Redraw(true);
513 ResizeDelegate(gfx::Tween::LINEAR
, false);
514 if (!delegate_
->IsAnimating()) {
515 // Don't call the closure re-entrantly.
516 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
, closure
);
518 popped_out_closure_
= closure
;
522 void ToolbarActionsBar::UndoPopOut() {
523 DCHECK(!in_overflow_mode()) << "Only the main bar can pop out actions.";
524 DCHECK(popped_out_action_
);
525 ToolbarActionViewController
* controller
= popped_out_action_
;
526 popped_out_action_
= nullptr;
527 popped_out_closure_
.Reset();
528 if (!IsActionVisibleOnMainBar(controller
))
529 delegate_
->Redraw(true);
530 ResizeDelegate(gfx::Tween::LINEAR
, false);
533 void ToolbarActionsBar::SetPopupOwner(
534 ToolbarActionViewController
* popup_owner
) {
535 // We should never be setting a popup owner when one already exists, and
536 // never unsetting one when one wasn't set.
537 DCHECK((!popup_owner_
&& popup_owner
) ||
538 (popup_owner_
&& !popup_owner
));
539 popup_owner_
= popup_owner
;
542 void ToolbarActionsBar::HideActivePopup() {
544 popup_owner_
->HidePopup();
545 DCHECK(!popup_owner_
);
548 ToolbarActionViewController
* ToolbarActionsBar::GetMainControllerForAction(
549 ToolbarActionViewController
* action
) {
550 return in_overflow_mode() ?
551 main_bar_
->GetActionForId(action
->GetId()) : action
;
554 void ToolbarActionsBar::MaybeShowExtensionBubble(
555 scoped_ptr
<extensions::ExtensionMessageBubbleController
> controller
) {
556 controller
->HighlightExtensionsIfNecessary(); // Safe to call multiple times.
557 if (delegate_
->IsAnimating()) {
558 // If the toolbar is animating, we can't effectively anchor the bubble,
559 // so wait until animation stops.
560 pending_extension_bubble_controller_
= controller
.Pass();
562 const std::vector
<std::string
>& affected_extensions
=
563 controller
->GetExtensionIdList();
564 ToolbarActionViewController
* anchor_action
= nullptr;
565 for (const std::string
& id
: affected_extensions
) {
566 anchor_action
= GetActionForId(id
);
570 delegate_
->ShowExtensionMessageBubble(controller
.Pass(), anchor_action
);
574 void ToolbarActionsBar::OnToolbarActionAdded(const std::string
& action_id
,
576 DCHECK(GetActionForId(action_id
) == nullptr)
577 << "Asked to add a toolbar action view for an extension that already "
580 // TODO(devlin): This is a minor layering violation and the model should pass
581 // in an action directly.
582 const extensions::Extension
* extension
=
583 extensions::ExtensionRegistry::Get(browser_
->profile())
584 ->enabled_extensions()
586 // Only extensions should be added after initialization.
589 toolbar_actions_
.insert(
590 toolbar_actions_
.begin() + index
,
591 new ExtensionActionViewController(
594 extensions::ExtensionActionManager::Get(browser_
->profile())->
595 GetExtensionAction(*extension
),
598 delegate_
->AddViewForAction(toolbar_actions_
[index
], index
);
600 // If we are still initializing the container, don't bother animating.
601 if (!model_
->actions_initialized())
604 // We may need to resize (e.g. to show the new icon, or the chevron). We don't
605 // need to check if the extension is upgrading here, because ResizeDelegate()
606 // checks to see if the container is already the proper size, and because
607 // if the action is newly incognito enabled, even though it's a reload, it's
608 // a new extension to this toolbar.
609 // We suppress the chevron during animation because, if we're expanding to
610 // show a new icon, we don't want to have the chevron visible only for the
611 // duration of the animation.
612 ResizeDelegate(gfx::Tween::LINEAR
, true);
615 void ToolbarActionsBar::OnToolbarActionRemoved(const std::string
& action_id
) {
616 ToolbarActions::iterator iter
= toolbar_actions_
.begin();
617 while (iter
!= toolbar_actions_
.end() && (*iter
)->GetId() != action_id
)
620 if (iter
== toolbar_actions_
.end())
623 // The action should outlive the UI element (which is owned by the delegate),
624 // so we can't delete it just yet. But we should remove it from the list of
625 // actions so that any width calculations are correct.
626 scoped_ptr
<ToolbarActionViewController
> removed_action(*iter
);
627 toolbar_actions_
.weak_erase(iter
);
628 delegate_
->RemoveViewForAction(removed_action
.get());
629 removed_action
.reset();
631 // If the extension is being upgraded we don't want the bar to shrink
632 // because the icon is just going to get re-added to the same location.
633 // There is an exception if this is an off-the-record profile, and the
634 // extension is no longer incognito-enabled.
635 if (!extensions::ExtensionSystem::Get(browser_
->profile())
637 ->IsBeingUpgraded(action_id
) ||
638 (browser_
->profile()->IsOffTheRecord() &&
639 !extensions::util::IsIncognitoEnabled(action_id
, browser_
->profile()))) {
640 if (toolbar_actions_
.size() > model_
->visible_icon_count()) {
641 // If we have more icons than we can show, then we must not be changing
642 // the container size (since we either removed an icon from the main
643 // area and one from the overflow list will have shifted in, or we
644 // removed an entry directly from the overflow list).
645 delegate_
->Redraw(false);
647 delegate_
->SetChevronVisibility(false);
648 // Either we went from overflow to no-overflow, or we shrunk the no-
649 // overflow container by 1. Either way the size changed, so animate.
650 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
655 void ToolbarActionsBar::OnToolbarActionMoved(const std::string
& action_id
,
657 DCHECK(index
>= 0 && index
< static_cast<int>(toolbar_actions_
.size()));
658 // Unfortunately, |index| doesn't really mean a lot to us, because this
659 // window's toolbar could be different (if actions are popped out). Just
660 // do a full reorder.
664 void ToolbarActionsBar::OnToolbarActionUpdated(const std::string
& action_id
) {
665 ToolbarActionViewController
* action
= GetActionForId(action_id
);
666 // There might not be a view in cases where we are highlighting or if we
667 // haven't fully initialized the actions.
669 action
->UpdateState();
672 void ToolbarActionsBar::OnToolbarVisibleCountChanged() {
673 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
676 void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type
,
677 bool suppress_chevron
) {
678 int desired_width
= GetPreferredSize().width();
680 delegate_
->GetWidth(ToolbarActionsBarDelegate::GET_WIDTH_CURRENT
)) {
681 delegate_
->ResizeAndAnimate(tween_type
, desired_width
, suppress_chevron
);
682 } else if (delegate_
->IsAnimating()) {
683 // It's possible that we're right where we're supposed to be in terms of
684 // width, but that we're also currently resizing. If this is the case, end
685 // the current animation with the current width.
686 delegate_
->StopAnimating();
688 // We may already be at the right size (this can happen frequently with
689 // overflow, where we have a fixed width, and in tests, where we skip
690 // animations). If this is the case, we still need to Redraw(), because the
691 // icons within the toolbar may have changed (e.g. if we removed one
692 // action and added a different one in quick succession).
693 delegate_
->Redraw(false);
697 void ToolbarActionsBar::OnToolbarHighlightModeChanged(bool is_highlighting
) {
698 if (!model_
->actions_initialized())
700 // It's a bit of a pain that we delete and recreate everything here, but given
701 // everything else going on (the lack of highlight, [n] more extensions
702 // appearing, etc), it's not worth the extra complexity to create and insert
703 // only the new actions.
706 // Resize the delegate. We suppress the chevron so that we don't risk showing
707 // it only for the duration of the animation.
708 ResizeDelegate(gfx::Tween::LINEAR
, true);
711 void ToolbarActionsBar::OnToolbarModelInitialized() {
712 // We shouldn't have any actions before the model is initialized.
713 DCHECK(toolbar_actions_
.empty());
716 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337 is
718 tracked_objects::ScopedTracker
tracking_profile(
719 FROM_HERE_WITH_EXPLICIT_FUNCTION(
720 "ToolbarActionsBar::OnToolbarModelInitialized"));
721 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
724 void ToolbarActionsBar::ReorderActions() {
725 if (toolbar_actions_
.empty())
728 // First, reset the order to that of the model.
729 auto compare
= [](ToolbarActionViewController
* const& action
,
730 const ToolbarActionsModel::ToolbarItem
& item
) {
731 return action
->GetId() == item
.id
;
733 SortContainer(&toolbar_actions_
.get(), model_
->toolbar_items(), compare
);
735 // Our visible browser actions may have changed - re-Layout() and check the
736 // size (if we aren't suppressing the layout).
737 if (!suppress_layout_
) {
738 ResizeDelegate(gfx::Tween::EASE_OUT
, false);
739 delegate_
->Redraw(true);
743 ToolbarActionViewController
* ToolbarActionsBar::GetActionForId(
744 const std::string
& action_id
) {
745 for (ToolbarActionViewController
* action
: toolbar_actions_
) {
746 if (action
->GetId() == action_id
)
752 content::WebContents
* ToolbarActionsBar::GetCurrentWebContents() {
753 return browser_
->tab_strip_model()->GetActiveWebContents();