[go: up one dir, main page]

blob: ed4b060a1281f9fe77c71f011d6a314ee0a4e451 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
#include "base/auto_reset.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h"
#include "components/crx_file/id_util.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/runtime_data.h"
#include "extensions/common/extension.h"
#include "extensions/common/feature_switch.h"
#include "grit/theme_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"
namespace {
using WeakToolbarActions = std::vector<ToolbarActionViewController*>;
#if defined(OS_MACOSX)
const int kItemSpacing = 2;
const int kLeftPadding = kItemSpacing + 1;
const int kRightPadding = 0;
const int kOverflowLeftPadding = kItemSpacing;
const int kOverflowRightPadding = kItemSpacing;
#else
// Matches ToolbarView::kStandardSpacing;
const int kLeftPadding = 3;
const int kRightPadding = kLeftPadding;
const int kItemSpacing = kLeftPadding;
const int kOverflowLeftPadding = kItemSpacing;
const int kOverflowRightPadding = kItemSpacing;
#endif
enum DimensionType { WIDTH, HEIGHT };
// Returns the width or height of the toolbar action icon size.
int GetIconDimension(DimensionType type) {
static bool initialized = false;
static int icon_height = 0;
static int icon_width = 0;
if (!initialized) {
initialized = true;
gfx::ImageSkia* skia =
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_BROWSER_ACTION);
icon_height = skia->height();
icon_width = skia->width();
}
return type == WIDTH ? icon_width : icon_height;
}
// Takes a reference vector |reference| of length n, where n is less than or
// equal to the length of |to_sort|, and rearranges |to_sort| so that
// |to_sort|'s first n elements match the n elements of |reference| (the order
// of any remaining elements in |to_sort| is unspecified).
// |equal| is used to compare the elements of |to_sort| and |reference|.
// This allows us to sort a vector to match another vector of two different
// types without needing to construct a more cumbersome comparator class.
// |FunctionType| should equate to (something similar to)
// bool Equal(const Type1&, const Type2&), but we can't enforce this
// because of MSVC compilation limitations.
template<typename Type1, typename Type2, typename FunctionType>
void SortContainer(std::vector<Type1>* to_sort,
const std::vector<Type2>& reference,
FunctionType equal) {
DCHECK_GE(to_sort->size(), reference.size()) <<
"|to_sort| must contain all elements in |reference|.";
if (reference.empty())
return;
// Run through the each element and compare it to the reference. If something
// is out of place, find the correct spot for it.
for (size_t i = 0; i < reference.size() - 1; ++i) {
if (!equal(to_sort->at(i), reference[i])) {
// Find the correct index (it's guaranteed to be after our current
// index, since everything up to this point is correct), and swap.
size_t j = i + 1;
while (!equal(to_sort->at(j), reference[i])) {
++j;
DCHECK_LE(j, to_sort->size()) <<
"Item in |reference| not found in |to_sort|.";
}
std::swap(to_sort->at(i), to_sort->at(j));
}
}
}
} // namespace
// static
bool ToolbarActionsBar::disable_animations_for_testing_ = false;
// static
bool ToolbarActionsBar::pop_out_actions_to_run_ = false;
// static
bool ToolbarActionsBar::send_overflowed_action_changes_ = true;
// A class to implement an optional tab ordering that "pops out" actions that
// want to run on a particular page, as part of our experimentation with how to
// best signal that actions like extension page actions want to run.
// TODO(devlin): Once we finally settle on the right behavior, determine if
// we need this.
class ToolbarActionsBar::TabOrderHelper
: public TabStripModelObserver {
public:
TabOrderHelper(ToolbarActionsBar* toolbar,
Browser* browser,
extensions::ExtensionToolbarModel* model);
~TabOrderHelper() override;
// Returns the number of extra icons that should appear on the given |tab_id|
// because of actions that are going to pop out.
size_t GetExtraIconCount(int tab_id);
// Returns the item order of actions for the tab with the given
// |web_contents|.
WeakToolbarActions GetActionOrder(content::WebContents* web_contents);
// Notifies the TabOrderHelper that a given |action| does/doesn't want to run
// on the tab indicated by |tab_id|.
void SetActionWantsToRun(ToolbarActionViewController* action,
int tab_id,
bool wants_to_run);
// Notifies the TabOrderHelper that a given |action| has been removed.
void ActionRemoved(ToolbarActionViewController* action);
// Handles a resize, including updating any actions that want to act and
// updating the model.
void HandleResize(size_t resized_count, int tab_id);
// Handles a drag and drop, including updating any actions that want to act
// and updating the model.
void HandleDragDrop(int dragged,
int dropped,
ToolbarActionsBar::DragType drag_type,
int tab_id);
void notify_overflow_bar(ToolbarActionsBar* overflow_bar,
bool should_notify) {
// There's a possibility that a new overflow bar can be constructed before
// the first is fully destroyed. Only un-register the |overflow_bar_| if
// it's the one making the request.
if (should_notify)
overflow_bar_ = overflow_bar;
else if (overflow_bar_ == overflow_bar)
overflow_bar_ = nullptr;
}
private:
// TabStripModelObserver:
void TabInsertedAt(content::WebContents* web_contents,
int index,
bool foreground) override;
void TabDetachedAt(content::WebContents* web_contents, int index) override;
void ActiveTabChanged(content::WebContents* old_contents,
content::WebContents* new_contents,
int index,
int reason) override;
void TabStripModelDeleted() override;
// Notifies the main |toolbar_| and, if present, the |overflow_bar_| that
// actions need to be reordered.
void NotifyReorderActions();
// The set of tabs for the given action (the key) is currently "popped out".
// "Popped out" actions are those that were in the overflow menu normally, but
// want to run and are moved to the main bar so the user can see them.
std::map<ToolbarActionViewController*, std::set<int>> popped_out_in_tabs_;
// The set of tab ids that have been checked for whether actions need to be
// popped out or not.
std::set<int> tabs_checked_for_pop_out_;
// The owning ToolbarActionsBar.
ToolbarActionsBar* toolbar_;
// The overflow bar, if one is present.
ToolbarActionsBar* overflow_bar_;
// The associated toolbar model.
extensions::ExtensionToolbarModel* model_;
// A scoped tab strip observer so we can clean up |tabs_checked_for_popout_|.
ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_;
DISALLOW_COPY_AND_ASSIGN(TabOrderHelper);
};
ToolbarActionsBar::TabOrderHelper::TabOrderHelper(
ToolbarActionsBar* toolbar,
Browser* browser,
extensions::ExtensionToolbarModel* model)
: toolbar_(toolbar),
overflow_bar_(nullptr),
model_(model),
tab_strip_observer_(this) {
tab_strip_observer_.Add(browser->tab_strip_model());
}
ToolbarActionsBar::TabOrderHelper::~TabOrderHelper() {
}
size_t ToolbarActionsBar::TabOrderHelper::GetExtraIconCount(int tab_id) {
size_t extra_icons = 0;
const WeakToolbarActions& toolbar_actions = toolbar_->toolbar_actions();
for (ToolbarActionViewController* action : toolbar_actions) {
auto actions_tabs = popped_out_in_tabs_.find(action);
if (actions_tabs != popped_out_in_tabs_.end() &&
actions_tabs->second.count(tab_id))
++extra_icons;
}
return extra_icons;
}
void ToolbarActionsBar::TabOrderHelper::HandleResize(size_t resized_count,
int tab_id) {
int extra = GetExtraIconCount(tab_id);
size_t tab_icon_count = model_->visible_icon_count() + extra;
bool reorder_necessary = false;
const WeakToolbarActions& toolbar_actions = toolbar_->toolbar_actions();
if (resized_count < tab_icon_count) {
for (int i = resized_count; i < extra; ++i) {
// If an extension that was popped out to act is overflowed, then it
// should no longer be popped out, and it also doesn't count for adjusting
// the visible count (since it wasn't really out to begin with).
if (popped_out_in_tabs_[toolbar_actions[i]].count(tab_id)) {
reorder_necessary = true;
popped_out_in_tabs_[toolbar_actions[i]].erase(tab_id);
++(resized_count);
}
}
} else {
// If the user increases the toolbar size while actions that popped out are
// visible, we need to re-arrange the icons in other windows to be
// consistent with what the user sees.
// That is, if the normal order is A, B, [C, D] (with C and D hidden), C
// pops out to act, and then the user increases the size of the toolbar,
// the user sees uncovering D (since C is already out). This is what should
// happen in all windows.
for (size_t i = tab_icon_count; i < resized_count; ++i) {
if (toolbar_actions[i]->GetId() !=
model_->toolbar_items()[i - extra]->id())
model_->MoveExtensionIcon(toolbar_actions[i]->GetId(), i - extra);
}
}
resized_count -= extra;
model_->SetVisibleIconCount(resized_count);
if (reorder_necessary)
NotifyReorderActions();
}
void ToolbarActionsBar::TabOrderHelper::HandleDragDrop(
int dragged_index,
int dropped_index,
ToolbarActionsBar::DragType drag_type,
int tab_id) {
const WeakToolbarActions& toolbar_actions = toolbar_->toolbar_actions();
ToolbarActionViewController* action = toolbar_actions[dragged_index];
int delta = 0;
switch (drag_type) {
case ToolbarActionsBar::DRAG_TO_OVERFLOW:
// If the user moves an action back into overflow, then we don't adjust
// the base visible count, but do stop popping that action out.
if (popped_out_in_tabs_[action].count(tab_id))
popped_out_in_tabs_[action].erase(tab_id);
else
delta = -1;
break;
case ToolbarActionsBar::DRAG_TO_MAIN:
delta = 1;
break;
case ToolbarActionsBar::DRAG_TO_SAME:
// If the user moves an action that had popped out to be on the toolbar,
// then we treat it as "pinning" the action, and adjust the base visible
// count to accommodate.
if (popped_out_in_tabs_[action].count(tab_id)) {
delta = 1;
popped_out_in_tabs_[action].erase(tab_id);
}
break;
}
// If there are any actions that are in front of the dropped index only
// because they were popped out, decrement the dropped index.
for (int i = 0; i < dropped_index; ++i) {
if (i != dragged_index &&
model_->GetIndexForId(toolbar_actions[i]->GetId()) >= dropped_index)
--dropped_index;
}
model_->MoveExtensionIcon(action->GetId(), dropped_index);
if (delta)
model_->SetVisibleIconCount(model_->visible_icon_count() + delta);
}
void ToolbarActionsBar::TabOrderHelper::SetActionWantsToRun(
ToolbarActionViewController* action,
int tab_id,
bool wants_to_run) {
bool is_overflowed = model_->GetIndexForId(action->GetId()) >=
static_cast<int>(model_->visible_icon_count());
bool reorder_necessary = false;
if (wants_to_run && is_overflowed) {
popped_out_in_tabs_[action].insert(tab_id);
reorder_necessary = true;
} else if (!wants_to_run && popped_out_in_tabs_[action].count(tab_id)) {
popped_out_in_tabs_[action].erase(tab_id);
reorder_necessary = true;
}
if (reorder_necessary)
NotifyReorderActions();
}
void ToolbarActionsBar::TabOrderHelper::ActionRemoved(
ToolbarActionViewController* action) {
popped_out_in_tabs_.erase(action);
}
WeakToolbarActions ToolbarActionsBar::TabOrderHelper::GetActionOrder(
content::WebContents* web_contents) {
WeakToolbarActions toolbar_actions = toolbar_->toolbar_actions();
// First, make sure that we've checked any actions that want to run.
int tab_id = SessionTabHelper::IdForTab(web_contents);
if (!tabs_checked_for_pop_out_.count(tab_id)) {
tabs_checked_for_pop_out_.insert(tab_id);
for (ToolbarActionViewController* toolbar_action : toolbar_actions) {
if (toolbar_action->WantsToRun(web_contents))
popped_out_in_tabs_[toolbar_action].insert(tab_id);
}
}
// Then, shift any actions that want to run to the front.
size_t insert_at = 0;
// Rotate any actions that want to run to the boundary between visible and
// overflowed actions.
for (WeakToolbarActions::iterator iter =
toolbar_actions.begin() + model_->visible_icon_count();
iter != toolbar_actions.end(); ++iter) {
if (popped_out_in_tabs_[(*iter)].count(tab_id)) {
std::rotate(toolbar_actions.begin() + insert_at, iter, iter + 1);
++insert_at;
}
}
return toolbar_actions;
}
void ToolbarActionsBar::TabOrderHelper::TabInsertedAt(
content::WebContents* web_contents,
int index,
bool foreground) {
if (foreground)
NotifyReorderActions();
}
void ToolbarActionsBar::TabOrderHelper::TabDetachedAt(
content::WebContents* web_contents,
int index) {
int tab_id = SessionTabHelper::IdForTab(web_contents);
for (auto& tabs : popped_out_in_tabs_)
tabs.second.erase(tab_id);
tabs_checked_for_pop_out_.erase(tab_id);
}
void ToolbarActionsBar::TabOrderHelper::ActiveTabChanged(
content::WebContents* old_contents,
content::WebContents* new_contents,
int index,
int reason) {
// When we do a bulk-refresh by switching tabs, we don't animate the
// difference. We only animate when it's a change driven by the action or the
// user.
base::AutoReset<bool> animation_reset(&toolbar_->suppress_animation_, true);
NotifyReorderActions();
}
void ToolbarActionsBar::TabOrderHelper::TabStripModelDeleted() {
tab_strip_observer_.RemoveAll();
}
void ToolbarActionsBar::TabOrderHelper::NotifyReorderActions() {
// Reorder the reference toolbar first (since we use its actions in
// GetActionOrder()).
toolbar_->ReorderActions();
if (overflow_bar_)
overflow_bar_->ReorderActions();
}
ToolbarActionsBar::PlatformSettings::PlatformSettings(bool in_overflow_mode)
: left_padding(in_overflow_mode ? kOverflowLeftPadding : kLeftPadding),
right_padding(in_overflow_mode ? kOverflowRightPadding : kRightPadding),
item_spacing(kItemSpacing),
icons_per_overflow_menu_row(1),
chevron_enabled(!extensions::FeatureSwitch::extension_action_redesign()->
IsEnabled()) {
}
ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate* delegate,
Browser* browser,
ToolbarActionsBar* main_bar)
: delegate_(delegate),
browser_(browser),
model_(extensions::ExtensionToolbarModel::Get(browser_->profile())),
main_bar_(main_bar),
platform_settings_(main_bar != nullptr),
model_observer_(this),
suppress_layout_(false),
suppress_animation_(true),
overflowed_action_wants_to_run_(false) {
if (model_) // |model_| can be null in unittests.
model_observer_.Add(model_);
if (pop_out_actions_to_run_) {
if (in_overflow_mode())
main_bar_->tab_order_helper_->notify_overflow_bar(this, true);
else
tab_order_helper_.reset(new TabOrderHelper(this, browser_, model_));
}
}
ToolbarActionsBar::~ToolbarActionsBar() {
// We don't just call DeleteActions() here because it makes assumptions about
// the order of deletion between the views and the ToolbarActionsBar.
DCHECK(toolbar_actions_.empty()) <<
"Must call DeleteActions() before destruction.";
if (in_overflow_mode() && pop_out_actions_to_run_)
main_bar_->tab_order_helper_->notify_overflow_bar(this, false);
}
// static
int ToolbarActionsBar::IconWidth(bool include_padding) {
return GetIconDimension(WIDTH) + (include_padding ? kItemSpacing : 0);
}
// static
int ToolbarActionsBar::IconHeight() {
return GetIconDimension(HEIGHT);
}
gfx::Size ToolbarActionsBar::GetPreferredSize() const {
int icon_count = GetIconCount();
if (in_overflow_mode()) {
// In overflow, we always have a preferred size of a full row (even if we
// don't use it), and always of at least one row. The parent may decide to
// show us even when empty, e.g. as a drag target for dragging in icons from
// the main container.
int row_count = ((std::max(0, icon_count - 1)) /
platform_settings_.icons_per_overflow_menu_row) + 1;
return gfx::Size(
IconCountToWidth(platform_settings_.icons_per_overflow_menu_row),
row_count * IconHeight());
}
// If there are no actions to show (and this isn't an overflow container),
// then don't show the container at all.
if (toolbar_actions_.empty())
return gfx::Size();
return gfx::Size(IconCountToWidth(icon_count), IconHeight());
}
int ToolbarActionsBar::GetMinimumWidth() const {
if (!platform_settings_.chevron_enabled || toolbar_actions_.empty())
return kLeftPadding;
return kLeftPadding + delegate_->GetChevronWidth() + kRightPadding;
}
int ToolbarActionsBar::GetMaximumWidth() const {
return IconCountToWidth(-1);
}
int ToolbarActionsBar::IconCountToWidth(int icons) const {
if (icons < 0)
icons = toolbar_actions_.size();
bool display_chevron =
platform_settings_.chevron_enabled &&
static_cast<size_t>(icons) < toolbar_actions_.size();
if (icons == 0 && !display_chevron)
return platform_settings_.left_padding;
int icons_size = (icons == 0) ? 0 :
(icons * IconWidth(true)) - platform_settings_.item_spacing;
int chevron_size = display_chevron ? delegate_->GetChevronWidth() : 0;
int padding = platform_settings_.left_padding +
platform_settings_.right_padding;
return icons_size + chevron_size + padding;
}
size_t ToolbarActionsBar::WidthToIconCount(int pixels) const {
// Check for widths large enough to show the entire icon set.
if (pixels >= IconCountToWidth(-1))
return toolbar_actions_.size();
// We reserve space for the padding on either side of the toolbar...
int available_space = pixels -
(platform_settings_.left_padding + platform_settings_.right_padding);
// ... and, if the chevron is enabled, the chevron.
if (platform_settings_.chevron_enabled)
available_space -= delegate_->GetChevronWidth();
// Now we add an extra between-item padding value so the space can be divided
// evenly by (size of icon with padding).
return static_cast<size_t>(std::max(
0, available_space + platform_settings_.item_spacing) / IconWidth(true));
}
size_t ToolbarActionsBar::GetIconCount() const {
if (!model_)
return 0u;
size_t extra_icons = 0;
if (tab_order_helper_) {
extra_icons = tab_order_helper_->GetExtraIconCount(
SessionTabHelper::IdForTab(
browser_->tab_strip_model()->GetActiveWebContents()));
}
size_t visible_icons = in_overflow_mode() ?
toolbar_actions_.size() - main_bar_->GetIconCount() :
model_->visible_icon_count() + extra_icons;
#if DCHECK_IS_ON()
// Good time for some sanity checks: We should never try to display more
// icons than we have, and we should always have a view per item in the model.
// (The only exception is if this is in initialization.)
if (!toolbar_actions_.empty() && !suppress_layout_ &&
model_->extensions_initialized()) {
size_t num_extension_actions = 0u;
for (ToolbarActionViewController* action : toolbar_actions_) {
// No component action should ever have a valid extension id, so we can
// use this to check the extension amount.
// TODO(devlin): Fix this to just check model size when the model also
// includes component actions.
if (crx_file::id_util::IdIsValid(action->GetId()))
++num_extension_actions;
}
DCHECK_LE(visible_icons, num_extension_actions);
DCHECK_EQ(model_->toolbar_items().size(), num_extension_actions);
}
#endif
return visible_icons;
}
void ToolbarActionsBar::CreateActions() {
DCHECK(toolbar_actions_.empty());
// We wait for the extension system to be initialized before we add any
// actions, as they rely on the extension system to function.
if (!model_ || !model_->extensions_initialized())
return;
{
// We don't redraw the view while creating actions.
base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
// Extension actions come first.
extensions::ExtensionActionManager* action_manager =
extensions::ExtensionActionManager::Get(browser_->profile());
const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
for (const scoped_refptr<const extensions::Extension>& extension :
toolbar_items) {
toolbar_actions_.push_back(new ExtensionActionViewController(
extension.get(),
browser_,
action_manager->GetExtensionAction(*extension)));
}
// Component actions come second, and are suppressed if the extension
// actions are being highlighted.
if (!model_->is_highlighting()) {
ScopedVector<ToolbarActionViewController> component_actions =
ComponentToolbarActionsFactory::GetInstance()->
GetComponentToolbarActions();
DCHECK(component_actions.empty() ||
extensions::FeatureSwitch::extension_action_redesign()->IsEnabled());
toolbar_actions_.insert(toolbar_actions_.end(),
component_actions.begin(),
component_actions.end());
component_actions.weak_clear();
}
if (!toolbar_actions_.empty())
ReorderActions();
for (size_t i = 0; i < toolbar_actions_.size(); ++i)
delegate_->AddViewForAction(toolbar_actions_[i], i);
}
// Once the actions are created, we should animate the changes.
suppress_animation_ = false;
}
void ToolbarActionsBar::DeleteActions() {
delegate_->RemoveAllViews();
toolbar_actions_.clear();
}
void ToolbarActionsBar::Update() {
if (toolbar_actions_.empty())
return; // Nothing to do.
{
// Don't layout until the end.
base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
for (ToolbarActionViewController* action : toolbar_actions_)
action->UpdateState();
}
ReorderActions(); // Also triggers a draw.
}
void ToolbarActionsBar::SetOverflowRowWidth(int width) {
DCHECK(in_overflow_mode());
platform_settings_.icons_per_overflow_menu_row =
std::max((width - kItemSpacing) / IconWidth(true), 1);
}
void ToolbarActionsBar::OnResizeComplete(int width) {
DCHECK(!in_overflow_mode()); // The user can't resize the overflow container.
size_t resized_count = WidthToIconCount(width);
// Save off the desired number of visible icons. We do this now instead of
// at the end of the animation so that even if the browser is shut down
// while animating, the right value will be restored on next run.
if (tab_order_helper_) {
tab_order_helper_->HandleResize(
resized_count,
SessionTabHelper::IdForTab(GetCurrentWebContents()));
} else {
model_->SetVisibleIconCount(resized_count);
}
}
void ToolbarActionsBar::OnDragDrop(int dragged_index,
int dropped_index,
DragType drag_type) {
// All drag-and-drop commands should go to the main bar.
if (in_overflow_mode()) {
main_bar_->OnDragDrop(dragged_index, dropped_index, drag_type);
return;
}
if (tab_order_helper_) {
tab_order_helper_->HandleDragDrop(
dragged_index,
dropped_index,
drag_type,
SessionTabHelper::IdForTab(GetCurrentWebContents()));
} else {
int delta = 0;
if (drag_type == DRAG_TO_OVERFLOW)
delta = -1;
else if (drag_type == DRAG_TO_MAIN)
delta = 1;
model_->MoveExtensionIcon(toolbar_actions_[dragged_index]->GetId(),
dropped_index);
if (delta)
model_->SetVisibleIconCount(model_->visible_icon_count() + delta);
}
}
void ToolbarActionsBar::ToolbarExtensionAdded(
const extensions::Extension* extension,
int index) {
DCHECK(GetActionForId(extension->id()) == nullptr) <<
"Asked to add a toolbar action view for an extension that already exists";
toolbar_actions_.insert(
toolbar_actions_.begin() + index,
new ExtensionActionViewController(
extension,
browser_,
extensions::ExtensionActionManager::Get(browser_->profile())->
GetExtensionAction(*extension)));
delegate_->AddViewForAction(toolbar_actions_[index], index);
// If we are still initializing the container, don't bother animating.
if (!model_->extensions_initialized())
return;
// We may need to resize (e.g. to show the new icon, or the chevron). We don't
// need to check if the extension is upgrading here, because ResizeDelegate()
// checks to see if the container is already the proper size, and because
// if the action is newly incognito enabled, even though it's a reload, it's
// a new extension to this toolbar.
// We suppress the chevron during animation because, if we're expanding to
// show a new icon, we don't want to have the chevron visible only for the
// duration of the animation.
ResizeDelegate(gfx::Tween::LINEAR, true);
}
void ToolbarActionsBar::ToolbarExtensionRemoved(
const extensions::Extension* extension) {
ToolbarActions::iterator iter = toolbar_actions_.begin();
while (iter != toolbar_actions_.end() && (*iter)->GetId() != extension->id())
++iter;
if (iter == toolbar_actions_.end())
return;
// The action should outlive the UI element (which is owned by the delegate),
// so we can't delete it just yet. But we should remove it from the list of
// actions so that any width calculations are correct.
scoped_ptr<ToolbarActionViewController> removed_action(*iter);
toolbar_actions_.weak_erase(iter);
delegate_->RemoveViewForAction(removed_action.get());
if (tab_order_helper_)
tab_order_helper_->ActionRemoved(removed_action.get());
removed_action.reset();
// If the extension is being upgraded we don't want the bar to shrink
// because the icon is just going to get re-added to the same location.
// There is an exception if this is an off-the-record profile, and the
// extension is no longer incognito-enabled.
if (!extensions::ExtensionSystem::Get(browser_->profile())->runtime_data()->
IsBeingUpgraded(extension->id()) ||
(browser_->profile()->IsOffTheRecord() &&
!extensions::util::IsIncognitoEnabled(extension->id(),
browser_->profile()))) {
if (toolbar_actions_.size() > model_->visible_icon_count()) {
// If we have more icons than we can show, then we must not be changing
// the container size (since we either removed an icon from the main
// area and one from the overflow list will have shifted in, or we
// removed an entry directly from the overflow list).
delegate_->Redraw(false);
} else {
delegate_->SetChevronVisibility(false);
// Either we went from overflow to no-overflow, or we shrunk the no-
// overflow container by 1. Either way the size changed, so animate.
ResizeDelegate(gfx::Tween::EASE_OUT, false);
}
}
}
void ToolbarActionsBar::ToolbarExtensionMoved(
const extensions::Extension* extension,
int index) {
DCHECK(index >= 0 && index < static_cast<int>(toolbar_actions_.size()));
// Unfortunately, |index| doesn't really mean a lot to us, because this
// window's toolbar could be different (if actions are popped out). Just
// do a full reorder.
ReorderActions();
}
void ToolbarActionsBar::ToolbarExtensionUpdated(
const extensions::Extension* extension) {
ToolbarActionViewController* action = GetActionForId(extension->id());
// There might not be a view in cases where we are highlighting or if we
// haven't fully initialized the actions.
if (action) {
content::WebContents* web_contents = GetCurrentWebContents();
action->UpdateState();
if (tab_order_helper_) {
tab_order_helper_->SetActionWantsToRun(
action,
SessionTabHelper::IdForTab(web_contents),
action->WantsToRun(web_contents));
}
}
SetOverflowedActionWantsToRun();
}
bool ToolbarActionsBar::ShowExtensionActionPopup(
const extensions::Extension* extension,
bool grant_active_tab) {
// Don't override another popup, and only show in the active window.
if (delegate_->IsPopupRunning() || !browser_->window()->IsActive())
return false;
ToolbarActionViewController* action = GetActionForId(extension->id());
return action && action->ExecuteAction(grant_active_tab);
}
void ToolbarActionsBar::ToolbarVisibleCountChanged() {
ResizeDelegate(gfx::Tween::EASE_OUT, false);
SetOverflowedActionWantsToRun();
}
void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type,
bool suppress_chevron) {
int desired_width = GetPreferredSize().width();
if (desired_width != delegate_->GetWidth()) {
delegate_->ResizeAndAnimate(tween_type, desired_width, suppress_chevron);
} else if (delegate_->IsAnimating()) {
// It's possible that we're right where we're supposed to be in terms of
// width, but that we're also currently resizing. If this is the case, end
// the current animation with the current width.
delegate_->StopAnimating();
} else {
// We may already be at the right size (this can happen frequently with
// overflow, where we have a fixed width, and in tests, where we skip
// animations). If this is the case, we still need to Redraw(), because the
// icons within the toolbar may have changed (e.g. if we removed one
// action and added a different one in quick succession).
delegate_->Redraw(false);
}
}
void ToolbarActionsBar::ToolbarHighlightModeChanged(bool is_highlighting) {
// It's a bit of a pain that we delete and recreate everything here, but given
// everything else going on (the lack of highlight, [n] more extensions
// appearing, etc), it's not worth the extra complexity to create and insert
// only the new actions.
DeleteActions();
CreateActions();
// Resize the delegate. We suppress the chevron so that we don't risk showing
// it only for the duration of the animation.
ResizeDelegate(gfx::Tween::LINEAR, true);
}
void ToolbarActionsBar::OnToolbarModelInitialized() {
// We shouldn't have any actions before the model is initialized.
DCHECK(toolbar_actions_.empty());
CreateActions();
ResizeDelegate(gfx::Tween::EASE_OUT, false);
}
Browser* ToolbarActionsBar::GetBrowser() {
return browser_;
}
void ToolbarActionsBar::ReorderActions() {
if (toolbar_actions_.empty())
return;
// First, reset the order to that of the model.
auto compare = [](ToolbarActionViewController* const& action,
const scoped_refptr<const extensions::Extension>& ext) {
return action->GetId() == ext->id();
};
SortContainer(&toolbar_actions_.get(), model_->toolbar_items(), compare);
// Only adjust the order if the model isn't highlighting a particular
// subset (and the specialized tab order is enabled).
TabOrderHelper* tab_order_helper = in_overflow_mode() ?
main_bar_->tab_order_helper_.get() : tab_order_helper_.get();
if (!model_->is_highlighting() && tab_order_helper) {
WeakToolbarActions new_order =
tab_order_helper->GetActionOrder(GetCurrentWebContents());
auto compare = [](ToolbarActionViewController* const& first,
ToolbarActionViewController* const& second) {
return first->GetId() == second->GetId();
};
SortContainer(
&toolbar_actions_.get(), new_order, compare);
}
// Our visible browser actions may have changed - re-Layout() and check the
// size (if we aren't suppressing the layout).
if (!suppress_layout_) {
ResizeDelegate(gfx::Tween::EASE_OUT, false);
delegate_->Redraw(true);
}
SetOverflowedActionWantsToRun();
}
void ToolbarActionsBar::SetOverflowedActionWantsToRun() {
if (in_overflow_mode())
return;
bool overflowed_action_wants_to_run = false;
content::WebContents* web_contents = GetCurrentWebContents();
for (size_t i = GetIconCount(); i < toolbar_actions_.size(); ++i) {
if (toolbar_actions_[i]->WantsToRun(web_contents)) {
overflowed_action_wants_to_run = true;
break;
}
}
if (overflowed_action_wants_to_run_ != overflowed_action_wants_to_run) {
overflowed_action_wants_to_run_ = overflowed_action_wants_to_run;
if (send_overflowed_action_changes_)
delegate_->OnOverflowedActionWantsToRunChanged(
overflowed_action_wants_to_run_);
}
}
ToolbarActionViewController* ToolbarActionsBar::GetActionForId(
const std::string& id) {
for (ToolbarActionViewController* action : toolbar_actions_) {
if (action->GetId() == id)
return action;
}
return nullptr;
}
content::WebContents* ToolbarActionsBar::GetCurrentWebContents() {
return browser_->tab_strip_model()->GetActiveWebContents();
}