Home | History | Annotate | Download | only in toolbar
      1 // Copyright 2013 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.
      4 
      5 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
      6 
      7 #include "base/compiler_specific.h"
      8 #include "base/stl_util.h"
      9 #include "chrome/browser/extensions/extension_action_manager.h"
     10 #include "chrome/browser/extensions/extension_util.h"
     11 #include "chrome/browser/extensions/extension_view_host.h"
     12 #include "chrome/browser/extensions/tab_helper.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/ui/browser.h"
     15 #include "chrome/browser/ui/browser_window.h"
     16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     17 #include "chrome/browser/ui/view_ids.h"
     18 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
     19 #include "chrome/browser/ui/views/extensions/extension_popup.h"
     20 #include "chrome/browser/ui/views/frame/browser_view.h"
     21 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
     22 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
     23 #include "chrome/common/extensions/command.h"
     24 #include "chrome/grit/generated_resources.h"
     25 #include "extensions/browser/extension_registry.h"
     26 #include "extensions/browser/extension_system.h"
     27 #include "extensions/browser/runtime_data.h"
     28 #include "extensions/common/feature_switch.h"
     29 #include "grit/theme_resources.h"
     30 #include "third_party/skia/include/core/SkColor.h"
     31 #include "ui/accessibility/ax_view_state.h"
     32 #include "ui/base/dragdrop/drag_utils.h"
     33 #include "ui/base/l10n/l10n_util.h"
     34 #include "ui/base/nine_image_painter_factory.h"
     35 #include "ui/base/resource/resource_bundle.h"
     36 #include "ui/base/theme_provider.h"
     37 #include "ui/gfx/animation/slide_animation.h"
     38 #include "ui/gfx/canvas.h"
     39 #include "ui/gfx/geometry/rect.h"
     40 #include "ui/resources/grit/ui_resources.h"
     41 #include "ui/views/controls/button/label_button_border.h"
     42 #include "ui/views/controls/button/menu_button.h"
     43 #include "ui/views/controls/resize_area.h"
     44 #include "ui/views/metrics.h"
     45 #include "ui/views/painter.h"
     46 #include "ui/views/widget/widget.h"
     47 
     48 using extensions::Extension;
     49 
     50 namespace {
     51 
     52 // Horizontal spacing before the chevron (if visible).
     53 const int kChevronSpacing = ToolbarView::kStandardSpacing - 2;
     54 
     55 // A version of MenuButton with almost empty insets to fit properly on the
     56 // toolbar.
     57 class ChevronMenuButton : public views::MenuButton {
     58  public:
     59   ChevronMenuButton(views::ButtonListener* listener,
     60                     const base::string16& text,
     61                     views::MenuButtonListener* menu_button_listener,
     62                     bool show_menu_marker)
     63       : views::MenuButton(listener,
     64                           text,
     65                           menu_button_listener,
     66                           show_menu_marker) {
     67   }
     68 
     69   virtual ~ChevronMenuButton() {}
     70 
     71   virtual scoped_ptr<views::LabelButtonBorder> CreateDefaultBorder() const
     72       OVERRIDE {
     73     // The chevron resource was designed to not have any insets.
     74     scoped_ptr<views::LabelButtonBorder> border =
     75         views::MenuButton::CreateDefaultBorder();
     76     border->set_insets(gfx::Insets());
     77     return border.Pass();
     78   }
     79 
     80  private:
     81   DISALLOW_COPY_AND_ASSIGN(ChevronMenuButton);
     82 };
     83 
     84 }  // namespace
     85 
     86 ////////////////////////////////////////////////////////////////////////////////
     87 // BrowserActionsContainer::DropPosition
     88 
     89 struct BrowserActionsContainer::DropPosition {
     90   DropPosition(size_t row, size_t icon_in_row);
     91 
     92   // The (0-indexed) row into which the action will be dropped.
     93   size_t row;
     94 
     95   // The (0-indexed) icon in the row before the action will be dropped.
     96   size_t icon_in_row;
     97 };
     98 
     99 BrowserActionsContainer::DropPosition::DropPosition(
    100     size_t row, size_t icon_in_row)
    101     : row(row), icon_in_row(icon_in_row) {
    102 }
    103 
    104 ////////////////////////////////////////////////////////////////////////////////
    105 // BrowserActionsContainer
    106 
    107 // static
    108 int BrowserActionsContainer::icons_per_overflow_menu_row_ = 1;
    109 
    110 // static
    111 const int BrowserActionsContainer::kItemSpacing = ToolbarView::kStandardSpacing;
    112 
    113 // static
    114 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
    115 
    116 BrowserActionsContainer::BrowserActionsContainer(
    117     Browser* browser,
    118     View* owner_view,
    119     BrowserActionsContainer* main_container)
    120     : initialized_(false),
    121       profile_(browser->profile()),
    122       browser_(browser),
    123       owner_view_(owner_view),
    124       main_container_(main_container),
    125       popup_owner_(NULL),
    126       model_(NULL),
    127       container_width_(0),
    128       resize_area_(NULL),
    129       chevron_(NULL),
    130       overflow_menu_(NULL),
    131       suppress_chevron_(false),
    132       resize_amount_(0),
    133       animation_target_size_(0),
    134       show_menu_task_factory_(this) {
    135   set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
    136 
    137   model_ = extensions::ExtensionToolbarModel::Get(browser->profile());
    138   if (model_)
    139     model_->AddObserver(this);
    140 
    141   bool overflow_experiment =
    142       extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
    143   DCHECK(!in_overflow_mode() || overflow_experiment);
    144 
    145   if (!in_overflow_mode()) {
    146     extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
    147         browser->profile(),
    148         owner_view->GetFocusManager(),
    149         extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
    150         this));
    151 
    152     resize_animation_.reset(new gfx::SlideAnimation(this));
    153     resize_area_ = new views::ResizeArea(this);
    154     AddChildView(resize_area_);
    155 
    156     // 'Main' mode doesn't need a chevron overflow when overflow is shown inside
    157     // the Chrome menu.
    158     if (!overflow_experiment) {
    159       chevron_ = new ChevronMenuButton(NULL, base::string16(), this, false);
    160       chevron_->EnableCanvasFlippingForRTLUI(true);
    161       chevron_->SetAccessibleName(
    162           l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
    163       chevron_->SetVisible(false);
    164       AddChildView(chevron_);
    165     }
    166   }
    167 }
    168 
    169 BrowserActionsContainer::~BrowserActionsContainer() {
    170   FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
    171                     observers_,
    172                     OnBrowserActionsContainerDestroyed());
    173 
    174   if (overflow_menu_)
    175     overflow_menu_->set_observer(NULL);
    176   if (model_)
    177     model_->RemoveObserver(this);
    178   StopShowFolderDropMenuTimer();
    179   HideActivePopup();
    180   DeleteBrowserActionViews();
    181 }
    182 
    183 void BrowserActionsContainer::Init() {
    184   LoadImages();
    185 
    186   // We wait to set the container width until now so that the chevron images
    187   // will be loaded.  The width calculation needs to know the chevron size.
    188   if (model_ && model_->extensions_initialized()) {
    189     container_width_ = GetPreferredWidth();
    190     SetChevronVisibility();
    191   }
    192 
    193   initialized_ = true;
    194 }
    195 
    196 BrowserActionView* BrowserActionsContainer::GetViewForExtension(
    197     const Extension* extension) {
    198   for (BrowserActionViews::iterator view = browser_action_views_.begin();
    199        view != browser_action_views_.end(); ++view) {
    200     if ((*view)->extension() == extension)
    201       return *view;
    202   }
    203   return NULL;
    204 }
    205 
    206 void BrowserActionsContainer::RefreshBrowserActionViews() {
    207   for (size_t i = 0; i < browser_action_views_.size(); ++i)
    208     browser_action_views_[i]->UpdateState();
    209 }
    210 
    211 void BrowserActionsContainer::CreateBrowserActionViews() {
    212   DCHECK(browser_action_views_.empty());
    213   if (!model_)
    214     return;
    215 
    216   extensions::ExtensionActionManager* action_manager =
    217       extensions::ExtensionActionManager::Get(profile_);
    218   const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
    219   for (extensions::ExtensionList::const_iterator i(toolbar_items.begin());
    220        i != toolbar_items.end(); ++i) {
    221     if (!ShouldDisplayBrowserAction(i->get()))
    222       continue;
    223 
    224     BrowserActionView* view =
    225         new BrowserActionView(i->get(),
    226                               action_manager->GetExtensionAction(**i),
    227                               browser_,
    228                               this);
    229     browser_action_views_.push_back(view);
    230     AddChildView(view);
    231   }
    232 }
    233 
    234 void BrowserActionsContainer::DeleteBrowserActionViews() {
    235   HideActivePopup();
    236   if (overflow_menu_)
    237     overflow_menu_->NotifyBrowserActionViewsDeleting();
    238   STLDeleteElements(&browser_action_views_);
    239 }
    240 
    241 size_t BrowserActionsContainer::VisibleBrowserActions() const {
    242   size_t visible_actions = 0;
    243   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    244     if (browser_action_views_[i]->visible())
    245       ++visible_actions;
    246   }
    247   return visible_actions;
    248 }
    249 
    250 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
    251   if (!animating())
    252     return VisibleBrowserActions();
    253 
    254   return WidthToIconCount(animation_target_size_);
    255 }
    256 
    257 void BrowserActionsContainer::ExecuteExtensionCommand(
    258     const extensions::Extension* extension,
    259     const extensions::Command& command) {
    260   // Global commands are handled by the ExtensionCommandsGlobalRegistry
    261   // instance.
    262   DCHECK(!command.global());
    263   extension_keybinding_registry_->ExecuteCommand(extension->id(),
    264                                                  command.accelerator());
    265 }
    266 
    267 void BrowserActionsContainer::NotifyActionMovedToOverflow() {
    268   // When an action is moved to overflow, we shrink the size of the container
    269   // by 1.
    270   if (!profile_->IsOffTheRecord()) {
    271     int icon_count = model_->GetVisibleIconCount();
    272     // Since this happens when an icon moves from the main bar to overflow, we
    273     // can't possibly have had no visible icons on the main bar.
    274     DCHECK_NE(0, icon_count);
    275     if (icon_count == -1)
    276       icon_count = browser_action_views_.size();
    277     model_->SetVisibleIconCount(icon_count - 1);
    278   }
    279   Animate(gfx::Tween::EASE_OUT,
    280           VisibleBrowserActionsAfterAnimation() - 1);
    281 }
    282 
    283 bool BrowserActionsContainer::ShownInsideMenu() const {
    284   return in_overflow_mode();
    285 }
    286 
    287 void BrowserActionsContainer::OnBrowserActionViewDragDone() {
    288   ToolbarVisibleCountChanged();
    289   FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
    290                     observers_,
    291                     OnBrowserActionDragDone());
    292 }
    293 
    294 views::MenuButton* BrowserActionsContainer::GetOverflowReferenceView() {
    295   // With traditional overflow, the reference is the chevron. With the
    296   // redesign, we use the wrench menu instead.
    297   return chevron_ ?
    298       chevron_ :
    299       BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->app_menu();
    300 }
    301 
    302 void BrowserActionsContainer::SetPopupOwner(BrowserActionView* popup_owner) {
    303   // We should never be setting a popup owner when one already exists, and
    304   // never unsetting one when one wasn't set.
    305   DCHECK((!popup_owner_ && popup_owner) ||
    306          (popup_owner_ && !popup_owner));
    307   popup_owner_ = popup_owner;
    308 }
    309 
    310 void BrowserActionsContainer::HideActivePopup() {
    311   if (popup_owner_)
    312     popup_owner_->view_controller()->HidePopup();
    313 }
    314 
    315 BrowserActionView* BrowserActionsContainer::GetMainViewForExtension(
    316     const Extension* extension) {
    317   return in_overflow_mode() ?
    318       main_container_->GetViewForExtension(extension) :
    319       GetViewForExtension(extension);
    320 }
    321 
    322 void BrowserActionsContainer::AddObserver(
    323     BrowserActionsContainerObserver* observer) {
    324   observers_.AddObserver(observer);
    325 }
    326 
    327 void BrowserActionsContainer::RemoveObserver(
    328     BrowserActionsContainerObserver* observer) {
    329   observers_.RemoveObserver(observer);
    330 }
    331 
    332 gfx::Size BrowserActionsContainer::GetPreferredSize() const {
    333   if (in_overflow_mode()) {
    334     int icon_count = GetIconCount();
    335     // In overflow, we always have a preferred size of a full row (even if we
    336     // don't use it), and always of at least one row. The parent may decide to
    337     // show us even when empty, e.g. as a drag target for dragging in icons from
    338     // the main container.
    339     int row_count =
    340         ((std::max(0, icon_count - 1)) / icons_per_overflow_menu_row_) + 1;
    341     return gfx::Size(
    342         IconCountToWidth(icons_per_overflow_menu_row_, false),
    343         row_count * IconHeight());
    344   }
    345 
    346   // If there are no actions to show, then don't show the container at all.
    347   if (browser_action_views_.empty())
    348     return gfx::Size();
    349 
    350   // We calculate the size of the view by taking the current width and
    351   // subtracting resize_amount_ (the latter represents how far the user is
    352   // resizing the view or, if animating the snapping, how far to animate it).
    353   // But we also clamp it to a minimum size and the maximum size, so that the
    354   // container can never shrink too far or take up more space than it needs.
    355   // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX).
    356   int preferred_width = std::min(
    357       std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_),
    358       IconCountToWidth(-1, false));
    359   return gfx::Size(preferred_width, IconHeight());
    360 }
    361 
    362 int BrowserActionsContainer::GetHeightForWidth(int width) const {
    363   if (in_overflow_mode())
    364     icons_per_overflow_menu_row_ = (width - kItemSpacing) / IconWidth(true);
    365   return GetPreferredSize().height();
    366 }
    367 
    368 gfx::Size BrowserActionsContainer::GetMinimumSize() const {
    369   int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false));
    370   return gfx::Size(min_width, IconHeight());
    371 }
    372 
    373 void BrowserActionsContainer::Layout() {
    374   if (browser_action_views_.empty()) {
    375     SetVisible(false);
    376     return;
    377   }
    378 
    379   SetVisible(true);
    380   if (resize_area_)
    381     resize_area_->SetBounds(0, 0, kItemSpacing, height());
    382 
    383   // If the icons don't all fit, show the chevron (unless suppressed).
    384   int max_x = GetPreferredSize().width();
    385   if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_ && chevron_) {
    386     chevron_->SetVisible(true);
    387     gfx::Size chevron_size(chevron_->GetPreferredSize());
    388     max_x -=
    389         ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
    390     chevron_->SetBounds(
    391         width() - ToolbarView::kStandardSpacing - chevron_size.width(),
    392         0,
    393         chevron_size.width(),
    394         chevron_size.height());
    395   } else if (chevron_) {
    396     chevron_->SetVisible(false);
    397   }
    398 
    399   // Now draw the icons for the browser actions in the available space.
    400   int icon_width = IconWidth(false);
    401   if (in_overflow_mode()) {
    402     for (size_t i = 0;
    403          i < main_container_->VisibleBrowserActionsAfterAnimation(); ++i) {
    404       // Ensure that any browser actions shown in the main view are hidden in
    405       // the overflow view.
    406       browser_action_views_[i]->SetVisible(false);
    407     }
    408 
    409     for (size_t i = main_container_->VisibleBrowserActionsAfterAnimation();
    410          i < browser_action_views_.size(); ++i) {
    411       BrowserActionView* view = browser_action_views_[i];
    412       size_t index = i - main_container_->VisibleBrowserActionsAfterAnimation();
    413       int row_index = static_cast<int>(index) / icons_per_overflow_menu_row_;
    414       int x = kItemSpacing + (index * IconWidth(true)) -
    415           (row_index * IconWidth(true) * icons_per_overflow_menu_row_);
    416       gfx::Rect rect_bounds(
    417           x, IconHeight() * row_index, icon_width, IconHeight());
    418       view->SetBoundsRect(rect_bounds);
    419       view->SetVisible(true);
    420     }
    421   } else {
    422     for (BrowserActionViews::const_iterator it = browser_action_views_.begin();
    423          it < browser_action_views_.end(); ++it) {
    424       BrowserActionView* view = *it;
    425       int x = ToolbarView::kStandardSpacing +
    426           ((it - browser_action_views_.begin()) * IconWidth(true));
    427       view->SetVisible(x + icon_width <= max_x);
    428       if (view->visible())
    429         view->SetBounds(x, 0, icon_width, IconHeight());
    430     }
    431   }
    432 }
    433 
    434 bool BrowserActionsContainer::GetDropFormats(
    435     int* formats,
    436     std::set<OSExchangeData::CustomFormat>* custom_formats) {
    437   return BrowserActionDragData::GetDropFormats(custom_formats);
    438 }
    439 
    440 bool BrowserActionsContainer::AreDropTypesRequired() {
    441   return BrowserActionDragData::AreDropTypesRequired();
    442 }
    443 
    444 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
    445   return BrowserActionDragData::CanDrop(data, profile_);
    446 }
    447 
    448 int BrowserActionsContainer::OnDragUpdated(
    449     const ui::DropTargetEvent& event) {
    450   // First check if we are above the chevron (overflow) menu.
    451   if (chevron_ && GetEventHandlerForPoint(event.location()) == chevron_) {
    452     if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_)
    453       StartShowFolderDropMenuTimer();
    454     return ui::DragDropTypes::DRAG_MOVE;
    455   }
    456   StopShowFolderDropMenuTimer();
    457 
    458   size_t row_index = 0;
    459   size_t before_icon_in_row = 0;
    460   // If there are no visible browser actions (such as when dragging an icon to
    461   // an empty overflow/main container), then 0, 0 for row, column is correct.
    462   if (VisibleBrowserActions() != 0) {
    463     // Figure out where to display the indicator. This is a complex calculation:
    464 
    465     // First, we subtract out the padding to the left of the icon area, which is
    466     // ToolbarView::kStandardSpacing. If we're right-to-left, we also mirror the
    467     // event.x() so that our calculations are consistent with left-to-right.
    468     int offset_into_icon_area =
    469         GetMirroredXInView(event.x()) - ToolbarView::kStandardSpacing;
    470 
    471     // Next, figure out what row we're on. This only matters for overflow mode,
    472     // but the calculation is the same for both.
    473     row_index = event.y() / IconHeight();
    474 
    475     // Sanity check - we should never be on a different row in the main
    476     // container.
    477     DCHECK(in_overflow_mode() || row_index == 0);
    478 
    479     // Next, we determine which icon to place the indicator in front of. We want
    480     // to place the indicator in front of icon n when the cursor is between the
    481     // midpoints of icons (n - 1) and n.  To do this we take the offset into the
    482     // icon area and transform it as follows:
    483     //
    484     // Real icon area:
    485     //   0   a     *  b        c
    486     //   |   |        |        |
    487     //   |[IC|ON]  [IC|ON]  [IC|ON]
    488     // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
    489     // Here the "*" represents the offset into the icon area, and since it's
    490     // between a and b, we want to return "1".
    491     //
    492     // Transformed "icon area":
    493     //   0        a     *  b        c
    494     //   |        |        |        |
    495     //   |[ICON]  |[ICON]  |[ICON]  |
    496     // If we shift both our offset and our divider points later by half an icon
    497     // plus one spacing unit, then it becomes very easy to calculate how many
    498     // divider points we've passed, because they're the multiples of "one icon
    499     // plus padding".
    500     int before_icon_unclamped =
    501         (offset_into_icon_area + (IconWidth(false) / 2) +
    502         kItemSpacing) / IconWidth(true);
    503 
    504     // We need to figure out how many icons are visible on the relevant row.
    505     // In the main container, this will just be the visible actions.
    506     int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
    507     if (in_overflow_mode()) {
    508       // If this is the final row of the overflow, then this is the remainder of
    509       // visible icons. Otherwise, it's a full row (kIconsPerRow).
    510       visible_icons_on_row =
    511           row_index ==
    512               static_cast<size_t>(visible_icons_on_row /
    513                                   icons_per_overflow_menu_row_) ?
    514                   visible_icons_on_row % icons_per_overflow_menu_row_ :
    515                   icons_per_overflow_menu_row_;
    516     }
    517 
    518     // Because the user can drag outside the container bounds, we need to clamp
    519     // to the valid range. Note that the maximum allowable value is (num icons),
    520     // not (num icons - 1), because we represent the indicator being past the
    521     // last icon as being "before the (last + 1) icon".
    522     before_icon_in_row =
    523         std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row);
    524   }
    525 
    526   if (!drop_position_.get() ||
    527       !(drop_position_->row == row_index &&
    528         drop_position_->icon_in_row == before_icon_in_row)) {
    529     drop_position_.reset(new DropPosition(row_index, before_icon_in_row));
    530     SchedulePaint();
    531   }
    532 
    533   return ui::DragDropTypes::DRAG_MOVE;
    534 }
    535 
    536 void BrowserActionsContainer::OnDragExited() {
    537   StopShowFolderDropMenuTimer();
    538   drop_position_.reset();
    539   SchedulePaint();
    540 }
    541 
    542 int BrowserActionsContainer::OnPerformDrop(
    543     const ui::DropTargetEvent& event) {
    544   BrowserActionDragData data;
    545   if (!data.Read(event.data()))
    546     return ui::DragDropTypes::DRAG_NONE;
    547 
    548   // Make sure we have the same view as we started with.
    549   DCHECK_EQ(browser_action_views_[data.index()]->extension()->id(),
    550             data.id());
    551   DCHECK(model_);
    552 
    553   size_t i = drop_position_->row * icons_per_overflow_menu_row_ +
    554              drop_position_->icon_in_row;
    555   if (in_overflow_mode())
    556     i += main_container_->VisibleBrowserActionsAfterAnimation();
    557   // |i| now points to the item to the right of the drop indicator*, which is
    558   // correct when dragging an icon to the left. When dragging to the right,
    559   // however, we want the icon being dragged to get the index of the item to
    560   // the left of the drop indicator, so we subtract one.
    561   // * Well, it can also point to the end, but not when dragging to the left. :)
    562   if (i > data.index())
    563     --i;
    564 
    565   if (profile_->IsOffTheRecord())
    566     i = model_->IncognitoIndexToOriginal(i);
    567 
    568   // If this was a drag between containers, we will have to adjust the number of
    569   // visible icons.
    570   bool drag_between_containers =
    571       !browser_action_views_[data.index()]->visible();
    572   model_->MoveExtensionIcon(
    573       browser_action_views_[data.index()]->extension(), i);
    574 
    575   if (drag_between_containers) {
    576     // Add one for the dropped icon.
    577     size_t new_icon_count = VisibleBrowserActionsAfterAnimation() + 1;
    578 
    579     // Let the main container update the model.
    580     if (in_overflow_mode())
    581       main_container_->NotifyActionMovedToOverflow();
    582     else if (!profile_->IsOffTheRecord())  // This is the main container.
    583       model_->SetVisibleIconCount(model_->GetVisibleIconCount() + 1);
    584 
    585     // The size changed, so we need to animate.
    586     Animate(gfx::Tween::EASE_OUT, new_icon_count);
    587   }
    588 
    589   OnDragExited();  // Perform clean up after dragging.
    590   return ui::DragDropTypes::DRAG_MOVE;
    591 }
    592 
    593 void BrowserActionsContainer::GetAccessibleState(
    594     ui::AXViewState* state) {
    595   state->role = ui::AX_ROLE_GROUP;
    596   state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
    597 }
    598 
    599 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source,
    600                                                   const gfx::Point& point) {
    601   if (source == chevron_) {
    602     overflow_menu_ =
    603         new BrowserActionOverflowMenuController(this,
    604                                                 browser_,
    605                                                 chevron_,
    606                                                 browser_action_views_,
    607                                                 VisibleBrowserActions(),
    608                                                 false);
    609     overflow_menu_->set_observer(this);
    610     overflow_menu_->RunMenu(GetWidget());
    611   }
    612 }
    613 
    614 void BrowserActionsContainer::WriteDragDataForView(View* sender,
    615                                                    const gfx::Point& press_pt,
    616                                                    OSExchangeData* data) {
    617   DCHECK(data);
    618 
    619   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    620     BrowserActionView* view = browser_action_views_[i];
    621     if (view == sender) {
    622       // Set the dragging image for the icon.
    623       gfx::ImageSkia badge(view->GetIconWithBadge());
    624       drag_utils::SetDragImageOnDataObject(badge,
    625                                            press_pt.OffsetFromOrigin(),
    626                                            data);
    627 
    628       // Fill in the remaining info.
    629       BrowserActionDragData drag_data(view->extension()->id(), i);
    630       drag_data.Write(profile_, data);
    631       break;
    632     }
    633   }
    634 }
    635 
    636 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
    637                                                       const gfx::Point& p) {
    638   return ui::DragDropTypes::DRAG_MOVE;
    639 }
    640 
    641 bool BrowserActionsContainer::CanStartDragForView(View* sender,
    642                                                   const gfx::Point& press_pt,
    643                                                   const gfx::Point& p) {
    644   // We don't allow dragging while we're highlighting.
    645   return !model_->is_highlighting();
    646 }
    647 
    648 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
    649   if (!done_resizing) {
    650     resize_amount_ = resize_amount;
    651     OnBrowserActionVisibilityChanged();
    652     return;
    653   }
    654 
    655   // Up until now we've only been modifying the resize_amount, but now it is
    656   // time to set the container size to the size we have resized to, and then
    657   // animate to the nearest icon count size if necessary (which may be 0).
    658   int max_width = IconCountToWidth(-1, false);
    659   container_width_ =
    660       std::min(std::max(0, container_width_ - resize_amount), max_width);
    661 
    662   // Save off the desired number of visible icons.  We do this now instead of at
    663   // the end of the animation so that even if the browser is shut down while
    664   // animating, the right value will be restored on next run.
    665   // NOTE: Don't save the icon count in incognito because there may be fewer
    666   // icons in that mode. The result is that the container in a normal window is
    667   // always at least as wide as in an incognito window.
    668   int visible_icons = WidthToIconCount(container_width_);
    669   if (!profile_->IsOffTheRecord())
    670     model_->SetVisibleIconCount(visible_icons);
    671   Animate(gfx::Tween::EASE_OUT, visible_icons);
    672 }
    673 
    674 void BrowserActionsContainer::AnimationProgressed(
    675     const gfx::Animation* animation) {
    676   DCHECK_EQ(resize_animation_.get(), animation);
    677   resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
    678       (container_width_ - animation_target_size_));
    679   OnBrowserActionVisibilityChanged();
    680 }
    681 
    682 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
    683   container_width_ = animation_target_size_;
    684   animation_target_size_ = 0;
    685   resize_amount_ = 0;
    686   suppress_chevron_ = false;
    687   SetChevronVisibility();
    688   OnBrowserActionVisibilityChanged();
    689 
    690   FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
    691                     observers_,
    692                     OnBrowserActionsContainerAnimationEnded());
    693 }
    694 
    695 void BrowserActionsContainer::NotifyMenuDeleted(
    696     BrowserActionOverflowMenuController* controller) {
    697   DCHECK_EQ(overflow_menu_, controller);
    698   overflow_menu_ = NULL;
    699 }
    700 
    701 content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
    702   return browser_->tab_strip_model()->GetActiveWebContents();
    703 }
    704 
    705 extensions::ActiveTabPermissionGranter*
    706     BrowserActionsContainer::GetActiveTabPermissionGranter() {
    707   content::WebContents* web_contents =
    708       browser_->tab_strip_model()->GetActiveWebContents();
    709   if (!web_contents)
    710     return NULL;
    711   return extensions::TabHelper::FromWebContents(web_contents)->
    712       active_tab_permission_granter();
    713 }
    714 
    715 ExtensionPopup* BrowserActionsContainer::TestGetPopup() {
    716   return popup_owner_ ? popup_owner_->view_controller()->popup() : NULL;
    717 }
    718 
    719 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
    720   model_->SetVisibleIconCountForTest(icons);
    721 }
    722 
    723 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
    724   // If the views haven't been initialized yet, wait for the next call to
    725   // paint (one will be triggered by entering highlight mode).
    726   if (model_->is_highlighting() && !browser_action_views_.empty()) {
    727     views::Painter::PaintPainterAt(
    728         canvas, highlight_painter_.get(), GetLocalBounds());
    729   }
    730 
    731   // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
    732   // dragging (like we do for tab dragging).
    733   if (drop_position_.get()) {
    734     // The two-pixel width drop indicator.
    735     static const int kDropIndicatorWidth = 2;
    736 
    737     // Convert back to a pixel offset into the container.  First find the X
    738     // coordinate of the drop icon.
    739     int drop_icon_x = ToolbarView::kStandardSpacing +
    740         (drop_position_->icon_in_row * IconWidth(true));
    741     // Next, find the space before the drop icon. This will either be
    742     // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this
    743     // is the first icon.
    744     // NOTE: Right now, these are the same. But let's do this right for if they
    745     // ever aren't.
    746     int space_before_drop_icon = drop_position_->icon_in_row == 0 ?
    747         ToolbarView::kStandardSpacing : kItemSpacing;
    748     // Now place the drop indicator halfway between this and the end of the
    749     // previous icon.  If there is an odd amount of available space between the
    750     // two icons (or the icon and the address bar) after subtracting the drop
    751     // indicator width, this calculation puts the extra pixel on the left side
    752     // of the indicator, since when the indicator is between the address bar and
    753     // the first icon, it looks better closer to the icon.
    754     int drop_indicator_x = drop_icon_x -
    755         ((space_before_drop_icon + kDropIndicatorWidth) / 2);
    756     int row_height = IconHeight();
    757     int drop_indicator_y = row_height * drop_position_->row;
    758     gfx::Rect indicator_bounds(drop_indicator_x,
    759                                drop_indicator_y,
    760                                kDropIndicatorWidth,
    761                                row_height);
    762     indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
    763 
    764     // Color of the drop indicator.
    765     static const SkColor kDropIndicatorColor = SK_ColorBLACK;
    766     canvas->FillRect(indicator_bounds, kDropIndicatorColor);
    767   }
    768 }
    769 
    770 void BrowserActionsContainer::OnThemeChanged() {
    771   LoadImages();
    772 }
    773 
    774 void BrowserActionsContainer::ViewHierarchyChanged(
    775     const ViewHierarchyChangedDetails& details) {
    776   // No extensions (e.g., incognito).
    777   if (!model_)
    778     return;
    779 
    780   if (details.is_add && details.child == this) {
    781     // Initial toolbar button creation and placement in the widget hierarchy.
    782     // We do this here instead of in the constructor because AddBrowserAction
    783     // calls Layout on the Toolbar, which needs this object to be constructed
    784     // before its Layout function is called.
    785     CreateBrowserActionViews();
    786   }
    787 }
    788 
    789 // static
    790 int BrowserActionsContainer::IconWidth(bool include_padding) {
    791   static bool initialized = false;
    792   static int icon_width = 0;
    793   if (!initialized) {
    794     initialized = true;
    795     icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    796         IDR_BROWSER_ACTION)->width();
    797   }
    798   return icon_width + (include_padding ? kItemSpacing : 0);
    799 }
    800 
    801 // static
    802 int BrowserActionsContainer::IconHeight() {
    803   static bool initialized = false;
    804   static int icon_height = 0;
    805   if (!initialized) {
    806     initialized = true;
    807     icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    808         IDR_BROWSER_ACTION)->height();
    809   }
    810   return icon_height;
    811 }
    812 
    813 void BrowserActionsContainer::ToolbarExtensionAdded(const Extension* extension,
    814                                                     int index) {
    815 #if defined(DEBUG)
    816   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    817     DCHECK(browser_action_views_[i]->extension() != extension) <<
    818            "Asked to add a browser action view for an extension that already "
    819            "exists.";
    820   }
    821 #endif
    822   CloseOverflowMenu();
    823 
    824   if (!ShouldDisplayBrowserAction(extension))
    825     return;
    826 
    827   // Add the new browser action to the vector and the view hierarchy.
    828   if (profile_->IsOffTheRecord())
    829     index = model_->OriginalIndexToIncognito(index);
    830   BrowserActionView* view =
    831       new BrowserActionView(extension,
    832                             extensions::ExtensionActionManager::Get(profile_)->
    833                                 GetExtensionAction(*extension),
    834                             browser_,
    835                             this);
    836   browser_action_views_.insert(browser_action_views_.begin() + index, view);
    837   AddChildViewAt(view, index);
    838 
    839   // If we are still initializing the container, don't bother animating.
    840   if (!model_->extensions_initialized())
    841     return;
    842 
    843   // If this is just an upgrade, then don't worry about resizing.
    844   if (!extensions::ExtensionSystem::Get(profile_)->runtime_data()->
    845           IsBeingUpgraded(extension)) {
    846     // We need to resize if either:
    847     // - The container is set to display all icons (visible count = -1), or
    848     // - The container will need to expand to include the chevron. This can
    849     //   happen when the container is set to display <n> icons, where <n> is
    850     //   the number of icons before the new icon. With the new icon, the chevron
    851     //   will need to be displayed.
    852     int model_icon_count = model_->GetVisibleIconCount();
    853     if (model_icon_count == -1 ||
    854         (static_cast<size_t>(model_icon_count) < browser_action_views_.size() &&
    855          (chevron_ && !chevron_->visible()))) {
    856       suppress_chevron_ = true;
    857       Animate(gfx::Tween::LINEAR, GetIconCount());
    858       return;
    859     }
    860   }
    861 
    862   // Otherwise, we don't have to resize, so just redraw the (possibly modified)
    863   // visible icon set.
    864   OnBrowserActionVisibilityChanged();
    865 }
    866 
    867 void BrowserActionsContainer::ToolbarExtensionRemoved(
    868     const Extension* extension) {
    869   CloseOverflowMenu();
    870 
    871   size_t visible_actions = VisibleBrowserActionsAfterAnimation();
    872   for (BrowserActionViews::iterator i(browser_action_views_.begin());
    873        i != browser_action_views_.end(); ++i) {
    874     if ((*i)->extension() == extension) {
    875       delete *i;
    876       browser_action_views_.erase(i);
    877 
    878       // If the extension is being upgraded we don't want the bar to shrink
    879       // because the icon is just going to get re-added to the same location.
    880       if (extensions::ExtensionSystem::Get(profile_)->runtime_data()->
    881               IsBeingUpgraded(extension))
    882         return;
    883 
    884       if (browser_action_views_.size() > visible_actions) {
    885         // If we have more icons than we can show, then we must not be changing
    886         // the container size (since we either removed an icon from the main
    887         // area and one from the overflow list will have shifted in, or we
    888         // removed an entry directly from the overflow list).
    889         OnBrowserActionVisibilityChanged();
    890       } else {
    891         // Either we went from overflow to no-overflow, or we shrunk the no-
    892         // overflow container by 1.  Either way the size changed, so animate.
    893         if (chevron_)
    894           chevron_->SetVisible(false);
    895         Animate(gfx::Tween::EASE_OUT, browser_action_views_.size());
    896       }
    897       return;  // We have found the action to remove, bail out.
    898     }
    899   }
    900 }
    901 
    902 void BrowserActionsContainer::ToolbarExtensionMoved(const Extension* extension,
    903                                                     int index) {
    904   if (!ShouldDisplayBrowserAction(extension))
    905     return;
    906 
    907   if (profile_->IsOffTheRecord())
    908     index = model_->OriginalIndexToIncognito(index);
    909 
    910   DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
    911 
    912   BrowserActionViews::iterator iter = browser_action_views_.begin();
    913   int old_index = 0;
    914   while (iter != browser_action_views_.end() &&
    915          (*iter)->extension() != extension) {
    916     ++iter;
    917     ++old_index;
    918   }
    919 
    920   DCHECK(iter != browser_action_views_.end());
    921   if (old_index == index)
    922     return;  // Already in place.
    923 
    924   BrowserActionView* moved_view = *iter;
    925   browser_action_views_.erase(iter);
    926   browser_action_views_.insert(
    927       browser_action_views_.begin() + index, moved_view);
    928 
    929   Layout();
    930   SchedulePaint();
    931 }
    932 
    933 void BrowserActionsContainer::ToolbarExtensionUpdated(
    934     const Extension* extension) {
    935   BrowserActionView* view = GetViewForExtension(extension);
    936   if (view)
    937     view->UpdateState();
    938 }
    939 
    940 bool BrowserActionsContainer::ShowExtensionActionPopup(
    941     const Extension* extension,
    942     bool grant_active_tab) {
    943   // Don't override another popup, and only show in the active window.
    944   if (popup_owner_ || !browser_->window()->IsActive())
    945     return false;
    946 
    947   BrowserActionView* view = GetViewForExtension(extension);
    948   return view && view->view_controller()->ExecuteAction(ExtensionPopup::SHOW,
    949                                                         grant_active_tab);
    950 }
    951 
    952 void BrowserActionsContainer::ToolbarVisibleCountChanged() {
    953   if (GetPreferredWidth() != container_width_)
    954     Animate(gfx::Tween::EASE_OUT, GetIconCount());
    955 }
    956 
    957 void BrowserActionsContainer::ToolbarHighlightModeChanged(
    958     bool is_highlighting) {
    959   // The visual highlighting is done in OnPaint(). It's a bit of a pain that
    960   // we delete and recreate everything here, but given everything else going on
    961   // (the lack of highlight, n more extensions appearing, etc), it's not worth
    962   // the extra complexity to create and insert only the new extensions.
    963   DeleteBrowserActionViews();
    964   CreateBrowserActionViews();
    965   Animate(gfx::Tween::LINEAR, GetIconCount());
    966 }
    967 
    968 Browser* BrowserActionsContainer::GetBrowser() {
    969   return browser_;
    970 }
    971 
    972 void BrowserActionsContainer::LoadImages() {
    973   ui::ThemeProvider* tp = GetThemeProvider();
    974   if (!tp || !chevron_)
    975     return;
    976 
    977   chevron_->SetImage(views::Button::STATE_NORMAL,
    978                      *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
    979 
    980   const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
    981   highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages));
    982 }
    983 
    984 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
    985   SetVisible(!browser_action_views_.empty());
    986   if (owner_view_) {
    987     owner_view_->Layout();
    988     owner_view_->SchedulePaint();
    989   } else {
    990     // In overflow mode, we don't have an owner view, but we still have to
    991     // update ourselves.
    992     Layout();
    993     SchedulePaint();
    994   }
    995 }
    996 
    997 int BrowserActionsContainer::GetPreferredWidth() {
    998   size_t visible_actions = GetIconCount();
    999   return IconCountToWidth(
   1000       visible_actions,
   1001       chevron_ && visible_actions < browser_action_views_.size());
   1002 }
   1003 
   1004 void BrowserActionsContainer::SetChevronVisibility() {
   1005   if (chevron_) {
   1006     chevron_->SetVisible(
   1007         VisibleBrowserActionsAfterAnimation() < browser_action_views_.size());
   1008   }
   1009 }
   1010 
   1011 void BrowserActionsContainer::CloseOverflowMenu() {
   1012   if (overflow_menu_)
   1013     overflow_menu_->CancelMenu();
   1014 }
   1015 
   1016 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
   1017   show_menu_task_factory_.InvalidateWeakPtrs();
   1018 }
   1019 
   1020 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
   1021   base::MessageLoop::current()->PostDelayedTask(
   1022       FROM_HERE,
   1023       base::Bind(&BrowserActionsContainer::ShowDropFolder,
   1024                  show_menu_task_factory_.GetWeakPtr()),
   1025       base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
   1026 }
   1027 
   1028 void BrowserActionsContainer::ShowDropFolder() {
   1029   DCHECK(!overflow_menu_);
   1030   overflow_menu_ =
   1031       new BrowserActionOverflowMenuController(this,
   1032                                               browser_,
   1033                                               chevron_,
   1034                                               browser_action_views_,
   1035                                               VisibleBrowserActions(),
   1036                                               true);
   1037   overflow_menu_->set_observer(this);
   1038   overflow_menu_->RunMenu(GetWidget());
   1039 }
   1040 
   1041 int BrowserActionsContainer::IconCountToWidth(int icons,
   1042                                               bool display_chevron) const {
   1043   if (icons < 0)
   1044     icons = browser_action_views_.size();
   1045   if ((icons == 0) && !display_chevron)
   1046     return ToolbarView::kStandardSpacing;
   1047   int icons_size =
   1048       (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
   1049   int chevron_size = chevron_ && display_chevron ?
   1050       (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
   1051   // In overflow mode, our padding is to use item spacing on either end (just so
   1052   // we can see the drop indicator). Otherwise we use the standard toolbar
   1053   // spacing.
   1054   // Note: These are actually the same thing, but, on the offchance one
   1055   // changes, let's get it right.
   1056   int padding =
   1057       2 * (in_overflow_mode() ? kItemSpacing : ToolbarView::kStandardSpacing);
   1058   return icons_size + chevron_size + padding;
   1059 }
   1060 
   1061 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
   1062   // Check for widths large enough to show the entire icon set.
   1063   if (pixels >= IconCountToWidth(-1, false))
   1064     return browser_action_views_.size();
   1065 
   1066   // We reserve space for the padding on either side of the toolbar...
   1067   int available_space = pixels - (ToolbarView::kStandardSpacing * 2);
   1068   // ... and, if the chevron is enabled, the chevron.
   1069   if (chevron_)
   1070     available_space -= (chevron_->GetPreferredSize().width() + kChevronSpacing);
   1071 
   1072   // Now we add an extra between-item padding value so the space can be divided
   1073   // evenly by (size of icon with padding).
   1074   return static_cast<size_t>(
   1075       std::max(0, available_space + kItemSpacing) / IconWidth(true));
   1076 }
   1077 
   1078 int BrowserActionsContainer::MinimumNonemptyWidth() const {
   1079   if (!chevron_)
   1080     return ToolbarView::kStandardSpacing;
   1081   return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing +
   1082       chevron_->GetPreferredSize().width();
   1083 }
   1084 
   1085 void BrowserActionsContainer::Animate(gfx::Tween::Type tween_type,
   1086                                       size_t num_visible_icons) {
   1087   int target_size = IconCountToWidth(num_visible_icons,
   1088       num_visible_icons < browser_action_views_.size());
   1089   if (resize_animation_ && !disable_animations_during_testing_) {
   1090     // Animate! We have to set the animation_target_size_ after calling Reset(),
   1091     // because that could end up calling AnimationEnded which clears the value.
   1092     resize_animation_->Reset();
   1093     resize_animation_->SetTweenType(tween_type);
   1094     animation_target_size_ = target_size;
   1095     resize_animation_->Show();
   1096   } else {
   1097     animation_target_size_ = target_size;
   1098     AnimationEnded(resize_animation_.get());
   1099   }
   1100 }
   1101 
   1102 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
   1103     const Extension* extension) const {
   1104   // Only display incognito-enabled extensions while in incognito mode.
   1105   return !profile_->IsOffTheRecord() ||
   1106       extensions::util::IsIncognitoEnabled(extension->id(), profile_);
   1107 }
   1108 
   1109 size_t BrowserActionsContainer::GetIconCount() const {
   1110   if (!model_)
   1111     return 0u;
   1112 
   1113   const extensions::ExtensionList& extensions = model_->toolbar_items();
   1114 
   1115   // Find the absolute value for the model's visible count.
   1116   int model_visible_size = model_->GetVisibleIconCount();
   1117   size_t absolute_model_visible_size =
   1118       model_visible_size == -1 ? extensions.size() : model_visible_size;
   1119 
   1120   // Find the number of icons which could be displayed.
   1121   size_t displayable_icon_count = 0u;
   1122   size_t main_displayed = 0u;
   1123   for (size_t i = 0; i < extensions.size(); ++i) {
   1124     // Should there be an icon for this extension at all?
   1125     if (ShouldDisplayBrowserAction(extensions[i].get())) {
   1126       ++displayable_icon_count;
   1127       // Should we display it on the main bar? If this is an incognito window,
   1128       // icons have the same overflow status they do in a regular window.
   1129       main_displayed += i < absolute_model_visible_size ? 1u : 0u;
   1130     }
   1131   }
   1132 
   1133   // If this is an existing (initialized) container from an incognito profile,
   1134   // we can't trust the model (because the incognito bars don't adjust model
   1135   // settings). Instead, we go off what we currently have displayed.
   1136   if (initialized_ && profile_->IsOffTheRecord()) {
   1137     main_displayed = in_overflow_mode() ?
   1138         main_container_->VisibleBrowserActionsAfterAnimation() :
   1139         VisibleBrowserActionsAfterAnimation();
   1140   }
   1141 
   1142   // The overflow displays any (displayable) icons not shown by the main bar.
   1143   return in_overflow_mode() ?
   1144       displayable_icon_count - main_displayed : main_displayed;
   1145 }
   1146