base: Change DCHECK_IS_ON to a macro DCHECK_IS_ON().
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / toolbar_actions_bar.cc
blobfc749dd5938bd72e9e0ef76e5e261108dfeb6c15
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"
29 namespace {
31 using WeakToolbarActions = std::vector<ToolbarActionViewController*>;
33 #if defined(OS_MACOSX)
34 const int kItemSpacing = 2;
35 const int kLeftPadding = kItemSpacing + 1;
36 const int kRightPadding = 0;
37 const int kOverflowLeftPadding = kItemSpacing;
38 const int kOverflowRightPadding = kItemSpacing;
39 #else
40 // Matches ToolbarView::kStandardSpacing;
41 const int kLeftPadding = 3;
42 const int kRightPadding = kLeftPadding;
43 const int kItemSpacing = kLeftPadding;
44 const int kOverflowLeftPadding = kItemSpacing;
45 const int kOverflowRightPadding = kItemSpacing;
46 #endif
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_LE(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::pop_out_actions_to_run_ = false;
109 // static
110 bool ToolbarActionsBar::send_overflowed_action_changes_ = true;
112 // A class to implement an optional tab ordering that "pops out" actions that
113 // want to run on a particular page, as part of our experimentation with how to
114 // best signal that actions like extension page actions want to run.
115 // TODO(devlin): Once we finally settle on the right behavior, determine if
116 // we need this.
117 class ToolbarActionsBar::TabOrderHelper
118 : public TabStripModelObserver {
119 public:
120 TabOrderHelper(ToolbarActionsBar* toolbar,
121 Browser* browser,
122 extensions::ExtensionToolbarModel* model);
123 ~TabOrderHelper() override;
125 // Returns the number of extra icons that should appear on the given |tab_id|
126 // because of actions that are going to pop out.
127 size_t GetExtraIconCount(int tab_id);
129 // Returns the item order of actions for the tab with the given
130 // |web_contents|.
131 WeakToolbarActions GetActionOrder(content::WebContents* web_contents);
133 // Notifies the TabOrderHelper that a given |action| does/doesn't want to run
134 // on the tab indicated by |tab_id|.
135 void SetActionWantsToRun(ToolbarActionViewController* action,
136 int tab_id,
137 bool wants_to_run);
139 // Notifies the TabOrderHelper that a given |action| has been removed.
140 void ActionRemoved(ToolbarActionViewController* action);
142 // Handles a resize, including updating any actions that want to act and
143 // updating the model.
144 void HandleResize(size_t resized_count, int tab_id);
146 // Handles a drag and drop, including updating any actions that want to act
147 // and updating the model.
148 void HandleDragDrop(int dragged,
149 int dropped,
150 ToolbarActionsBar::DragType drag_type,
151 int tab_id);
153 void notify_overflow_bar(ToolbarActionsBar* overflow_bar,
154 bool should_notify) {
155 // There's a possibility that a new overflow bar can be constructed before
156 // the first is fully destroyed. Only un-register the |overflow_bar_| if
157 // it's the one making the request.
158 if (should_notify)
159 overflow_bar_ = overflow_bar;
160 else if (overflow_bar_ == overflow_bar)
161 overflow_bar_ = nullptr;
164 private:
165 // TabStripModelObserver:
166 void TabInsertedAt(content::WebContents* web_contents,
167 int index,
168 bool foreground) override;
169 void TabDetachedAt(content::WebContents* web_contents, int index) override;
170 void ActiveTabChanged(content::WebContents* old_contents,
171 content::WebContents* new_contents,
172 int index,
173 int reason) override;
174 void TabStripModelDeleted() override;
176 // Notifies the main |toolbar_| and, if present, the |overflow_bar_| that
177 // actions need to be reordered.
178 void NotifyReorderActions();
180 // The set of tabs for the given action (the key) is currently "popped out".
181 // "Popped out" actions are those that were in the overflow menu normally, but
182 // want to run and are moved to the main bar so the user can see them.
183 std::map<ToolbarActionViewController*, std::set<int>> popped_out_in_tabs_;
185 // The set of tab ids that have been checked for whether actions need to be
186 // popped out or not.
187 std::set<int> tabs_checked_for_pop_out_;
189 // The owning ToolbarActionsBar.
190 ToolbarActionsBar* toolbar_;
192 // The overflow bar, if one is present.
193 ToolbarActionsBar* overflow_bar_;
195 // The associated toolbar model.
196 extensions::ExtensionToolbarModel* model_;
198 // A scoped tab strip observer so we can clean up |tabs_checked_for_popout_|.
199 ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_;
201 DISALLOW_COPY_AND_ASSIGN(TabOrderHelper);
204 ToolbarActionsBar::TabOrderHelper::TabOrderHelper(
205 ToolbarActionsBar* toolbar,
206 Browser* browser,
207 extensions::ExtensionToolbarModel* model)
208 : toolbar_(toolbar),
209 overflow_bar_(nullptr),
210 model_(model),
211 tab_strip_observer_(this) {
212 tab_strip_observer_.Add(browser->tab_strip_model());
215 ToolbarActionsBar::TabOrderHelper::~TabOrderHelper() {
218 size_t ToolbarActionsBar::TabOrderHelper::GetExtraIconCount(int tab_id) {
219 size_t extra_icons = 0;
220 const WeakToolbarActions& toolbar_actions = toolbar_->toolbar_actions();
221 for (ToolbarActionViewController* action : toolbar_actions) {
222 auto actions_tabs = popped_out_in_tabs_.find(action);
223 if (actions_tabs != popped_out_in_tabs_.end() &&
224 actions_tabs->second.count(tab_id))
225 ++extra_icons;
227 return extra_icons;
230 void ToolbarActionsBar::TabOrderHelper::HandleResize(size_t resized_count,
231 int tab_id) {
232 int extra = GetExtraIconCount(tab_id);
233 size_t tab_icon_count = model_->visible_icon_count() + extra;
234 bool reorder_necessary = false;
235 const WeakToolbarActions& toolbar_actions = toolbar_->toolbar_actions();
236 if (resized_count < tab_icon_count) {
237 for (int i = resized_count; i < extra; ++i) {
238 // If an extension that was popped out to act is overflowed, then it
239 // should no longer be popped out, and it also doesn't count for adjusting
240 // the visible count (since it wasn't really out to begin with).
241 if (popped_out_in_tabs_[toolbar_actions[i]].count(tab_id)) {
242 reorder_necessary = true;
243 popped_out_in_tabs_[toolbar_actions[i]].erase(tab_id);
244 ++(resized_count);
247 } else {
248 // If the user increases the toolbar size while actions that popped out are
249 // visible, we need to re-arrange the icons in other windows to be
250 // consistent with what the user sees.
251 // That is, if the normal order is A, B, [C, D] (with C and D hidden), C
252 // pops out to act, and then the user increases the size of the toolbar,
253 // the user sees uncovering D (since C is already out). This is what should
254 // happen in all windows.
255 for (size_t i = tab_icon_count; i < resized_count; ++i) {
256 if (toolbar_actions[i]->GetId() !=
257 model_->toolbar_items()[i - extra]->id())
258 model_->MoveExtensionIcon(toolbar_actions[i]->GetId(), i - extra);
262 resized_count -= extra;
263 model_->SetVisibleIconCount(resized_count);
264 if (reorder_necessary)
265 NotifyReorderActions();
268 void ToolbarActionsBar::TabOrderHelper::HandleDragDrop(
269 int dragged_index,
270 int dropped_index,
271 ToolbarActionsBar::DragType drag_type,
272 int tab_id) {
273 const WeakToolbarActions& toolbar_actions = toolbar_->toolbar_actions();
274 ToolbarActionViewController* action = toolbar_actions[dragged_index];
275 int delta = 0;
276 switch (drag_type) {
277 case ToolbarActionsBar::DRAG_TO_OVERFLOW:
278 // If the user moves an action back into overflow, then we don't adjust
279 // the base visible count, but do stop popping that action out.
280 if (popped_out_in_tabs_[action].count(tab_id))
281 popped_out_in_tabs_[action].erase(tab_id);
282 else
283 delta = -1;
284 break;
285 case ToolbarActionsBar::DRAG_TO_MAIN:
286 delta = 1;
287 break;
288 case ToolbarActionsBar::DRAG_TO_SAME:
289 // If the user moves an action that had popped out to be on the toolbar,
290 // then we treat it as "pinning" the action, and adjust the base visible
291 // count to accommodate.
292 if (popped_out_in_tabs_[action].count(tab_id)) {
293 delta = 1;
294 popped_out_in_tabs_[action].erase(tab_id);
296 break;
299 // If there are any actions that are in front of the dropped index only
300 // because they were popped out, decrement the dropped index.
301 for (int i = 0; i < dropped_index; ++i) {
302 if (i != dragged_index &&
303 model_->GetIndexForId(toolbar_actions[i]->GetId()) >= dropped_index)
304 --dropped_index;
307 model_->MoveExtensionIcon(action->GetId(), dropped_index);
309 if (delta)
310 model_->SetVisibleIconCount(model_->visible_icon_count() + delta);
313 void ToolbarActionsBar::TabOrderHelper::SetActionWantsToRun(
314 ToolbarActionViewController* action,
315 int tab_id,
316 bool wants_to_run) {
317 bool is_overflowed = model_->GetIndexForId(action->GetId()) >=
318 static_cast<int>(model_->visible_icon_count());
319 bool reorder_necessary = false;
320 if (wants_to_run && is_overflowed) {
321 popped_out_in_tabs_[action].insert(tab_id);
322 reorder_necessary = true;
323 } else if (!wants_to_run && popped_out_in_tabs_[action].count(tab_id)) {
324 popped_out_in_tabs_[action].erase(tab_id);
325 reorder_necessary = true;
327 if (reorder_necessary)
328 NotifyReorderActions();
331 void ToolbarActionsBar::TabOrderHelper::ActionRemoved(
332 ToolbarActionViewController* action) {
333 popped_out_in_tabs_.erase(action);
336 WeakToolbarActions ToolbarActionsBar::TabOrderHelper::GetActionOrder(
337 content::WebContents* web_contents) {
338 WeakToolbarActions toolbar_actions = toolbar_->toolbar_actions();
339 // First, make sure that we've checked any actions that want to run.
340 int tab_id = SessionTabHelper::IdForTab(web_contents);
341 if (!tabs_checked_for_pop_out_.count(tab_id)) {
342 tabs_checked_for_pop_out_.insert(tab_id);
343 for (ToolbarActionViewController* toolbar_action : toolbar_actions) {
344 if (toolbar_action->WantsToRun(web_contents))
345 popped_out_in_tabs_[toolbar_action].insert(tab_id);
349 // Then, shift any actions that want to run to the front.
350 size_t insert_at = 0;
351 // Rotate any actions that want to run to the boundary between visible and
352 // overflowed actions.
353 for (WeakToolbarActions::iterator iter =
354 toolbar_actions.begin() + model_->visible_icon_count();
355 iter != toolbar_actions.end(); ++iter) {
356 if (popped_out_in_tabs_[(*iter)].count(tab_id)) {
357 std::rotate(toolbar_actions.begin() + insert_at, iter, iter + 1);
358 ++insert_at;
362 return toolbar_actions;
365 void ToolbarActionsBar::TabOrderHelper::TabInsertedAt(
366 content::WebContents* web_contents,
367 int index,
368 bool foreground) {
369 if (foreground)
370 NotifyReorderActions();
373 void ToolbarActionsBar::TabOrderHelper::TabDetachedAt(
374 content::WebContents* web_contents,
375 int index) {
376 int tab_id = SessionTabHelper::IdForTab(web_contents);
377 for (auto& tabs : popped_out_in_tabs_)
378 tabs.second.erase(tab_id);
379 tabs_checked_for_pop_out_.erase(tab_id);
382 void ToolbarActionsBar::TabOrderHelper::ActiveTabChanged(
383 content::WebContents* old_contents,
384 content::WebContents* new_contents,
385 int index,
386 int reason) {
387 // When we do a bulk-refresh by switching tabs, we don't animate the
388 // difference. We only animate when it's a change driven by the action or the
389 // user.
390 base::AutoReset<bool> animation_reset(&toolbar_->suppress_animation_, true);
391 NotifyReorderActions();
394 void ToolbarActionsBar::TabOrderHelper::TabStripModelDeleted() {
395 tab_strip_observer_.RemoveAll();
398 void ToolbarActionsBar::TabOrderHelper::NotifyReorderActions() {
399 // Reorder the reference toolbar first (since we use its actions in
400 // GetActionOrder()).
401 toolbar_->ReorderActions();
402 if (overflow_bar_)
403 overflow_bar_->ReorderActions();
406 ToolbarActionsBar::PlatformSettings::PlatformSettings(bool in_overflow_mode)
407 : left_padding(in_overflow_mode ? kOverflowLeftPadding : kLeftPadding),
408 right_padding(in_overflow_mode ? kOverflowRightPadding : kRightPadding),
409 item_spacing(kItemSpacing),
410 icons_per_overflow_menu_row(1),
411 chevron_enabled(!extensions::FeatureSwitch::extension_action_redesign()->
412 IsEnabled()) {
415 ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate* delegate,
416 Browser* browser,
417 ToolbarActionsBar* main_bar)
418 : delegate_(delegate),
419 browser_(browser),
420 model_(extensions::ExtensionToolbarModel::Get(browser_->profile())),
421 main_bar_(main_bar),
422 platform_settings_(main_bar != nullptr),
423 model_observer_(this),
424 suppress_layout_(false),
425 suppress_animation_(true),
426 overflowed_action_wants_to_run_(false) {
427 if (model_) // |model_| can be null in unittests.
428 model_observer_.Add(model_);
430 if (pop_out_actions_to_run_) {
431 if (in_overflow_mode())
432 main_bar_->tab_order_helper_->notify_overflow_bar(this, true);
433 else
434 tab_order_helper_.reset(new TabOrderHelper(this, browser_, model_));
438 ToolbarActionsBar::~ToolbarActionsBar() {
439 // We don't just call DeleteActions() here because it makes assumptions about
440 // the order of deletion between the views and the ToolbarActionsBar.
441 DCHECK(toolbar_actions_.empty()) <<
442 "Must call DeleteActions() before destruction.";
443 if (in_overflow_mode() && pop_out_actions_to_run_)
444 main_bar_->tab_order_helper_->notify_overflow_bar(this, false);
447 // static
448 int ToolbarActionsBar::IconWidth(bool include_padding) {
449 return GetIconDimension(WIDTH) + (include_padding ? kItemSpacing : 0);
452 // static
453 int ToolbarActionsBar::IconHeight() {
454 return GetIconDimension(HEIGHT);
457 gfx::Size ToolbarActionsBar::GetPreferredSize() const {
458 int icon_count = GetIconCount();
459 if (in_overflow_mode()) {
460 // In overflow, we always have a preferred size of a full row (even if we
461 // don't use it), and always of at least one row. The parent may decide to
462 // show us even when empty, e.g. as a drag target for dragging in icons from
463 // the main container.
464 int row_count = ((std::max(0, icon_count - 1)) /
465 platform_settings_.icons_per_overflow_menu_row) + 1;
466 return gfx::Size(
467 IconCountToWidth(platform_settings_.icons_per_overflow_menu_row),
468 row_count * IconHeight());
471 // If there are no actions to show (and this isn't an overflow container),
472 // then don't show the container at all.
473 if (toolbar_actions_.empty())
474 return gfx::Size();
476 return gfx::Size(IconCountToWidth(icon_count), IconHeight());
479 int ToolbarActionsBar::GetMinimumWidth() const {
480 if (!platform_settings_.chevron_enabled || toolbar_actions_.empty())
481 return kLeftPadding;
482 return kLeftPadding + delegate_->GetChevronWidth() + kRightPadding;
485 int ToolbarActionsBar::GetMaximumWidth() const {
486 return IconCountToWidth(-1);
489 int ToolbarActionsBar::IconCountToWidth(int icons) const {
490 if (icons < 0)
491 icons = toolbar_actions_.size();
492 bool display_chevron =
493 platform_settings_.chevron_enabled &&
494 static_cast<size_t>(icons) < toolbar_actions_.size();
495 if (icons == 0 && !display_chevron)
496 return platform_settings_.left_padding;
497 int icons_size = (icons == 0) ? 0 :
498 (icons * IconWidth(true)) - platform_settings_.item_spacing;
499 int chevron_size = display_chevron ? delegate_->GetChevronWidth() : 0;
500 int padding = platform_settings_.left_padding +
501 platform_settings_.right_padding;
502 return icons_size + chevron_size + padding;
505 size_t ToolbarActionsBar::WidthToIconCount(int pixels) const {
506 // Check for widths large enough to show the entire icon set.
507 if (pixels >= IconCountToWidth(-1))
508 return toolbar_actions_.size();
510 // We reserve space for the padding on either side of the toolbar...
511 int available_space = pixels -
512 (platform_settings_.left_padding + platform_settings_.right_padding);
513 // ... and, if the chevron is enabled, the chevron.
514 if (platform_settings_.chevron_enabled)
515 available_space -= delegate_->GetChevronWidth();
517 // Now we add an extra between-item padding value so the space can be divided
518 // evenly by (size of icon with padding).
519 return static_cast<size_t>(std::max(
520 0, available_space + platform_settings_.item_spacing) / IconWidth(true));
523 size_t ToolbarActionsBar::GetIconCount() const {
524 if (!model_)
525 return 0u;
527 size_t extra_icons = 0;
528 if (tab_order_helper_) {
529 extra_icons = tab_order_helper_->GetExtraIconCount(
530 SessionTabHelper::IdForTab(
531 browser_->tab_strip_model()->GetActiveWebContents()));
534 size_t visible_icons = in_overflow_mode() ?
535 toolbar_actions_.size() - main_bar_->GetIconCount() :
536 model_->visible_icon_count() + extra_icons;
538 #if DCHECK_IS_ON()
539 // Good time for some sanity checks: We should never try to display more
540 // icons than we have, and we should always have a view per item in the model.
541 // (The only exception is if this is in initialization.)
542 if (!toolbar_actions_.empty() && !suppress_layout_ &&
543 model_->extensions_initialized()) {
544 size_t num_extension_actions = 0u;
545 for (ToolbarActionViewController* action : toolbar_actions_) {
546 // No component action should ever have a valid extension id, so we can
547 // use this to check the extension amount.
548 // TODO(devlin): Fix this to just check model size when the model also
549 // includes component actions.
550 if (crx_file::id_util::IdIsValid(action->GetId()))
551 ++num_extension_actions;
553 DCHECK_LE(visible_icons, num_extension_actions);
554 DCHECK_EQ(model_->toolbar_items().size(), num_extension_actions);
556 #endif
558 return visible_icons;
561 void ToolbarActionsBar::CreateActions() {
562 DCHECK(toolbar_actions_.empty());
563 // We wait for the extension system to be initialized before we add any
564 // actions, as they rely on the extension system to function.
565 if (!model_ || !model_->extensions_initialized())
566 return;
569 // We don't redraw the view while creating actions.
570 base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
572 // Extension actions come first.
573 extensions::ExtensionActionManager* action_manager =
574 extensions::ExtensionActionManager::Get(browser_->profile());
575 const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
576 for (const scoped_refptr<const extensions::Extension>& extension :
577 toolbar_items) {
578 toolbar_actions_.push_back(new ExtensionActionViewController(
579 extension.get(),
580 browser_,
581 action_manager->GetExtensionAction(*extension)));
584 // Component actions come second, and are suppressed if the extension
585 // actions are being highlighted.
586 if (!model_->is_highlighting()) {
587 ScopedVector<ToolbarActionViewController> component_actions =
588 ComponentToolbarActionsFactory::GetInstance()->
589 GetComponentToolbarActions();
590 DCHECK(component_actions.empty() ||
591 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled());
592 toolbar_actions_.insert(toolbar_actions_.end(),
593 component_actions.begin(),
594 component_actions.end());
595 component_actions.weak_clear();
598 if (!toolbar_actions_.empty())
599 ReorderActions();
601 for (size_t i = 0; i < toolbar_actions_.size(); ++i)
602 delegate_->AddViewForAction(toolbar_actions_[i], i);
605 // Once the actions are created, we should animate the changes.
606 suppress_animation_ = false;
609 void ToolbarActionsBar::DeleteActions() {
610 delegate_->RemoveAllViews();
611 toolbar_actions_.clear();
614 void ToolbarActionsBar::Update() {
615 if (toolbar_actions_.empty())
616 return; // Nothing to do.
619 // Don't layout until the end.
620 base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
621 for (ToolbarActionViewController* action : toolbar_actions_)
622 action->UpdateState();
625 ReorderActions(); // Also triggers a draw.
628 void ToolbarActionsBar::SetOverflowRowWidth(int width) {
629 DCHECK(in_overflow_mode());
630 platform_settings_.icons_per_overflow_menu_row =
631 std::max((width - kItemSpacing) / IconWidth(true), 1);
634 void ToolbarActionsBar::OnResizeComplete(int width) {
635 DCHECK(!in_overflow_mode()); // The user can't resize the overflow container.
636 size_t resized_count = WidthToIconCount(width);
637 // Save off the desired number of visible icons. We do this now instead of
638 // at the end of the animation so that even if the browser is shut down
639 // while animating, the right value will be restored on next run.
640 if (tab_order_helper_) {
641 tab_order_helper_->HandleResize(
642 resized_count,
643 SessionTabHelper::IdForTab(GetCurrentWebContents()));
644 } else {
645 model_->SetVisibleIconCount(resized_count);
649 void ToolbarActionsBar::OnDragDrop(int dragged_index,
650 int dropped_index,
651 DragType drag_type) {
652 // All drag-and-drop commands should go to the main bar.
653 if (in_overflow_mode()) {
654 main_bar_->OnDragDrop(dragged_index, dropped_index, drag_type);
655 return;
658 if (tab_order_helper_) {
659 tab_order_helper_->HandleDragDrop(
660 dragged_index,
661 dropped_index,
662 drag_type,
663 SessionTabHelper::IdForTab(GetCurrentWebContents()));
664 } else {
665 int delta = 0;
666 if (drag_type == DRAG_TO_OVERFLOW)
667 delta = -1;
668 else if (drag_type == DRAG_TO_MAIN)
669 delta = 1;
670 model_->MoveExtensionIcon(toolbar_actions_[dragged_index]->GetId(),
671 dropped_index);
672 if (delta)
673 model_->SetVisibleIconCount(model_->visible_icon_count() + delta);
677 void ToolbarActionsBar::ToolbarExtensionAdded(
678 const extensions::Extension* extension,
679 int index) {
680 DCHECK(GetActionForId(extension->id()) == nullptr) <<
681 "Asked to add a toolbar action view for an extension that already exists";
683 toolbar_actions_.insert(
684 toolbar_actions_.begin() + index,
685 new ExtensionActionViewController(
686 extension,
687 browser_,
688 extensions::ExtensionActionManager::Get(browser_->profile())->
689 GetExtensionAction(*extension)));
691 delegate_->AddViewForAction(toolbar_actions_[index], index);
693 // If we are still initializing the container, don't bother animating.
694 if (!model_->extensions_initialized())
695 return;
697 // We may need to resize (e.g. to show the new icon, or the chevron). We don't
698 // need to check if the extension is upgrading here, because ResizeDelegate()
699 // checks to see if the container is already the proper size, and because
700 // if the action is newly incognito enabled, even though it's a reload, it's
701 // a new extension to this toolbar.
702 // We suppress the chevron during animation because, if we're expanding to
703 // show a new icon, we don't want to have the chevron visible only for the
704 // duration of the animation.
705 ResizeDelegate(gfx::Tween::LINEAR, true);
708 void ToolbarActionsBar::ToolbarExtensionRemoved(
709 const extensions::Extension* extension) {
710 ToolbarActions::iterator iter = toolbar_actions_.begin();
711 while (iter != toolbar_actions_.end() && (*iter)->GetId() != extension->id())
712 ++iter;
714 if (iter == toolbar_actions_.end())
715 return;
717 delegate_->RemoveViewForAction(*iter);
718 if (tab_order_helper_)
719 tab_order_helper_->ActionRemoved(*iter);
720 toolbar_actions_.erase(iter);
722 // If the extension is being upgraded we don't want the bar to shrink
723 // because the icon is just going to get re-added to the same location.
724 // There is an exception if this is an off-the-record profile, and the
725 // extension is no longer incognito-enabled.
726 if (!extensions::ExtensionSystem::Get(browser_->profile())->runtime_data()->
727 IsBeingUpgraded(extension->id()) ||
728 (browser_->profile()->IsOffTheRecord() &&
729 !extensions::util::IsIncognitoEnabled(extension->id(),
730 browser_->profile()))) {
731 if (toolbar_actions_.size() > model_->visible_icon_count()) {
732 // If we have more icons than we can show, then we must not be changing
733 // the container size (since we either removed an icon from the main
734 // area and one from the overflow list will have shifted in, or we
735 // removed an entry directly from the overflow list).
736 delegate_->Redraw(false);
737 } else {
738 delegate_->SetChevronVisibility(false);
739 // Either we went from overflow to no-overflow, or we shrunk the no-
740 // overflow container by 1. Either way the size changed, so animate.
741 ResizeDelegate(gfx::Tween::EASE_OUT, false);
746 void ToolbarActionsBar::ToolbarExtensionMoved(
747 const extensions::Extension* extension,
748 int index) {
749 DCHECK(index >= 0 && index < static_cast<int>(toolbar_actions_.size()));
750 // Unfortunately, |index| doesn't really mean a lot to us, because this
751 // window's toolbar could be different (if actions are popped out). Just
752 // do a full reorder.
753 ReorderActions();
756 void ToolbarActionsBar::ToolbarExtensionUpdated(
757 const extensions::Extension* extension) {
758 ToolbarActionViewController* action = GetActionForId(extension->id());
759 // There might not be a view in cases where we are highlighting or if we
760 // haven't fully initialized the actions.
761 if (action) {
762 content::WebContents* web_contents = GetCurrentWebContents();
763 action->UpdateState();
765 if (tab_order_helper_) {
766 tab_order_helper_->SetActionWantsToRun(
767 action,
768 SessionTabHelper::IdForTab(web_contents),
769 action->WantsToRun(web_contents));
773 SetOverflowedActionWantsToRun();
776 bool ToolbarActionsBar::ShowExtensionActionPopup(
777 const extensions::Extension* extension,
778 bool grant_active_tab) {
779 // Don't override another popup, and only show in the active window.
780 if (delegate_->IsPopupRunning() || !browser_->window()->IsActive())
781 return false;
783 ToolbarActionViewController* action = GetActionForId(extension->id());
784 return action && action->ExecuteAction(grant_active_tab);
787 void ToolbarActionsBar::ToolbarVisibleCountChanged() {
788 ResizeDelegate(gfx::Tween::EASE_OUT, false);
789 SetOverflowedActionWantsToRun();
792 void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type,
793 bool suppress_chevron) {
794 int desired_width = GetPreferredSize().width();
795 if (desired_width != delegate_->GetWidth()) {
796 delegate_->ResizeAndAnimate(tween_type, desired_width, suppress_chevron);
797 } else if (delegate_->IsAnimating()) {
798 // It's possible that we're right where we're supposed to be in terms of
799 // width, but that we're also currently resizing. If this is the case, end
800 // the current animation with the current width.
801 delegate_->StopAnimating();
802 } else {
803 // We may already be at the right size (this can happen frequently with
804 // overflow, where we have a fixed width, and in tests, where we skip
805 // animations). If this is the case, we still need to Redraw(), because the
806 // icons within the toolbar may have changed (e.g. if we removed one
807 // action and added a different one in quick succession).
808 delegate_->Redraw(false);
812 void ToolbarActionsBar::ToolbarHighlightModeChanged(bool is_highlighting) {
813 // It's a bit of a pain that we delete and recreate everything here, but given
814 // everything else going on (the lack of highlight, [n] more extensions
815 // appearing, etc), it's not worth the extra complexity to create and insert
816 // only the new actions.
817 DeleteActions();
818 CreateActions();
819 // Resize the delegate. We suppress the chevron so that we don't risk showing
820 // it only for the duration of the animation.
821 ResizeDelegate(gfx::Tween::LINEAR, true);
824 void ToolbarActionsBar::OnToolbarModelInitialized() {
825 // We shouldn't have any actions before the model is initialized.
826 DCHECK(toolbar_actions_.empty());
827 CreateActions();
828 ResizeDelegate(gfx::Tween::EASE_OUT, false);
831 Browser* ToolbarActionsBar::GetBrowser() {
832 return browser_;
835 void ToolbarActionsBar::ReorderActions() {
836 if (toolbar_actions_.empty())
837 return;
839 // First, reset the order to that of the model.
840 auto compare = [](ToolbarActionViewController* const& action,
841 const scoped_refptr<const extensions::Extension>& ext) {
842 return action->GetId() == ext->id();
844 SortContainer(&toolbar_actions_.get(), model_->toolbar_items(), compare);
846 // Only adjust the order if the model isn't highlighting a particular
847 // subset (and the specialized tab order is enabled).
848 TabOrderHelper* tab_order_helper = in_overflow_mode() ?
849 main_bar_->tab_order_helper_.get() : tab_order_helper_.get();
850 if (!model_->is_highlighting() && tab_order_helper) {
851 WeakToolbarActions new_order =
852 tab_order_helper->GetActionOrder(GetCurrentWebContents());
853 auto compare = [](ToolbarActionViewController* const& first,
854 ToolbarActionViewController* const& second) {
855 return first->GetId() == second->GetId();
857 SortContainer(
858 &toolbar_actions_.get(), new_order, compare);
861 // Our visible browser actions may have changed - re-Layout() and check the
862 // size (if we aren't suppressing the layout).
863 if (!suppress_layout_) {
864 ResizeDelegate(gfx::Tween::EASE_OUT, false);
865 delegate_->Redraw(true);
868 SetOverflowedActionWantsToRun();
871 void ToolbarActionsBar::SetOverflowedActionWantsToRun() {
872 if (in_overflow_mode())
873 return;
874 bool overflowed_action_wants_to_run = false;
875 content::WebContents* web_contents = GetCurrentWebContents();
876 for (size_t i = GetIconCount(); i < toolbar_actions_.size(); ++i) {
877 if (toolbar_actions_[i]->WantsToRun(web_contents)) {
878 overflowed_action_wants_to_run = true;
879 break;
883 if (overflowed_action_wants_to_run_ != overflowed_action_wants_to_run) {
884 overflowed_action_wants_to_run_ = overflowed_action_wants_to_run;
885 if (send_overflowed_action_changes_)
886 delegate_->OnOverflowedActionWantsToRunChanged(
887 overflowed_action_wants_to_run_);
891 ToolbarActionViewController* ToolbarActionsBar::GetActionForId(
892 const std::string& id) {
893 for (ToolbarActionViewController* action : toolbar_actions_) {
894 if (action->GetId() == id)
895 return action;
897 return nullptr;
900 content::WebContents* ToolbarActionsBar::GetCurrentWebContents() {
901 return browser_->tab_strip_model()->GetActiveWebContents();