blob: 0aaa2e7367f1919457aea4e49ae960e412c2e42b [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 <algorithm>
#include <set>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/numerics/ranges.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/extension_message_bubble_controller.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/extensions/extension_message_bubble_bridge.h"
#include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar_observer.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "components/crx_file/id_util.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/runtime_data.h"
#include "extensions/common/extension.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/image/image_skia.h"
namespace {
using WeakToolbarActions = std::vector<ToolbarActionViewController*>;
enum DimensionType { WIDTH, 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<std::unique_ptr<Type1>>* to_sort,
const std::vector<Type2>& reference,
FunctionType equal) {
CHECK_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).get(), 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).get(), reference[i])) {
++j;
DCHECK_LT(j, to_sort->size())
<< "Item in |reference| not found in |to_sort|.";
}
std::swap(to_sort->at(i), to_sort->at(j));
}
}
}
// How long to wait until showing an extension message bubble.
int g_extension_bubble_appearance_wait_time_in_seconds = 5;
} // namespace
// static
bool ToolbarActionsBar::disable_animations_for_testing_ = false;
ToolbarActionsBar::PlatformSettings::PlatformSettings()
: item_spacing(GetLayoutConstant(TOOLBAR_STANDARD_SPACING)),
icons_per_overflow_menu_row(1) {}
ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate* delegate,
Browser* browser,
ToolbarActionsBar* main_bar)
: delegate_(delegate),
browser_(browser),
model_(ToolbarActionsModel::Get(browser_->profile())),
main_bar_(main_bar),
platform_settings_(),
popup_owner_(nullptr),
model_observer_(this),
suppress_layout_(false),
suppress_animation_(true),
should_check_extension_bubble_(!main_bar),
popped_out_action_(nullptr),
is_popped_out_sticky_(false),
is_showing_bubble_(false),
tab_strip_observer_(this),
weak_ptr_factory_(this) {
if (model_) // |model_| can be null in unittests.
model_observer_.Add(model_);
if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu))
DCHECK(!in_overflow_mode());
tab_strip_observer_.Add(browser_->tab_strip_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.";
for (ToolbarActionsBarObserver& observer : observers_)
observer.OnToolbarActionsBarDestroyed();
}
// static
void ToolbarActionsBar::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(
prefs::kToolbarIconSurfacingBubbleAcknowledged, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterInt64Pref(prefs::kToolbarIconSurfacingBubbleLastShowTime,
0);
}
// static
gfx::Size ToolbarActionsBar::GetIconAreaSize() {
return gfx::Size(28, 28);
}
gfx::Size ToolbarActionsBar::GetViewSize() const {
gfx::Rect rect(GetIconAreaSize());
rect.Inset(-GetIconAreaInsets());
return rect.size();
}
gfx::Size ToolbarActionsBar::GetFullSize() const {
// 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() && !in_overflow_mode())
return gfx::Size();
int num_icons = GetIconCount();
int num_rows = 1;
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.
num_icons = platform_settings_.icons_per_overflow_menu_row;
const int icon_count = GetEndIndexInBounds() - GetStartIndexInBounds();
num_rows += (std::max(0, icon_count - 1) / num_icons);
}
return gfx::Size(IconCountToWidth(num_icons), IconCountToWidth(num_rows));
}
int ToolbarActionsBar::GetMinimumWidth() const {
return platform_settings_.item_spacing;
}
int ToolbarActionsBar::GetMaximumWidth() const {
return IconCountToWidth(toolbar_actions_.size());
}
int ToolbarActionsBar::IconCountToWidth(size_t icons) const {
if (icons == 0)
return 0;
return icons * GetViewSize().width() +
(icons - 1) * GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
}
size_t ToolbarActionsBar::WidthToIconCountUnclamped(int pixels) const {
const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
return std::max(
(pixels + element_padding) / (GetViewSize().width() + element_padding),
0);
}
size_t ToolbarActionsBar::WidthToIconCount(int pixels) const {
return std::min(WidthToIconCountUnclamped(pixels), toolbar_actions_.size());
}
size_t ToolbarActionsBar::GetIconCount() const {
if (!model_)
return 0;
int pop_out_modifier = 0;
// If there is a popped out action, it could affect the number of visible
// icons - but only if it wouldn't otherwise be visible.
if (popped_out_action_) {
size_t popped_out_index = 0;
for (; popped_out_index < toolbar_actions_.size(); ++popped_out_index) {
if (toolbar_actions_[popped_out_index].get() == popped_out_action_)
break;
}
pop_out_modifier = popped_out_index >= model_->visible_icon_count() ? 1 : 0;
}
// We purposefully do not account for any "popped out" actions in overflow
// mode. This is because the popup cannot be showing while the overflow menu
// is open, so there's no concern there. Also, if the user has a popped out
// action, and immediately opens the overflow menu, we *want* the action there
// (since it will close the popup, but do so asynchronously, and we don't
// want to "slide" the action back in.
size_t visible_icons =
in_overflow_mode()
? toolbar_actions_.size() - model_->visible_icon_count()
: model_->visible_icon_count() + pop_out_modifier;
#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_->actions_initialized()) {
DCHECK_LE(visible_icons, toolbar_actions_.size());
DCHECK_EQ(model_->action_ids().size(), toolbar_actions_.size());
}
#endif
return visible_icons;
}
size_t ToolbarActionsBar::GetStartIndexInBounds() const {
return in_overflow_mode() ? main_bar_->GetEndIndexInBounds() : 0;
}
size_t ToolbarActionsBar::GetEndIndexInBounds() const {
// The end index for the main bar is however many icons can fit with the given
// width. We take the width-after-animation here so that we don't have to
// constantly adjust both this and the overflow as the size changes (the
// animations are small and fast enough that this doesn't cause problems).
return in_overflow_mode()
? toolbar_actions_.size()
: WidthToIconCount(delegate_->GetWidth(
ToolbarActionsBarDelegate::GET_WIDTH_AFTER_ANIMATION));
}
bool ToolbarActionsBar::NeedsOverflow() const {
DCHECK(!in_overflow_mode());
// We need an overflow view if either the end index is less than the number of
// icons, if a drag is in progress with the redesign turned on (since the
// user can drag an icon into the app menu), or if there is a non-sticky
// popped out action (because the action will pop back into overflow when the
// menu opens).
return GetEndIndexInBounds() != toolbar_actions_.size() ||
is_drag_in_progress() ||
(popped_out_action_ && !is_popped_out_sticky_);
}
gfx::Rect ToolbarActionsBar::GetFrameForIndex(size_t index) const {
size_t start_index = GetStartIndexInBounds();
// If the index is for an action that is before range we show (i.e., is for
// a button that's on the main bar, and this is the overflow), send back an
// empty rect.
if (index < start_index)
return gfx::Rect();
const size_t relative_index = index - start_index;
const int icons_per_overflow_row =
platform_settings().icons_per_overflow_menu_row;
const size_t row_index =
in_overflow_mode() ? relative_index / icons_per_overflow_row : 0;
const size_t index_in_row = in_overflow_mode()
? relative_index % icons_per_overflow_row
: relative_index;
const auto size = GetViewSize();
const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
return gfx::Rect(gfx::Point(index_in_row * (size.width() + element_padding),
row_index * (size.height() + element_padding)),
size);
}
std::vector<ToolbarActionViewController*> ToolbarActionsBar::GetActions()
const {
std::vector<ToolbarActionViewController*> actions;
for (const auto& action : toolbar_actions_)
actions.push_back(action.get());
// If there is an action that should be popped out, and it's not visible by
// default, make it the final action in the list.
if (popped_out_action_) {
size_t index =
std::find(actions.begin(), actions.end(), popped_out_action_) -
actions.begin();
DCHECK_NE(actions.size(), index);
size_t visible = GetIconCount();
if (index >= visible) {
size_t rindex = actions.size() - index - 1;
std::rotate(actions.rbegin() + rindex, actions.rbegin() + rindex + 1,
actions.rend() - visible + 1);
}
}
return actions;
}
void ToolbarActionsBar::CreateActions() {
CHECK(toolbar_actions_.empty());
// If the model isn't initialized, wait for it.
if (!model_ || !model_->actions_initialized())
return;
{
// We don't redraw the view while creating actions.
base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
// Get the toolbar actions.
toolbar_actions_ =
model_->CreateActions(browser_, GetMainBar(), in_overflow_mode());
if (!toolbar_actions_.empty())
ReorderActions();
for (size_t i = 0; i < toolbar_actions_.size(); ++i)
delegate_->AddViewForAction(toolbar_actions_[i].get(), i);
}
// Once the actions are created, we should animate the changes.
suppress_animation_ = false;
// CreateActions() can be called multiple times, so we need to make sure we
// haven't already shown the bubble.
// Extension bubbles can also highlight a subset of actions, so don't show the
// bubble if the toolbar is already highlighting a different set.
if (should_check_extension_bubble_ && !is_highlighting()) {
should_check_extension_bubble_ = false;
// CreateActions() can be called as part of the browser window set up, which
// we need to let finish before showing the actions.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ToolbarActionsBar::MaybeShowExtensionBubble,
weak_ptr_factory_.GetWeakPtr()));
}
}
void ToolbarActionsBar::DeleteActions() {
HideActivePopup();
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 (const auto& action : toolbar_actions_)
action->UpdateState();
}
ReorderActions(); // Also triggers a draw.
}
bool ToolbarActionsBar::ShowToolbarActionPopup(const std::string& action_id,
bool grant_active_tab) {
// Don't override another popup, and only show in the active window.
if (popup_owner() || !browser_->window()->IsActive())
return false;
ToolbarActionViewController* action = GetActionForId(action_id);
return action && action->ExecuteAction(grant_active_tab);
}
void ToolbarActionsBar::SetOverflowRowWidth(int width) {
DCHECK(in_overflow_mode());
// This uses the unclamped icon count to allow the in-menu bar to span the
// menu width.
platform_settings_.icons_per_overflow_menu_row =
std::max(WidthToIconCountUnclamped(width), static_cast<size_t>(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.
model_->SetVisibleIconCount(resized_count);
}
void ToolbarActionsBar::OnDragStarted(size_t index_of_dragged_item) {
if (in_overflow_mode()) {
main_bar_->OnDragStarted(index_of_dragged_item);
return;
}
DCHECK(!is_drag_in_progress());
index_of_dragged_item_ = index_of_dragged_item;
}
void ToolbarActionsBar::OnDragEnded() {
// All drag-and-drop commands should go to the main bar.
if (in_overflow_mode()) {
main_bar_->OnDragEnded();
return;
}
DCHECK(is_drag_in_progress());
index_of_dragged_item_.reset();
for (ToolbarActionsBarObserver& observer : observers_)
observer.OnToolbarActionDragDone();
}
void ToolbarActionsBar::OnDragDrop(int dragged_index,
int dropped_index,
DragType drag_type) {
if (in_overflow_mode()) {
// All drag-and-drop commands should go to the main bar.
main_bar_->OnDragDrop(dragged_index, dropped_index, drag_type);
return;
}
int delta = 0;
if (drag_type == DRAG_TO_OVERFLOW)
delta = -1;
else if (drag_type == DRAG_TO_MAIN &&
dragged_index >= static_cast<int>(model_->visible_icon_count()))
delta = 1;
model_->MoveActionIcon(toolbar_actions_[dragged_index]->GetId(),
dropped_index);
if (delta)
model_->SetVisibleIconCount(model_->visible_icon_count() + delta);
}
const base::Optional<size_t> ToolbarActionsBar::IndexOfDraggedItem() const {
DCHECK(!in_overflow_mode());
return index_of_dragged_item_;
}
void ToolbarActionsBar::OnAnimationEnded() {
// Notify the observers now, since showing a bubble or popup could potentially
// cause another animation to start.
for (ToolbarActionsBarObserver& observer : observers_)
observer.OnToolbarActionsBarAnimationEnded();
// Check if we were waiting for animation to complete to either show a
// message bubble, or to show a popup.
if (pending_bubble_controller_) {
ShowToolbarActionBubble(std::move(pending_bubble_controller_));
} else if (!popped_out_closure_.is_null()) {
popped_out_closure_.Run();
popped_out_closure_.Reset();
}
}
void ToolbarActionsBar::OnBubbleClosed() {
is_showing_bubble_ = false;
}
bool ToolbarActionsBar::IsActionVisibleOnToolbar(
const ToolbarActionViewController* action) const {
if (in_overflow_mode())
return main_bar_->IsActionVisibleOnToolbar(action);
if (action == popped_out_action_)
return true;
size_t visible_icon_count = std::min(toolbar_actions_.size(), GetIconCount());
for (size_t index = 0; index < visible_icon_count; ++index)
if (toolbar_actions_[index].get() == action)
return true;
return false;
}
void ToolbarActionsBar::PopOutAction(ToolbarActionViewController* controller,
bool is_sticky,
const base::Closure& closure) {
DCHECK(!in_overflow_mode()) << "Only the main bar can pop out actions.";
DCHECK(!popped_out_action_) << "Only one action can be popped out at a time!";
bool needs_redraw = !IsActionVisibleOnToolbar(controller);
popped_out_action_ = controller;
is_popped_out_sticky_ = is_sticky;
if (needs_redraw) {
// We suppress animation for this draw, because we need the action to get
// into position immediately, since it's about to show its popup.
base::AutoReset<bool> layout_resetter(&suppress_animation_, false);
delegate_->Redraw(true);
}
ResizeDelegate(gfx::Tween::LINEAR);
if (!delegate_->IsAnimating()) {
// Don't call the closure re-entrantly.
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, closure);
} else {
popped_out_closure_ = closure;
}
}
ToolbarActionViewController* ToolbarActionsBar::GetPoppedOutAction() const {
return popped_out_action_;
}
void ToolbarActionsBar::UndoPopOut() {
DCHECK(!in_overflow_mode()) << "Only the main bar can pop out actions.";
DCHECK(popped_out_action_);
ToolbarActionViewController* controller = popped_out_action_;
popped_out_action_ = nullptr;
is_popped_out_sticky_ = false;
popped_out_closure_.Reset();
if (!IsActionVisibleOnToolbar(controller))
delegate_->Redraw(true);
ResizeDelegate(gfx::Tween::LINEAR);
}
void ToolbarActionsBar::SetPopupOwner(
ToolbarActionViewController* popup_owner) {
// We should never be setting a popup owner when one already exists, and
// never unsetting one when one wasn't set.
DCHECK((!popup_owner_ && popup_owner) || (popup_owner_ && !popup_owner));
popup_owner_ = popup_owner;
}
void ToolbarActionsBar::HideActivePopup() {
if (popup_owner_)
popup_owner_->HidePopup();
DCHECK(!popup_owner_);
}
void ToolbarActionsBar::AddObserver(ToolbarActionsBarObserver* observer) {
observers_.AddObserver(observer);
}
void ToolbarActionsBar::RemoveObserver(ToolbarActionsBarObserver* observer) {
observers_.RemoveObserver(observer);
}
void ToolbarActionsBar::ShowToolbarActionBubble(
std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) {
DCHECK(!in_overflow_mode());
if (delegate_->IsAnimating()) {
// If the toolbar is animating, we can't effectively anchor the bubble,
// so wait until animation stops.
pending_bubble_controller_ = std::move(bubble);
} else if (bubble->ShouldShow()) {
// We check ShouldShow() above since we show the bubble asynchronously, and
// it might no longer have been valid.
// If needed, close the overflow menu before showing the bubble.
ToolbarActionViewController* controller =
GetActionForId(bubble->GetAnchorActionId());
bool close_overflow_menu =
controller && !IsActionVisibleOnToolbar(controller);
if (close_overflow_menu)
delegate_->CloseOverflowMenuIfOpen();
is_showing_bubble_ = true;
delegate_->ShowToolbarActionBubble(std::move(bubble));
}
}
void ToolbarActionsBar::ShowToolbarActionBubbleAsync(
std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ToolbarActionsBar::ShowToolbarActionBubble,
weak_ptr_factory_.GetWeakPtr(), std::move(bubble)));
}
bool ToolbarActionsBar::CloseOverflowMenuIfOpen() {
return delegate_->CloseOverflowMenuIfOpen();
}
void ToolbarActionsBar::MaybeShowExtensionBubble() {
std::unique_ptr<extensions::ExtensionMessageBubbleController> controller =
model_->GetExtensionMessageBubbleController(browser_);
if (!controller)
return;
DCHECK(controller->ShouldShow());
controller->HighlightExtensionsIfNecessary(); // Safe to call multiple times.
// Not showing the bubble right away (during startup) has a few benefits:
// We don't have to worry about focus being lost due to the Omnibox (or to
// other things that want focus at startup). This allows Esc to work to close
// the bubble and also solves the keyboard accessibility problem that comes
// with focus being lost (we don't have a good generic mechanism of injecting
// bubbles into the focus cycle). Another benefit of delaying the show is
// that fade-in works (the fade-in isn't apparent if the the bubble appears at
// startup).
std::unique_ptr<ToolbarActionsBarBubbleDelegate> delegate(
new ExtensionMessageBubbleBridge(std::move(controller)));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ToolbarActionsBar::ShowToolbarActionBubble,
weak_ptr_factory_.GetWeakPtr(), std::move(delegate)),
base::TimeDelta::FromSeconds(
g_extension_bubble_appearance_wait_time_in_seconds));
}
ToolbarActionsBar* ToolbarActionsBar::GetMainBar() {
return main_bar_ ? main_bar_ : this;
}
// static
void ToolbarActionsBar::set_extension_bubble_appearance_wait_time_for_testing(
int time_in_seconds) {
g_extension_bubble_appearance_wait_time_in_seconds = time_in_seconds;
}
gfx::Insets ToolbarActionsBar::GetIconAreaInsets() const {
return GetLayoutInsets(TOOLBAR_ACTION_VIEW);
}
void ToolbarActionsBar::OnToolbarActionAdded(
const ToolbarActionsModel::ActionId& action_id,
int index) {
CHECK(model_->actions_initialized());
CHECK(GetActionForId(action_id) == nullptr)
<< "Asked to add a toolbar action view for an action that already "
"exists";
toolbar_actions_.insert(
toolbar_actions_.begin() + index,
model_->CreateActionForId(browser_, GetMainBar(), in_overflow_mode(),
action_id));
delegate_->AddViewForAction(toolbar_actions_[index].get(), index);
// We may need to resize (e.g. to show the new icon). We don't need to check
// if an 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.
ResizeDelegate(gfx::Tween::LINEAR);
}
void ToolbarActionsBar::OnToolbarActionLoadFailed() {
// When an extension is re-uploaded, it is first unloaded from Chrome. At this
// point, the extension's icon is initially removed from the toolbar, leaving
// an empty slot in the toolbar. Then the (newer version of the) extension is
// loaded, and its icon populates the empty slot.
//
// If the extension failed to load, then the empty slot should be removed and
// hence we resize the toolbar.
ResizeDelegate(gfx::Tween::EASE_OUT);
}
void ToolbarActionsBar::OnToolbarActionRemoved(
const ToolbarActionsModel::ActionId& action_id) {
auto iter = toolbar_actions_.begin();
while (iter != toolbar_actions_.end() && (*iter)->GetId() != action_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.
std::unique_ptr<ToolbarActionViewController> removed_action =
std::move(*iter);
toolbar_actions_.erase(iter);
// If we kill the view before we undo the popout, highlights and pop-ups can
// get left in weird states, so undo the popout first.
if (popped_out_action_ == removed_action.get())
UndoPopOut();
delegate_->RemoveViewForAction(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(action_id) ||
(browser_->profile()->IsOffTheRecord() &&
!extensions::util::IsIncognitoEnabled(action_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 {
// 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);
}
}
}
void ToolbarActionsBar::OnToolbarActionMoved(
const ToolbarActionsModel::ActionId& action_id,
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::OnToolbarActionUpdated(
const ToolbarActionsModel::ActionId& action_id) {
ToolbarActionViewController* action = GetActionForId(action_id);
// There might not be a view in cases where we are highlighting or if we
// haven't fully initialized the actions.
if (action)
action->UpdateState();
}
void ToolbarActionsBar::OnToolbarVisibleCountChanged() {
ResizeDelegate(gfx::Tween::EASE_OUT);
}
void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type) {
int desired_width = GetFullSize().width();
if (desired_width !=
delegate_->GetWidth(ToolbarActionsBarDelegate::GET_WIDTH_CURRENT)) {
delegate_->ResizeAndAnimate(tween_type, desired_width);
} 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::OnToolbarHighlightModeChanged(bool is_highlighting) {
if (!model_->actions_initialized())
return;
{
base::AutoReset<bool> layout_resetter(&suppress_layout_, true);
base::AutoReset<bool> animation_resetter(&suppress_animation_, true);
std::set<std::string> model_action_ids;
for (const auto& model_action_id : model_->action_ids()) {
model_action_ids.insert(model_action_id);
bool found = false;
for (size_t i = 0; i < toolbar_actions_.size(); ++i) {
if (toolbar_actions_[i]->GetId() == model_action_id) {
found = true;
break;
}
}
if (!found) {
toolbar_actions_.push_back(model_->CreateActionForId(
browser_, GetMainBar(), in_overflow_mode(), model_action_id));
delegate_->AddViewForAction(toolbar_actions_.back().get(),
toolbar_actions_.size() - 1);
}
}
for (auto iter = toolbar_actions_.begin();
iter != toolbar_actions_.end();) {
if (model_action_ids.count((*iter)->GetId()) == 0) {
delegate_->RemoveViewForAction(iter->get());
iter = toolbar_actions_.erase(iter);
} else {
++iter;
}
}
}
ReorderActions();
}
void ToolbarActionsBar::OnToolbarModelInitialized() {
// We shouldn't have any actions before the model is initialized.
CHECK(toolbar_actions_.empty());
CreateActions();
ResizeDelegate(gfx::Tween::EASE_OUT);
}
void ToolbarActionsBar::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (tab_strip_model->empty() || !selection.active_tab_changed())
return;
extensions::MaybeShowExtensionControlledNewTabPage(browser_,
selection.new_contents);
}
void ToolbarActionsBar::ReorderActions() {
if (toolbar_actions_.empty())
return;
// First, reset the order to that of the model.
auto compare = [](ToolbarActionViewController* const& action,
const ToolbarActionsModel::ActionId& action_id) {
return action->GetId() == action_id;
};
SortContainer(&toolbar_actions_, model_->action_ids(), 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);
delegate_->Redraw(true);
}
}
ToolbarActionViewController* ToolbarActionsBar::GetActionForId(
const std::string& action_id) {
for (const auto& action : toolbar_actions_) {
if (action->GetId() == action_id)
return action.get();
}
return nullptr;
}
content::WebContents* ToolbarActionsBar::GetCurrentWebContents() {
return browser_->tab_strip_model()->GetActiveWebContents();
}