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