ExtensionSyncService: listen for relevant changes instead of being explicitly called...
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / toolbar_actions_bar.cc
blobe6cb99cc83c5e8c47edf832bc6113f86a75cc7e7
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"
37 namespace {
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;
55 if (!initialized) {
56 initialized = true;
57 gfx::ImageSkia* skia =
58 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
59 IDR_BROWSER_ACTION);
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,
79 FunctionType equal) {
80 DCHECK_GE(to_sort->size(), reference.size()) <<
81 "|to_sort| must contain all elements in |reference|.";
82 if (reference.empty())
83 return;
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.
90 size_t j = i + 1;
91 while (!equal(to_sort->at(j), reference[i])) {
92 ++j;
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));
101 } // namespace
103 // static
104 bool ToolbarActionsBar::disable_animations_for_testing_ = false;
106 // static
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()->
115 IsEnabled()) {
118 ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate* delegate,
119 Browser* browser,
120 ToolbarActionsBar* main_bar)
121 : delegate_(delegate),
122 browser_(browser),
123 model_(ToolbarActionsModel::Get(browser_->profile())),
124 main_bar_(main_bar),
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.";
145 // static
146 int ToolbarActionsBar::IconWidth(bool include_padding) {
147 return GetIconDimension(WIDTH) + (include_padding ? kItemSpacing : 0);
150 // static
151 int ToolbarActionsBar::IconHeight() {
152 return GetIconDimension(HEIGHT);
155 // static
156 void ToolbarActionsBar::RegisterProfilePrefs(
157 user_prefs::PrefRegistrySyncable* registry) {
158 registry->RegisterBooleanPref(
159 prefs::kToolbarIconSurfacingBubbleAcknowledged,
160 false,
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;
175 return gfx::Size(
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())
183 return gfx::Size();
185 return gfx::Size(IconCountToWidth(icon_count), IconHeight());
188 int ToolbarActionsBar::GetMinimumWidth() const {
189 if (!platform_settings_.chevron_enabled || toolbar_actions_.empty())
190 return kLeftPadding;
191 return kLeftPadding + delegate_->GetChevronWidth() + kRightPadding;
194 int ToolbarActionsBar::GetMaximumWidth() const {
195 return IconCountToWidth(-1);
198 int ToolbarActionsBar::IconCountToWidth(int icons) const {
199 if (icons < 0)
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 {
233 if (!model_)
234 return 0u;
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;
257 #if DCHECK_IS_ON()
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());
266 #endif
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
277 // empty rect.
278 if (index < start_index)
279 return gfx::Rect();
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(),
291 IconWidth(false),
292 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_) {
302 size_t index =
303 std::find(actions.begin(), actions.end(), popped_out_action_) -
304 actions.begin();
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);
315 return actions;
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())
322 return;
325 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337
326 // is fixed.
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
336 // is fixed.
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
344 // is fixed.
345 tracked_objects::ScopedTracker tracking_profile3(
346 FROM_HERE_WITH_EXPLICIT_FUNCTION(
347 "ToolbarActionsBar::CreateActions3"));
348 ReorderActions();
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();
371 if (controller) {
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() {
381 HideActivePopup();
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())
404 return false;
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,
426 int dropped_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);
431 return;
434 int delta = 0;
435 if (drag_type == DRAG_TO_OVERFLOW)
436 delta = -1;
437 else if (drag_type == DRAG_TO_MAIN)
438 delta = 1;
439 model_->MoveActionIcon(toolbar_actions_[dragged_index]->GetId(),
440 dropped_index);
441 if (delta)
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;
473 if (needs_redraw) {
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);
484 } else {
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() {
510 if (popup_owner_)
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();
528 } else {
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);
534 if (anchor_action)
535 break;
537 delegate_->ShowExtensionMessageBubble(controller.Pass(), anchor_action);
541 void ToolbarActionsBar::OnToolbarActionAdded(const std::string& action_id,
542 int index) {
543 DCHECK(GetActionForId(action_id) == nullptr)
544 << "Asked to add a toolbar action view for an extension that already "
545 "exists";
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()
552 .GetByID(action_id);
553 // Only extensions should be added after initialization.
554 DCHECK(extension);
556 toolbar_actions_.insert(
557 toolbar_actions_.begin() + index,
558 new ExtensionActionViewController(
559 extension,
560 browser_,
561 extensions::ExtensionActionManager::Get(browser_->profile())->
562 GetExtensionAction(*extension),
563 this));
565 delegate_->AddViewForAction(toolbar_actions_[index], index);
567 // If we are still initializing the container, don't bother animating.
568 if (!model_->actions_initialized())
569 return;
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)
585 ++iter;
587 if (iter == toolbar_actions_.end())
588 return;
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())
603 ->runtime_data()
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);
613 } else {
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,
625 int index) {
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.
630 ReorderActions();
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.
637 if (action) {
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();
658 } else {
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())
670 return;
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.
675 DeleteActions();
676 CreateActions();
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());
685 CreateActions();
687 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/463337 is
688 // fixed.
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())
697 return;
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())
718 return;
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;
724 break;
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)
740 return action;
742 return nullptr;
745 content::WebContents* ToolbarActionsBar::GetCurrentWebContents() {
746 return browser_->tab_strip_model()->GetActiveWebContents();