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