Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2012 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/browser_actions_container.h"
      6 
      7 #include "base/compiler_specific.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "base/stl_util.h"
     10 #include "chrome/browser/extensions/extension_service.h"
     11 #include "chrome/browser/extensions/extension_system.h"
     12 #include "chrome/browser/extensions/tab_helper.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/sessions/session_tab_helper.h"
     15 #include "chrome/browser/ui/browser.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/browser_action_view.h"
     19 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
     20 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
     21 #include "chrome/browser/ui/views/extensions/extension_popup.h"
     22 #include "chrome/browser/ui/views/toolbar_view.h"
     23 #include "chrome/common/pref_names.h"
     24 #include "grit/generated_resources.h"
     25 #include "grit/theme_resources.h"
     26 #include "grit/ui_resources.h"
     27 #include "ui/base/accessibility/accessible_view_state.h"
     28 #include "ui/base/animation/slide_animation.h"
     29 #include "ui/base/dragdrop/drag_utils.h"
     30 #include "ui/base/l10n/l10n_util.h"
     31 #include "ui/base/resource/resource_bundle.h"
     32 #include "ui/base/theme_provider.h"
     33 #include "ui/gfx/canvas.h"
     34 #include "ui/views/controls/resize_area.h"
     35 #include "ui/views/metrics.h"
     36 #include "ui/views/widget/widget.h"
     37 
     38 using extensions::Extension;
     39 
     40 namespace {
     41 
     42 // Horizontal spacing between most items in the container, as well as after the
     43 // last item or chevron (if visible).
     44 const int kItemSpacing = ToolbarView::kStandardSpacing;
     45 
     46 // Horizontal spacing before the chevron (if visible).
     47 const int kChevronSpacing = kItemSpacing - 2;
     48 
     49 }  // namespace
     50 
     51 // static
     52 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
     53 
     54 ////////////////////////////////////////////////////////////////////////////////
     55 // BrowserActionsContainer
     56 
     57 BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
     58                                                  View* owner_view)
     59     : profile_(browser->profile()),
     60       browser_(browser),
     61       owner_view_(owner_view),
     62       popup_(NULL),
     63       popup_button_(NULL),
     64       model_(NULL),
     65       container_width_(0),
     66       chevron_(NULL),
     67       overflow_menu_(NULL),
     68       suppress_chevron_(false),
     69       resize_amount_(0),
     70       animation_target_size_(0),
     71       drop_indicator_position_(-1),
     72       task_factory_(this),
     73       show_menu_task_factory_(this) {
     74   set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
     75 
     76   ExtensionService* service =
     77       extensions::ExtensionSystem::Get(profile_)->extension_service();
     78   if (service) {
     79     model_ = service->toolbar_model();
     80     model_->AddObserver(this);
     81   }
     82 
     83   extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
     84       browser->profile(),
     85       owner_view->GetFocusManager(),
     86       extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
     87       this)),
     88 
     89   resize_animation_.reset(new ui::SlideAnimation(this));
     90   resize_area_ = new views::ResizeArea(this);
     91   AddChildView(resize_area_);
     92 
     93   chevron_ = new views::MenuButton(NULL, string16(), this, false);
     94   chevron_->set_border(NULL);
     95   chevron_->EnableCanvasFlippingForRTLUI(true);
     96   chevron_->SetAccessibleName(
     97       l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
     98   chevron_->SetVisible(false);
     99   AddChildView(chevron_);
    100 }
    101 
    102 BrowserActionsContainer::~BrowserActionsContainer() {
    103   if (overflow_menu_)
    104     overflow_menu_->set_observer(NULL);
    105   if (model_)
    106     model_->RemoveObserver(this);
    107   StopShowFolderDropMenuTimer();
    108   if (popup_)
    109     popup_->GetWidget()->RemoveObserver(this);
    110   HidePopup();
    111   DeleteBrowserActionViews();
    112 }
    113 
    114 void BrowserActionsContainer::Init() {
    115   LoadImages();
    116 
    117   // We wait to set the container width until now so that the chevron images
    118   // will be loaded.  The width calculation needs to know the chevron size.
    119   if (model_ &&
    120       !profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) {
    121     // Migration code to the new VisibleIconCount pref.
    122     // TODO(mpcomplete): remove this after users are upgraded to 5.0.
    123     int predefined_width =
    124         profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth);
    125     if (predefined_width != 0)
    126       model_->SetVisibleIconCount(WidthToIconCount(predefined_width));
    127   }
    128   if (model_ && model_->extensions_initialized())
    129     SetContainerWidth();
    130 }
    131 
    132 BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
    133     ExtensionAction* action) {
    134   for (BrowserActionViews::iterator i(browser_action_views_.begin());
    135        i != browser_action_views_.end(); ++i) {
    136     if ((*i)->button()->browser_action() == action)
    137       return *i;
    138   }
    139   return NULL;
    140 }
    141 
    142 void BrowserActionsContainer::RefreshBrowserActionViews() {
    143   for (size_t i = 0; i < browser_action_views_.size(); ++i)
    144     browser_action_views_[i]->button()->UpdateState();
    145 }
    146 
    147 void BrowserActionsContainer::CreateBrowserActionViews() {
    148   DCHECK(browser_action_views_.empty());
    149   if (!model_)
    150     return;
    151 
    152   const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
    153   for (extensions::ExtensionList::const_iterator i(toolbar_items.begin());
    154        i != toolbar_items.end(); ++i) {
    155     if (!ShouldDisplayBrowserAction(i->get()))
    156       continue;
    157 
    158     BrowserActionView* view = new BrowserActionView(i->get(), browser_, this);
    159     browser_action_views_.push_back(view);
    160     AddChildView(view);
    161   }
    162 }
    163 
    164 void BrowserActionsContainer::DeleteBrowserActionViews() {
    165   HidePopup();
    166   STLDeleteElements(&browser_action_views_);
    167 }
    168 
    169 size_t BrowserActionsContainer::VisibleBrowserActions() const {
    170   size_t visible_actions = 0;
    171   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    172     if (browser_action_views_[i]->visible())
    173       ++visible_actions;
    174   }
    175   return visible_actions;
    176 }
    177 
    178 gfx::Size BrowserActionsContainer::GetPreferredSize() {
    179   if (browser_action_views_.empty())
    180     return gfx::Size(ToolbarView::kStandardSpacing, 0);
    181 
    182   // We calculate the size of the view by taking the current width and
    183   // subtracting resize_amount_ (the latter represents how far the user is
    184   // resizing the view or, if animating the snapping, how far to animate it).
    185   // But we also clamp it to a minimum size and the maximum size, so that the
    186   // container can never shrink too far or take up more space than it needs. In
    187   // other words: ContainerMinSize() < width() - resize < ClampTo(MAX).
    188   int clamped_width = std::min(
    189       std::max(ContainerMinSize(), container_width_ - resize_amount_),
    190       IconCountToWidth(-1, false));
    191   return gfx::Size(clamped_width, 0);
    192 }
    193 
    194 void BrowserActionsContainer::Layout() {
    195   if (browser_action_views_.empty()) {
    196     SetVisible(false);
    197     return;
    198   }
    199 
    200   SetVisible(true);
    201   resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing,
    202                           IconHeight());
    203 
    204   // If the icons don't all fit, show the chevron (unless suppressed).
    205   int max_x = GetPreferredSize().width();
    206   if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) {
    207     chevron_->SetVisible(true);
    208     gfx::Size chevron_size(chevron_->GetPreferredSize());
    209     max_x -=
    210         ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
    211     chevron_->SetBounds(
    212         width() - ToolbarView::kStandardSpacing - chevron_size.width(),
    213         ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height());
    214   } else {
    215     chevron_->SetVisible(false);
    216   }
    217 
    218   // Now draw the icons for the browser actions in the available space.
    219   int icon_width = IconWidth(false);
    220   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    221     BrowserActionView* view = browser_action_views_[i];
    222     int x = ToolbarView::kStandardSpacing + (i * IconWidth(true));
    223     if (x + icon_width <= max_x) {
    224       view->SetBounds(x, 0, icon_width, height());
    225       view->SetVisible(true);
    226     } else {
    227       view->SetVisible(false);
    228     }
    229   }
    230 }
    231 
    232 bool BrowserActionsContainer::GetDropFormats(
    233     int* formats,
    234     std::set<OSExchangeData::CustomFormat>* custom_formats) {
    235   custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
    236 
    237   return true;
    238 }
    239 
    240 bool BrowserActionsContainer::AreDropTypesRequired() {
    241   return true;
    242 }
    243 
    244 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
    245   BrowserActionDragData drop_data;
    246   return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false;
    247 }
    248 
    249 void BrowserActionsContainer::OnDragEntered(
    250     const ui::DropTargetEvent& event) {
    251 }
    252 
    253 int BrowserActionsContainer::OnDragUpdated(
    254     const ui::DropTargetEvent& event) {
    255   // First check if we are above the chevron (overflow) menu.
    256   if (GetEventHandlerForPoint(event.location()) == chevron_) {
    257     if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_)
    258       StartShowFolderDropMenuTimer();
    259     return ui::DragDropTypes::DRAG_MOVE;
    260   }
    261   StopShowFolderDropMenuTimer();
    262 
    263   // Figure out where to display the indicator.  This is a complex calculation:
    264 
    265   // First, we figure out how much space is to the left of the icon area, so we
    266   // can calculate the true offset into the icon area.
    267   int width_before_icons = ToolbarView::kStandardSpacing +
    268       (base::i18n::IsRTL() ?
    269           (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0);
    270   int offset_into_icon_area = event.x() - width_before_icons;
    271 
    272   // Next, we determine which icon to place the indicator in front of.  We want
    273   // to place the indicator in front of icon n when the cursor is between the
    274   // midpoints of icons (n - 1) and n.  To do this we take the offset into the
    275   // icon area and transform it as follows:
    276   //
    277   // Real icon area:
    278   //   0   a     *  b        c
    279   //   |   |        |        |
    280   //   |[IC|ON]  [IC|ON]  [IC|ON]
    281   // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
    282   // Here the "*" represents the offset into the icon area, and since it's
    283   // between a and b, we want to return "1".
    284   //
    285   // Transformed "icon area":
    286   //   0        a     *  b        c
    287   //   |        |        |        |
    288   //   |[ICON]  |[ICON]  |[ICON]  |
    289   // If we shift both our offset and our divider points later by half an icon
    290   // plus one spacing unit, then it becomes very easy to calculate how many
    291   // divider points we've passed, because they're the multiples of "one icon
    292   // plus padding".
    293   int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
    294       kItemSpacing) / IconWidth(true);
    295 
    296   // Because the user can drag outside the container bounds, we need to clamp to
    297   // the valid range.  Note that the maximum allowable value is (num icons), not
    298   // (num icons - 1), because we represent the indicator being past the last
    299   // icon as being "before the (last + 1) icon".
    300   int before_icon = std::min(std::max(before_icon_unclamped, 0),
    301                              static_cast<int>(VisibleBrowserActions()));
    302 
    303   // Now we convert back to a pixel offset into the container.  We want to place
    304   // the center of the drop indicator at the midpoint of the space before our
    305   // chosen icon.
    306   SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) -
    307       (kItemSpacing / 2));
    308 
    309   return ui::DragDropTypes::DRAG_MOVE;
    310 }
    311 
    312 void BrowserActionsContainer::OnDragExited() {
    313   StopShowFolderDropMenuTimer();
    314   drop_indicator_position_ = -1;
    315   SchedulePaint();
    316 }
    317 
    318 int BrowserActionsContainer::OnPerformDrop(
    319     const ui::DropTargetEvent& event) {
    320   BrowserActionDragData data;
    321   if (!data.Read(event.data()))
    322     return ui::DragDropTypes::DRAG_NONE;
    323 
    324   // Make sure we have the same view as we started with.
    325   DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(),
    326             data.id());
    327   DCHECK(model_);
    328 
    329   size_t i = 0;
    330   for (; i < browser_action_views_.size(); ++i) {
    331     int view_x = browser_action_views_[i]->GetMirroredBounds().x();
    332     if (!browser_action_views_[i]->visible() ||
    333         (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) :
    334             (view_x >= drop_indicator_position_))) {
    335       // We have reached the end of the visible icons or found one that has a
    336       // higher x position than the drop point.
    337       break;
    338     }
    339   }
    340 
    341   // |i| now points to the item to the right of the drop indicator*, which is
    342   // correct when dragging an icon to the left. When dragging to the right,
    343   // however, we want the icon being dragged to get the index of the item to
    344   // the left of the drop indicator, so we subtract one.
    345   // * Well, it can also point to the end, but not when dragging to the left. :)
    346   if (i > data.index())
    347     --i;
    348 
    349   if (profile_->IsOffTheRecord())
    350     i = model_->IncognitoIndexToOriginal(i);
    351 
    352   model_->MoveBrowserAction(
    353       browser_action_views_[data.index()]->button()->extension(), i);
    354 
    355   OnDragExited();  // Perform clean up after dragging.
    356   return ui::DragDropTypes::DRAG_MOVE;
    357 }
    358 
    359 void BrowserActionsContainer::GetAccessibleState(
    360     ui::AccessibleViewState* state) {
    361   state->role = ui::AccessibilityTypes::ROLE_GROUPING;
    362   state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
    363 }
    364 
    365 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source,
    366                                                   const gfx::Point& point) {
    367   if (source == chevron_) {
    368     overflow_menu_ = new BrowserActionOverflowMenuController(
    369         this, browser_, chevron_, browser_action_views_,
    370         VisibleBrowserActions());
    371     overflow_menu_->set_observer(this);
    372     overflow_menu_->RunMenu(GetWidget(), false);
    373   }
    374 }
    375 
    376 void BrowserActionsContainer::WriteDragDataForView(View* sender,
    377                                                    const gfx::Point& press_pt,
    378                                                    OSExchangeData* data) {
    379   DCHECK(data);
    380 
    381   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    382     BrowserActionButton* button = browser_action_views_[i]->button();
    383     if (button == sender) {
    384       // Set the dragging image for the icon.
    385       gfx::ImageSkia badge(browser_action_views_[i]->GetIconWithBadge());
    386       drag_utils::SetDragImageOnDataObject(badge, button->size(),
    387                                            press_pt.OffsetFromOrigin(),
    388                                            data);
    389 
    390       // Fill in the remaining info.
    391       BrowserActionDragData drag_data(
    392           browser_action_views_[i]->button()->extension()->id(), i);
    393       drag_data.Write(profile_, data);
    394       break;
    395     }
    396   }
    397 }
    398 
    399 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
    400                                                       const gfx::Point& p) {
    401   return ui::DragDropTypes::DRAG_MOVE;
    402 }
    403 
    404 bool BrowserActionsContainer::CanStartDragForView(View* sender,
    405                                                   const gfx::Point& press_pt,
    406                                                   const gfx::Point& p) {
    407   return true;
    408 }
    409 
    410 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
    411   if (!done_resizing) {
    412     resize_amount_ = resize_amount;
    413     OnBrowserActionVisibilityChanged();
    414     return;
    415   }
    416 
    417   // Up until now we've only been modifying the resize_amount, but now it is
    418   // time to set the container size to the size we have resized to, and then
    419   // animate to the nearest icon count size if necessary (which may be 0).
    420   int max_width = IconCountToWidth(-1, false);
    421   container_width_ =
    422       std::min(std::max(0, container_width_ - resize_amount), max_width);
    423   SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT,
    424                             WidthToIconCount(container_width_));
    425 }
    426 
    427 void BrowserActionsContainer::AnimationProgressed(
    428     const ui::Animation* animation) {
    429   DCHECK_EQ(resize_animation_.get(), animation);
    430   resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
    431       (container_width_ - animation_target_size_));
    432   OnBrowserActionVisibilityChanged();
    433 }
    434 
    435 void BrowserActionsContainer::AnimationEnded(const ui::Animation* animation) {
    436   container_width_ = animation_target_size_;
    437   animation_target_size_ = 0;
    438   resize_amount_ = 0;
    439   OnBrowserActionVisibilityChanged();
    440   suppress_chevron_ = false;
    441 }
    442 
    443 void BrowserActionsContainer::NotifyMenuDeleted(
    444     BrowserActionOverflowMenuController* controller) {
    445   DCHECK_EQ(overflow_menu_, controller);
    446   overflow_menu_ = NULL;
    447 }
    448 
    449 void BrowserActionsContainer::OnWidgetDestroying(views::Widget* widget) {
    450   DCHECK_EQ(popup_->GetWidget(), widget);
    451   popup_->GetWidget()->RemoveObserver(this);
    452   popup_ = NULL;
    453   // |popup_button_| is NULL if the extension has been removed.
    454   if (popup_button_) {
    455     popup_button_->SetButtonNotPushed();
    456     popup_button_ = NULL;
    457   }
    458 }
    459 
    460 void BrowserActionsContainer::InspectPopup(ExtensionAction* action) {
    461   BrowserActionView* view = GetBrowserActionView(action);
    462   ShowPopup(view->button(), ExtensionPopup::SHOW_AND_INSPECT);
    463 }
    464 
    465 int BrowserActionsContainer::GetCurrentTabId() const {
    466   content::WebContents* active_tab =
    467       browser_->tab_strip_model()->GetActiveWebContents();
    468   if (!active_tab)
    469     return -1;
    470 
    471   return SessionTabHelper::FromWebContents(active_tab)->session_id().id();
    472 }
    473 
    474 void BrowserActionsContainer::OnBrowserActionExecuted(
    475     BrowserActionButton* button) {
    476   ShowPopup(button, ExtensionPopup::SHOW);
    477 }
    478 
    479 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
    480   SetVisible(!browser_action_views_.empty());
    481   owner_view_->Layout();
    482   owner_view_->SchedulePaint();
    483 }
    484 
    485 gfx::Point BrowserActionsContainer::GetViewContentOffset() const {
    486   return gfx::Point(0, ToolbarView::kVertSpacing);
    487 }
    488 
    489 extensions::ActiveTabPermissionGranter*
    490     BrowserActionsContainer::GetActiveTabPermissionGranter() {
    491   content::WebContents* web_contents =
    492       browser_->tab_strip_model()->GetActiveWebContents();
    493   if (!web_contents)
    494     return NULL;
    495   return extensions::TabHelper::FromWebContents(web_contents)->
    496       active_tab_permission_granter();
    497 }
    498 
    499 void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
    500                                                 size_t new_index) {
    501   ExtensionService* service =
    502       extensions::ExtensionSystem::Get(profile_)->extension_service();
    503   if (service) {
    504     const Extension* extension = service->GetExtensionById(extension_id, false);
    505     model_->MoveBrowserAction(extension, new_index);
    506     SchedulePaint();
    507   }
    508 }
    509 
    510 void BrowserActionsContainer::HidePopup() {
    511   // Remove this as an observer and clear |popup_| and |popup_button_| here,
    512   // since we might change them before OnWidgetDestroying() gets called.
    513   if (popup_) {
    514     popup_->GetWidget()->RemoveObserver(this);
    515     popup_->GetWidget()->Close();
    516     popup_ = NULL;
    517   }
    518   if (popup_button_) {
    519     popup_button_->SetButtonNotPushed();
    520     popup_button_ = NULL;
    521   }
    522 }
    523 
    524 void BrowserActionsContainer::TestExecuteBrowserAction(int index) {
    525   BrowserActionButton* button = browser_action_views_[index]->button();
    526   OnBrowserActionExecuted(button);
    527 }
    528 
    529 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
    530   model_->SetVisibleIconCount(icons);
    531   chevron_->SetVisible(icons < browser_action_views_.size());
    532   container_width_ = IconCountToWidth(icons, chevron_->visible());
    533   Layout();
    534   SchedulePaint();
    535 }
    536 
    537 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
    538   // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
    539   // dragging (like we do for tab dragging).
    540   if (drop_indicator_position_ > -1) {
    541     // The two-pixel width drop indicator.
    542     static const int kDropIndicatorWidth = 2;
    543     gfx::Rect indicator_bounds(
    544         drop_indicator_position_ - (kDropIndicatorWidth / 2),
    545         ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight());
    546 
    547     // Color of the drop indicator.
    548     static const SkColor kDropIndicatorColor = SK_ColorBLACK;
    549     canvas->FillRect(indicator_bounds, kDropIndicatorColor);
    550   }
    551 }
    552 
    553 void BrowserActionsContainer::OnThemeChanged() {
    554   LoadImages();
    555 }
    556 
    557 void BrowserActionsContainer::ViewHierarchyChanged(
    558     const ViewHierarchyChangedDetails& details) {
    559   // No extensions (e.g., incognito).
    560   if (!model_)
    561     return;
    562 
    563   if (details.is_add && details.child == this) {
    564     // Initial toolbar button creation and placement in the widget hierarchy.
    565     // We do this here instead of in the constructor because AddBrowserAction
    566     // calls Layout on the Toolbar, which needs this object to be constructed
    567     // before its Layout function is called.
    568     CreateBrowserActionViews();
    569   }
    570 }
    571 
    572 // static
    573 int BrowserActionsContainer::IconWidth(bool include_padding) {
    574   static bool initialized = false;
    575   static int icon_width = 0;
    576   if (!initialized) {
    577     initialized = true;
    578     icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    579         IDR_BROWSER_ACTION)->width();
    580   }
    581   return icon_width + (include_padding ? kItemSpacing : 0);
    582 }
    583 
    584 // static
    585 int BrowserActionsContainer::IconHeight() {
    586   static bool initialized = false;
    587   static int icon_height = 0;
    588   if (!initialized) {
    589     initialized = true;
    590     icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    591         IDR_BROWSER_ACTION)->height();
    592   }
    593   return icon_height;
    594 }
    595 
    596 void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
    597                                                  int index) {
    598 #if defined(DEBUG)
    599   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
    600     DCHECK(browser_action_views_[i]->button()->extension() != extension) <<
    601            "Asked to add a browser action view for an extension that already "
    602            "exists.";
    603   }
    604 #endif
    605   CloseOverflowMenu();
    606 
    607   if (!ShouldDisplayBrowserAction(extension))
    608     return;
    609 
    610   size_t visible_actions = VisibleBrowserActions();
    611 
    612   // Add the new browser action to the vector and the view hierarchy.
    613   if (profile_->IsOffTheRecord())
    614     index = model_->OriginalIndexToIncognito(index);
    615   BrowserActionView* view = new BrowserActionView(extension, browser_, this);
    616   browser_action_views_.insert(browser_action_views_.begin() + index, view);
    617   AddChildViewAt(view, index);
    618 
    619   // If we are still initializing the container, don't bother animating.
    620   if (!model_->extensions_initialized())
    621     return;
    622 
    623   // Enlarge the container if it was already at maximum size and we're not in
    624   // the middle of upgrading.
    625   if ((model_->GetVisibleIconCount() < 0) &&
    626       !extensions::ExtensionSystem::Get(profile_)->extension_service()->
    627           IsBeingUpgraded(extension)) {
    628     suppress_chevron_ = true;
    629     SaveDesiredSizeAndAnimate(ui::Tween::LINEAR, visible_actions + 1);
    630   } else {
    631     // Just redraw the (possibly modified) visible icon set.
    632     OnBrowserActionVisibilityChanged();
    633   }
    634 }
    635 
    636 void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
    637   CloseOverflowMenu();
    638 
    639   if (popup_ && popup_->host()->extension() == extension)
    640     HidePopup();
    641 
    642   size_t visible_actions = VisibleBrowserActions();
    643   for (BrowserActionViews::iterator i(browser_action_views_.begin());
    644        i != browser_action_views_.end(); ++i) {
    645     if ((*i)->button()->extension() == extension) {
    646       delete *i;
    647       browser_action_views_.erase(i);
    648 
    649       // If the extension is being upgraded we don't want the bar to shrink
    650       // because the icon is just going to get re-added to the same location.
    651       if (extensions::ExtensionSystem::Get(profile_)->extension_service()->
    652               IsBeingUpgraded(extension))
    653         return;
    654 
    655       if (browser_action_views_.size() > visible_actions) {
    656         // If we have more icons than we can show, then we must not be changing
    657         // the container size (since we either removed an icon from the main
    658         // area and one from the overflow list will have shifted in, or we
    659         // removed an entry directly from the overflow list).
    660         OnBrowserActionVisibilityChanged();
    661       } else {
    662         // Either we went from overflow to no-overflow, or we shrunk the no-
    663         // overflow container by 1.  Either way the size changed, so animate.
    664         chevron_->SetVisible(false);
    665         SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT,
    666                                   browser_action_views_.size());
    667       }
    668       return;
    669     }
    670   }
    671 }
    672 
    673 void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
    674                                                  int index) {
    675   if (!ShouldDisplayBrowserAction(extension))
    676     return;
    677 
    678   if (profile_->IsOffTheRecord())
    679     index = model_->OriginalIndexToIncognito(index);
    680 
    681   DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
    682 
    683   DeleteBrowserActionViews();
    684   CreateBrowserActionViews();
    685   Layout();
    686   SchedulePaint();
    687 }
    688 
    689 void BrowserActionsContainer::ModelLoaded() {
    690   SetContainerWidth();
    691 }
    692 
    693 void BrowserActionsContainer::LoadImages() {
    694   ui::ThemeProvider* tp = GetThemeProvider();
    695   chevron_->SetIcon(*tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
    696   chevron_->SetHoverIcon(*tp->GetImageSkiaNamed(
    697       IDR_BROWSER_ACTIONS_OVERFLOW_H));
    698   chevron_->SetPushedIcon(*tp->GetImageSkiaNamed(
    699       IDR_BROWSER_ACTIONS_OVERFLOW_P));
    700 }
    701 
    702 void BrowserActionsContainer::SetContainerWidth() {
    703   int visible_actions = model_->GetVisibleIconCount();
    704   if (visible_actions < 0)  // All icons should be visible.
    705     visible_actions = model_->toolbar_items().size();
    706   chevron_->SetVisible(
    707     static_cast<size_t>(visible_actions) < model_->toolbar_items().size());
    708   container_width_ = IconCountToWidth(visible_actions, chevron_->visible());
    709 }
    710 
    711 void BrowserActionsContainer::CloseOverflowMenu() {
    712   if (overflow_menu_)
    713     overflow_menu_->CancelMenu();
    714 }
    715 
    716 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
    717   show_menu_task_factory_.InvalidateWeakPtrs();
    718 }
    719 
    720 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
    721   base::MessageLoop::current()->PostDelayedTask(
    722       FROM_HERE,
    723       base::Bind(&BrowserActionsContainer::ShowDropFolder,
    724                  show_menu_task_factory_.GetWeakPtr()),
    725       base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
    726 }
    727 
    728 void BrowserActionsContainer::ShowDropFolder() {
    729   DCHECK(!overflow_menu_);
    730   SetDropIndicator(-1);
    731   overflow_menu_ = new BrowserActionOverflowMenuController(
    732       this, browser_, chevron_, browser_action_views_, VisibleBrowserActions());
    733   overflow_menu_->set_observer(this);
    734   overflow_menu_->RunMenu(GetWidget(), true);
    735 }
    736 
    737 void BrowserActionsContainer::SetDropIndicator(int x_pos) {
    738   if (drop_indicator_position_ != x_pos) {
    739     drop_indicator_position_ = x_pos;
    740     SchedulePaint();
    741   }
    742 }
    743 
    744 int BrowserActionsContainer::IconCountToWidth(int icons,
    745                                               bool display_chevron) const {
    746   if (icons < 0)
    747     icons = browser_action_views_.size();
    748   if ((icons == 0) && !display_chevron)
    749     return ToolbarView::kStandardSpacing;
    750   int icons_size =
    751       (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
    752   int chevron_size = display_chevron ?
    753       (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
    754   return ToolbarView::kStandardSpacing + icons_size + chevron_size +
    755       ToolbarView::kStandardSpacing;
    756 }
    757 
    758 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
    759   // Check for widths large enough to show the entire icon set.
    760   if (pixels >= IconCountToWidth(-1, false))
    761     return browser_action_views_.size();
    762 
    763   // We need to reserve space for the resize area, chevron, and the spacing on
    764   // either side of the chevron.
    765   int available_space = pixels - ToolbarView::kStandardSpacing -
    766       chevron_->GetPreferredSize().width() - kChevronSpacing -
    767       ToolbarView::kStandardSpacing;
    768   // Now we add an extra between-item padding value so the space can be divided
    769   // evenly by (size of icon with padding).
    770   return static_cast<size_t>(
    771       std::max(0, available_space + kItemSpacing) / IconWidth(true));
    772 }
    773 
    774 int BrowserActionsContainer::ContainerMinSize() const {
    775   return ToolbarView::kStandardSpacing + kChevronSpacing +
    776       chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing;
    777 }
    778 
    779 void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
    780     ui::Tween::Type tween_type,
    781     size_t num_visible_icons) {
    782   // Save off the desired number of visible icons.  We do this now instead of at
    783   // the end of the animation so that even if the browser is shut down while
    784   // animating, the right value will be restored on next run.
    785   // NOTE: Don't save the icon count in incognito because there may be fewer
    786   // icons in that mode. The result is that the container in a normal window is
    787   // always at least as wide as in an incognito window.
    788   if (!profile_->IsOffTheRecord())
    789     model_->SetVisibleIconCount(num_visible_icons);
    790 
    791   int target_size = IconCountToWidth(num_visible_icons,
    792       num_visible_icons < browser_action_views_.size());
    793   if (!disable_animations_during_testing_) {
    794     // Animate! We have to set the animation_target_size_ after calling Reset(),
    795     // because that could end up calling AnimationEnded which clears the value.
    796     resize_animation_->Reset();
    797     resize_animation_->SetTweenType(tween_type);
    798     animation_target_size_ = target_size;
    799     resize_animation_->Show();
    800   } else {
    801     animation_target_size_ = target_size;
    802     AnimationEnded(resize_animation_.get());
    803   }
    804 }
    805 
    806 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
    807     const Extension* extension) {
    808   // Only display incognito-enabled extensions while in incognito mode.
    809   return
    810       (!profile_->IsOffTheRecord() ||
    811        extensions::ExtensionSystem::Get(profile_)->extension_service()->
    812            IsIncognitoEnabled(extension->id()));
    813 }
    814 
    815 void BrowserActionsContainer::ShowPopup(
    816     BrowserActionButton* button,
    817     ExtensionPopup::ShowAction show_action) {
    818   const Extension* extension = button->extension();
    819   GURL popup_url;
    820   if (model_->ExecuteBrowserAction(extension, browser_, &popup_url) !=
    821       ExtensionToolbarModel::ACTION_SHOW_POPUP) {
    822     return;
    823   }
    824 
    825   // If we're showing the same popup, just hide it and return.
    826   bool same_showing = popup_ && button == popup_button_;
    827 
    828   // Always hide the current popup, even if it's not the same.
    829   // Only one popup should be visible at a time.
    830   HidePopup();
    831 
    832   if (same_showing)
    833     return;
    834 
    835   // We can get the execute event for browser actions that are not visible,
    836   // since buttons can be activated from the overflow menu (chevron). In that
    837   // case we show the popup as originating from the chevron.
    838   View* reference_view = button->parent()->visible() ? button : chevron_;
    839   views::BubbleBorder::Arrow arrow = base::i18n::IsRTL() ?
    840       views::BubbleBorder::TOP_LEFT : views::BubbleBorder::TOP_RIGHT;
    841   popup_ = ExtensionPopup::ShowPopup(popup_url,
    842                                      browser_,
    843                                      reference_view,
    844                                      arrow,
    845                                      show_action);
    846   popup_->GetWidget()->AddObserver(this);
    847   popup_button_ = button;
    848   popup_button_->SetButtonPushed();
    849 }
    850