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