browser_actions_container.cc revision f2477e01787aa58f445919b809d89e252beef54f
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/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/extension_util.h"
13#include "chrome/browser/extensions/extension_view_host.h"
14#include "chrome/browser/extensions/tab_helper.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/sessions/session_tab_helper.h"
17#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_window.h"
19#include "chrome/browser/ui/tabs/tab_strip_model.h"
20#include "chrome/browser/ui/view_ids.h"
21#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
22#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
23#include "chrome/browser/ui/views/extensions/extension_popup.h"
24#include "chrome/browser/ui/views/toolbar/browser_action_view.h"
25#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
26#include "chrome/common/pref_names.h"
27#include "grit/generated_resources.h"
28#include "grit/theme_resources.h"
29#include "grit/ui_resources.h"
30#include "ui/base/accessibility/accessible_view_state.h"
31#include "ui/base/dragdrop/drag_utils.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/base/resource/resource_bundle.h"
34#include "ui/base/theme_provider.h"
35#include "ui/gfx/animation/slide_animation.h"
36#include "ui/gfx/canvas.h"
37#include "ui/views/controls/resize_area.h"
38#include "ui/views/metrics.h"
39#include "ui/views/widget/widget.h"
40
41using extensions::Extension;
42
43namespace {
44
45// Horizontal spacing between most items in the container, as well as after the
46// last item or chevron (if visible).
47const int kItemSpacing = ToolbarView::kStandardSpacing;
48
49// Horizontal spacing before the chevron (if visible).
50const int kChevronSpacing = kItemSpacing - 2;
51
52}  // namespace
53
54// static
55bool BrowserActionsContainer::disable_animations_during_testing_ = false;
56
57////////////////////////////////////////////////////////////////////////////////
58// BrowserActionsContainer
59
60BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
61                                                 View* owner_view)
62    : profile_(browser->profile()),
63      browser_(browser),
64      owner_view_(owner_view),
65      popup_(NULL),
66      popup_button_(NULL),
67      model_(NULL),
68      container_width_(0),
69      chevron_(NULL),
70      overflow_menu_(NULL),
71      suppress_chevron_(false),
72      resize_amount_(0),
73      animation_target_size_(0),
74      drop_indicator_position_(-1),
75      task_factory_(this),
76      show_menu_task_factory_(this) {
77  set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
78
79  model_ = ExtensionToolbarModel::Get(browser->profile());
80  if (model_)
81    model_->AddObserver(this);
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 gfx::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
102BrowserActionsContainer::~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
114void 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
132BrowserActionView* 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
142void BrowserActionsContainer::RefreshBrowserActionViews() {
143  for (size_t i = 0; i < browser_action_views_.size(); ++i)
144    browser_action_views_[i]->button()->UpdateState();
145}
146
147void 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
164void BrowserActionsContainer::DeleteBrowserActionViews() {
165  HidePopup();
166  STLDeleteElements(&browser_action_views_);
167}
168
169size_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
178gfx::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
194void 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
232bool BrowserActionsContainer::GetDropFormats(
233    int* formats,
234    std::set<OSExchangeData::CustomFormat>* custom_formats) {
235  custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
236
237  return true;
238}
239
240bool BrowserActionsContainer::AreDropTypesRequired() {
241  return true;
242}
243
244bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
245  BrowserActionDragData drop_data;
246  return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false;
247}
248
249void BrowserActionsContainer::OnDragEntered(
250    const ui::DropTargetEvent& event) {
251}
252
253int 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
312void BrowserActionsContainer::OnDragExited() {
313  StopShowFolderDropMenuTimer();
314  drop_indicator_position_ = -1;
315  SchedulePaint();
316}
317
318int 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
359void BrowserActionsContainer::GetAccessibleState(
360    ui::AccessibleViewState* state) {
361  state->role = ui::AccessibilityTypes::ROLE_GROUPING;
362  state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
363}
364
365void 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
376void 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
399int BrowserActionsContainer::GetDragOperationsForView(View* sender,
400                                                      const gfx::Point& p) {
401  return ui::DragDropTypes::DRAG_MOVE;
402}
403
404bool BrowserActionsContainer::CanStartDragForView(View* sender,
405                                                  const gfx::Point& press_pt,
406                                                  const gfx::Point& p) {
407  return true;
408}
409
410void 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(gfx::Tween::EASE_OUT,
424                            WidthToIconCount(container_width_));
425}
426
427void BrowserActionsContainer::AnimationProgressed(
428    const gfx::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
435void BrowserActionsContainer::AnimationEnded(const gfx::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
443void BrowserActionsContainer::NotifyMenuDeleted(
444    BrowserActionOverflowMenuController* controller) {
445  DCHECK_EQ(overflow_menu_, controller);
446  overflow_menu_ = NULL;
447}
448
449void 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
460void BrowserActionsContainer::InspectPopup(ExtensionAction* action) {
461  BrowserActionView* view = GetBrowserActionView(action);
462  ShowPopup(view->button(), ExtensionPopup::SHOW_AND_INSPECT, true);
463}
464
465int 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
474void BrowserActionsContainer::OnBrowserActionExecuted(
475    BrowserActionButton* button) {
476  ShowPopup(button, ExtensionPopup::SHOW, true);
477}
478
479void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
480  SetVisible(!browser_action_views_.empty());
481  owner_view_->Layout();
482  owner_view_->SchedulePaint();
483}
484
485gfx::Point BrowserActionsContainer::GetViewContentOffset() const {
486  return gfx::Point(0, ToolbarView::kVertSpacing);
487}
488
489extensions::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
499void 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
510void 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
524void BrowserActionsContainer::TestExecuteBrowserAction(int index) {
525  BrowserActionButton* button = browser_action_views_[index]->button();
526  OnBrowserActionExecuted(button);
527}
528
529void 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
537void 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
553void BrowserActionsContainer::OnThemeChanged() {
554  LoadImages();
555}
556
557void 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
573int 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
585int 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
596void 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(gfx::Tween::LINEAR, visible_actions + 1);
630  } else {
631    // Just redraw the (possibly modified) visible icon set.
632    OnBrowserActionVisibilityChanged();
633  }
634}
635
636void 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(gfx::Tween::EASE_OUT,
666                                  browser_action_views_.size());
667      }
668      return;
669    }
670  }
671}
672
673void 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
689bool BrowserActionsContainer::BrowserActionShowPopup(
690    const extensions::Extension* extension) {
691  // Do not override other popups and only show in active window. The window
692  // must also have a toolbar, otherwise it should not be showing popups.
693  // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
694  // fixed.
695  if (popup_ ||
696      !browser_->window()->IsActive() ||
697      !browser_->window()->IsToolbarVisible()) {
698    return false;
699  }
700
701  for (BrowserActionViews::iterator it = browser_action_views_.begin();
702       it != browser_action_views_.end(); ++it) {
703    BrowserActionButton* button = (*it)->button();
704    if (button && button->extension() == extension)
705      return ShowPopup(button, ExtensionPopup::SHOW, false);
706  }
707  return false;
708}
709
710void BrowserActionsContainer::ModelLoaded() {
711  SetContainerWidth();
712}
713
714void BrowserActionsContainer::LoadImages() {
715  ui::ThemeProvider* tp = GetThemeProvider();
716  chevron_->SetIcon(*tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
717  chevron_->SetHoverIcon(*tp->GetImageSkiaNamed(
718      IDR_BROWSER_ACTIONS_OVERFLOW_H));
719  chevron_->SetPushedIcon(*tp->GetImageSkiaNamed(
720      IDR_BROWSER_ACTIONS_OVERFLOW_P));
721}
722
723void BrowserActionsContainer::SetContainerWidth() {
724  int visible_actions = model_->GetVisibleIconCount();
725  if (visible_actions < 0)  // All icons should be visible.
726    visible_actions = model_->toolbar_items().size();
727  chevron_->SetVisible(
728    static_cast<size_t>(visible_actions) < model_->toolbar_items().size());
729  container_width_ = IconCountToWidth(visible_actions, chevron_->visible());
730}
731
732void BrowserActionsContainer::CloseOverflowMenu() {
733  if (overflow_menu_)
734    overflow_menu_->CancelMenu();
735}
736
737void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
738  show_menu_task_factory_.InvalidateWeakPtrs();
739}
740
741void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
742  base::MessageLoop::current()->PostDelayedTask(
743      FROM_HERE,
744      base::Bind(&BrowserActionsContainer::ShowDropFolder,
745                 show_menu_task_factory_.GetWeakPtr()),
746      base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
747}
748
749void BrowserActionsContainer::ShowDropFolder() {
750  DCHECK(!overflow_menu_);
751  SetDropIndicator(-1);
752  overflow_menu_ = new BrowserActionOverflowMenuController(
753      this, browser_, chevron_, browser_action_views_, VisibleBrowserActions());
754  overflow_menu_->set_observer(this);
755  overflow_menu_->RunMenu(GetWidget(), true);
756}
757
758void BrowserActionsContainer::SetDropIndicator(int x_pos) {
759  if (drop_indicator_position_ != x_pos) {
760    drop_indicator_position_ = x_pos;
761    SchedulePaint();
762  }
763}
764
765int BrowserActionsContainer::IconCountToWidth(int icons,
766                                              bool display_chevron) const {
767  if (icons < 0)
768    icons = browser_action_views_.size();
769  if ((icons == 0) && !display_chevron)
770    return ToolbarView::kStandardSpacing;
771  int icons_size =
772      (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
773  int chevron_size = display_chevron ?
774      (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
775  return ToolbarView::kStandardSpacing + icons_size + chevron_size +
776      ToolbarView::kStandardSpacing;
777}
778
779size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
780  // Check for widths large enough to show the entire icon set.
781  if (pixels >= IconCountToWidth(-1, false))
782    return browser_action_views_.size();
783
784  // We need to reserve space for the resize area, chevron, and the spacing on
785  // either side of the chevron.
786  int available_space = pixels - ToolbarView::kStandardSpacing -
787      chevron_->GetPreferredSize().width() - kChevronSpacing -
788      ToolbarView::kStandardSpacing;
789  // Now we add an extra between-item padding value so the space can be divided
790  // evenly by (size of icon with padding).
791  return static_cast<size_t>(
792      std::max(0, available_space + kItemSpacing) / IconWidth(true));
793}
794
795int BrowserActionsContainer::ContainerMinSize() const {
796  return ToolbarView::kStandardSpacing + kChevronSpacing +
797      chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing;
798}
799
800void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
801    gfx::Tween::Type tween_type,
802    size_t num_visible_icons) {
803  // Save off the desired number of visible icons.  We do this now instead of at
804  // the end of the animation so that even if the browser is shut down while
805  // animating, the right value will be restored on next run.
806  // NOTE: Don't save the icon count in incognito because there may be fewer
807  // icons in that mode. The result is that the container in a normal window is
808  // always at least as wide as in an incognito window.
809  if (!profile_->IsOffTheRecord())
810    model_->SetVisibleIconCount(num_visible_icons);
811
812  int target_size = IconCountToWidth(num_visible_icons,
813      num_visible_icons < browser_action_views_.size());
814  if (!disable_animations_during_testing_) {
815    // Animate! We have to set the animation_target_size_ after calling Reset(),
816    // because that could end up calling AnimationEnded which clears the value.
817    resize_animation_->Reset();
818    resize_animation_->SetTweenType(tween_type);
819    animation_target_size_ = target_size;
820    resize_animation_->Show();
821  } else {
822    animation_target_size_ = target_size;
823    AnimationEnded(resize_animation_.get());
824  }
825}
826
827bool BrowserActionsContainer::ShouldDisplayBrowserAction(
828    const Extension* extension) {
829  // Only display incognito-enabled extensions while in incognito mode.
830  return
831      (!profile_->IsOffTheRecord() ||
832       extension_util::IsIncognitoEnabled(
833           extension->id(),
834           extensions::ExtensionSystem::Get(profile_)->extension_service()));
835}
836
837bool BrowserActionsContainer::ShowPopup(
838    BrowserActionButton* button,
839    ExtensionPopup::ShowAction show_action,
840    bool should_grant) {
841  const Extension* extension = button->extension();
842  GURL popup_url;
843  if (model_->ExecuteBrowserAction(
844          extension, browser_, &popup_url, should_grant) !=
845      ExtensionToolbarModel::ACTION_SHOW_POPUP) {
846    return false;
847  }
848
849  // If we're showing the same popup, just hide it and return.
850  bool same_showing = popup_ && button == popup_button_;
851
852  // Always hide the current popup, even if it's not the same.
853  // Only one popup should be visible at a time.
854  HidePopup();
855
856  if (same_showing)
857    return false;
858
859  // We can get the execute event for browser actions that are not visible,
860  // since buttons can be activated from the overflow menu (chevron). In that
861  // case we show the popup as originating from the chevron.
862  View* reference_view = button->parent()->visible() ? button : chevron_;
863  views::BubbleBorder::Arrow arrow = base::i18n::IsRTL() ?
864      views::BubbleBorder::TOP_LEFT : views::BubbleBorder::TOP_RIGHT;
865  popup_ = ExtensionPopup::ShowPopup(popup_url,
866                                     browser_,
867                                     reference_view,
868                                     arrow,
869                                     show_action);
870  popup_->GetWidget()->AddObserver(this);
871  popup_button_ = button;
872
873  // Only set button as pushed if it was triggered by a user click.
874  if (should_grant)
875    popup_button_->SetButtonPushed();
876  return true;
877}
878