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.
5 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
7 #include "base/compiler_specific.h"
8 #include "base/stl_util.h"
9 #include "chrome/browser/extensions/extension_action_manager.h"
10 #include "chrome/browser/extensions/extension_util.h"
11 #include "chrome/browser/extensions/extension_view_host.h"
12 #include "chrome/browser/extensions/tab_helper.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_window.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/browser/ui/view_ids.h"
18 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
19 #include "chrome/browser/ui/views/extensions/extension_popup.h"
20 #include "chrome/browser/ui/views/frame/browser_view.h"
21 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
22 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
23 #include "chrome/common/extensions/command.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "extensions/browser/extension_registry.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/runtime_data.h"
28 #include "extensions/common/feature_switch.h"
29 #include "grit/theme_resources.h"
30 #include "third_party/skia/include/core/SkColor.h"
31 #include "ui/accessibility/ax_view_state.h"
32 #include "ui/base/dragdrop/drag_utils.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/nine_image_painter_factory.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/gfx/geometry/rect.h"
40 #include "ui/resources/grit/ui_resources.h"
41 #include "ui/views/controls/button/label_button_border.h"
42 #include "ui/views/controls/button/menu_button.h"
43 #include "ui/views/controls/resize_area.h"
44 #include "ui/views/metrics.h"
45 #include "ui/views/painter.h"
46 #include "ui/views/widget/widget.h"
48 using extensions::Extension;
52 // Horizontal spacing before the chevron (if visible).
53 const int kChevronSpacing = ToolbarView::kStandardSpacing - 2;
55 // A version of MenuButton with almost empty insets to fit properly on the
57 class ChevronMenuButton : public views::MenuButton {
59 ChevronMenuButton(views::ButtonListener* listener,
60 const base::string16& text,
61 views::MenuButtonListener* menu_button_listener,
62 bool show_menu_marker)
63 : views::MenuButton(listener,
69 virtual ~ChevronMenuButton() {}
71 virtual scoped_ptr<views::LabelButtonBorder> CreateDefaultBorder() const
73 // The chevron resource was designed to not have any insets.
74 scoped_ptr<views::LabelButtonBorder> border =
75 views::MenuButton::CreateDefaultBorder();
76 border->set_insets(gfx::Insets());
81 DISALLOW_COPY_AND_ASSIGN(ChevronMenuButton);
86 ////////////////////////////////////////////////////////////////////////////////
87 // BrowserActionsContainer::DropPosition
89 struct BrowserActionsContainer::DropPosition {
90 DropPosition(size_t row, size_t icon_in_row);
92 // The (0-indexed) row into which the action will be dropped.
95 // The (0-indexed) icon in the row before the action will be dropped.
99 BrowserActionsContainer::DropPosition::DropPosition(
100 size_t row, size_t icon_in_row)
101 : row(row), icon_in_row(icon_in_row) {
104 ////////////////////////////////////////////////////////////////////////////////
105 // BrowserActionsContainer
108 int BrowserActionsContainer::icons_per_overflow_menu_row_ = 1;
111 const int BrowserActionsContainer::kItemSpacing = ToolbarView::kStandardSpacing;
114 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
116 BrowserActionsContainer::BrowserActionsContainer(
119 BrowserActionsContainer* main_container)
120 : initialized_(false),
121 profile_(browser->profile()),
123 owner_view_(owner_view),
124 main_container_(main_container),
130 overflow_menu_(NULL),
131 suppress_chevron_(false),
133 animation_target_size_(0),
134 show_menu_task_factory_(this) {
135 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
137 model_ = extensions::ExtensionToolbarModel::Get(browser->profile());
139 model_->AddObserver(this);
141 bool overflow_experiment =
142 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
143 DCHECK(!in_overflow_mode() || overflow_experiment);
145 if (!in_overflow_mode()) {
146 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
148 owner_view->GetFocusManager(),
149 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
152 resize_animation_.reset(new gfx::SlideAnimation(this));
153 resize_area_ = new views::ResizeArea(this);
154 AddChildView(resize_area_);
156 // 'Main' mode doesn't need a chevron overflow when overflow is shown inside
158 if (!overflow_experiment) {
159 chevron_ = new ChevronMenuButton(NULL, base::string16(), this, false);
160 chevron_->EnableCanvasFlippingForRTLUI(true);
161 chevron_->SetAccessibleName(
162 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
163 chevron_->SetVisible(false);
164 AddChildView(chevron_);
169 BrowserActionsContainer::~BrowserActionsContainer() {
170 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
172 OnBrowserActionsContainerDestroyed());
175 overflow_menu_->set_observer(NULL);
177 model_->RemoveObserver(this);
178 StopShowFolderDropMenuTimer();
180 DeleteBrowserActionViews();
183 void BrowserActionsContainer::Init() {
186 // We wait to set the container width until now so that the chevron images
187 // will be loaded. The width calculation needs to know the chevron size.
188 if (model_ && model_->extensions_initialized()) {
189 container_width_ = GetPreferredWidth();
190 SetChevronVisibility();
196 BrowserActionView* BrowserActionsContainer::GetViewForExtension(
197 const Extension* extension) {
198 for (BrowserActionViews::iterator view = browser_action_views_.begin();
199 view != browser_action_views_.end(); ++view) {
200 if ((*view)->extension() == extension)
206 void BrowserActionsContainer::RefreshBrowserActionViews() {
207 for (size_t i = 0; i < browser_action_views_.size(); ++i)
208 browser_action_views_[i]->UpdateState();
211 void BrowserActionsContainer::CreateBrowserActionViews() {
212 DCHECK(browser_action_views_.empty());
216 extensions::ExtensionActionManager* action_manager =
217 extensions::ExtensionActionManager::Get(profile_);
218 const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
219 for (extensions::ExtensionList::const_iterator i(toolbar_items.begin());
220 i != toolbar_items.end(); ++i) {
221 if (!ShouldDisplayBrowserAction(i->get()))
224 BrowserActionView* view =
225 new BrowserActionView(i->get(),
226 action_manager->GetExtensionAction(**i),
229 browser_action_views_.push_back(view);
234 void BrowserActionsContainer::DeleteBrowserActionViews() {
237 overflow_menu_->NotifyBrowserActionViewsDeleting();
238 STLDeleteElements(&browser_action_views_);
241 size_t BrowserActionsContainer::VisibleBrowserActions() const {
242 size_t visible_actions = 0;
243 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
244 if (browser_action_views_[i]->visible())
247 return visible_actions;
250 size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const {
252 return VisibleBrowserActions();
254 return WidthToIconCount(animation_target_size_);
257 void BrowserActionsContainer::ExecuteExtensionCommand(
258 const extensions::Extension* extension,
259 const extensions::Command& command) {
260 // Global commands are handled by the ExtensionCommandsGlobalRegistry
262 DCHECK(!command.global());
263 extension_keybinding_registry_->ExecuteCommand(extension->id(),
264 command.accelerator());
267 void BrowserActionsContainer::NotifyActionMovedToOverflow() {
268 // When an action is moved to overflow, we shrink the size of the container
270 if (!profile_->IsOffTheRecord()) {
271 int icon_count = model_->GetVisibleIconCount();
272 // Since this happens when an icon moves from the main bar to overflow, we
273 // can't possibly have had no visible icons on the main bar.
274 DCHECK_NE(0, icon_count);
275 if (icon_count == -1)
276 icon_count = browser_action_views_.size();
277 model_->SetVisibleIconCount(icon_count - 1);
279 Animate(gfx::Tween::EASE_OUT,
280 VisibleBrowserActionsAfterAnimation() - 1);
283 bool BrowserActionsContainer::ShownInsideMenu() const {
284 return in_overflow_mode();
287 void BrowserActionsContainer::OnBrowserActionViewDragDone() {
288 ToolbarVisibleCountChanged();
289 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
291 OnBrowserActionDragDone());
294 views::MenuButton* BrowserActionsContainer::GetOverflowReferenceView() {
295 // With traditional overflow, the reference is the chevron. With the
296 // redesign, we use the wrench menu instead.
299 BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->app_menu();
302 void BrowserActionsContainer::SetPopupOwner(BrowserActionView* popup_owner) {
303 // We should never be setting a popup owner when one already exists, and
304 // never unsetting one when one wasn't set.
305 DCHECK((!popup_owner_ && popup_owner) ||
306 (popup_owner_ && !popup_owner));
307 popup_owner_ = popup_owner;
310 void BrowserActionsContainer::HideActivePopup() {
312 popup_owner_->view_controller()->HidePopup();
315 BrowserActionView* BrowserActionsContainer::GetMainViewForExtension(
316 const Extension* extension) {
317 return in_overflow_mode() ?
318 main_container_->GetViewForExtension(extension) :
319 GetViewForExtension(extension);
322 void BrowserActionsContainer::AddObserver(
323 BrowserActionsContainerObserver* observer) {
324 observers_.AddObserver(observer);
327 void BrowserActionsContainer::RemoveObserver(
328 BrowserActionsContainerObserver* observer) {
329 observers_.RemoveObserver(observer);
332 gfx::Size BrowserActionsContainer::GetPreferredSize() const {
333 if (in_overflow_mode()) {
334 int icon_count = GetIconCount();
335 // In overflow, we always have a preferred size of a full row (even if we
336 // don't use it), and always of at least one row. The parent may decide to
337 // show us even when empty, e.g. as a drag target for dragging in icons from
338 // the main container.
340 ((std::max(0, icon_count - 1)) / icons_per_overflow_menu_row_) + 1;
342 IconCountToWidth(icons_per_overflow_menu_row_, false),
343 row_count * IconHeight());
346 // If there are no actions to show, then don't show the container at all.
347 if (browser_action_views_.empty())
350 // We calculate the size of the view by taking the current width and
351 // subtracting resize_amount_ (the latter represents how far the user is
352 // resizing the view or, if animating the snapping, how far to animate it).
353 // But we also clamp it to a minimum size and the maximum size, so that the
354 // container can never shrink too far or take up more space than it needs.
355 // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX).
356 int preferred_width = std::min(
357 std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_),
358 IconCountToWidth(-1, false));
359 return gfx::Size(preferred_width, IconHeight());
362 int BrowserActionsContainer::GetHeightForWidth(int width) const {
363 if (in_overflow_mode())
364 icons_per_overflow_menu_row_ = (width - kItemSpacing) / IconWidth(true);
365 return GetPreferredSize().height();
368 gfx::Size BrowserActionsContainer::GetMinimumSize() const {
369 int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false));
370 return gfx::Size(min_width, IconHeight());
373 void BrowserActionsContainer::Layout() {
374 if (browser_action_views_.empty()) {
381 resize_area_->SetBounds(0, 0, kItemSpacing, height());
383 // If the icons don't all fit, show the chevron (unless suppressed).
384 int max_x = GetPreferredSize().width();
385 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_ && chevron_) {
386 chevron_->SetVisible(true);
387 gfx::Size chevron_size(chevron_->GetPreferredSize());
389 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
391 width() - ToolbarView::kStandardSpacing - chevron_size.width(),
393 chevron_size.width(),
394 chevron_size.height());
395 } else if (chevron_) {
396 chevron_->SetVisible(false);
399 // Now draw the icons for the browser actions in the available space.
400 int icon_width = IconWidth(false);
401 if (in_overflow_mode()) {
403 i < main_container_->VisibleBrowserActionsAfterAnimation(); ++i) {
404 // Ensure that any browser actions shown in the main view are hidden in
405 // the overflow view.
406 browser_action_views_[i]->SetVisible(false);
409 for (size_t i = main_container_->VisibleBrowserActionsAfterAnimation();
410 i < browser_action_views_.size(); ++i) {
411 BrowserActionView* view = browser_action_views_[i];
412 size_t index = i - main_container_->VisibleBrowserActionsAfterAnimation();
413 int row_index = static_cast<int>(index) / icons_per_overflow_menu_row_;
414 int x = kItemSpacing + (index * IconWidth(true)) -
415 (row_index * IconWidth(true) * icons_per_overflow_menu_row_);
416 gfx::Rect rect_bounds(
417 x, IconHeight() * row_index, icon_width, IconHeight());
418 view->SetBoundsRect(rect_bounds);
419 view->SetVisible(true);
422 for (BrowserActionViews::const_iterator it = browser_action_views_.begin();
423 it < browser_action_views_.end(); ++it) {
424 BrowserActionView* view = *it;
425 int x = ToolbarView::kStandardSpacing +
426 ((it - browser_action_views_.begin()) * IconWidth(true));
427 view->SetVisible(x + icon_width <= max_x);
429 view->SetBounds(x, 0, icon_width, IconHeight());
434 bool BrowserActionsContainer::GetDropFormats(
436 std::set<OSExchangeData::CustomFormat>* custom_formats) {
437 return BrowserActionDragData::GetDropFormats(custom_formats);
440 bool BrowserActionsContainer::AreDropTypesRequired() {
441 return BrowserActionDragData::AreDropTypesRequired();
444 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
445 return BrowserActionDragData::CanDrop(data, profile_);
448 int BrowserActionsContainer::OnDragUpdated(
449 const ui::DropTargetEvent& event) {
450 // First check if we are above the chevron (overflow) menu.
451 if (chevron_ && GetEventHandlerForPoint(event.location()) == chevron_) {
452 if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_)
453 StartShowFolderDropMenuTimer();
454 return ui::DragDropTypes::DRAG_MOVE;
456 StopShowFolderDropMenuTimer();
458 size_t row_index = 0;
459 size_t before_icon_in_row = 0;
460 // If there are no visible browser actions (such as when dragging an icon to
461 // an empty overflow/main container), then 0, 0 for row, column is correct.
462 if (VisibleBrowserActions() != 0) {
463 // Figure out where to display the indicator. This is a complex calculation:
465 // First, we subtract out the padding to the left of the icon area, which is
466 // ToolbarView::kStandardSpacing. If we're right-to-left, we also mirror the
467 // event.x() so that our calculations are consistent with left-to-right.
468 int offset_into_icon_area =
469 GetMirroredXInView(event.x()) - ToolbarView::kStandardSpacing;
471 // Next, figure out what row we're on. This only matters for overflow mode,
472 // but the calculation is the same for both.
473 row_index = event.y() / IconHeight();
475 // Sanity check - we should never be on a different row in the main
477 DCHECK(in_overflow_mode() || row_index == 0);
479 // Next, we determine which icon to place the indicator in front of. We want
480 // to place the indicator in front of icon n when the cursor is between the
481 // midpoints of icons (n - 1) and n. To do this we take the offset into the
482 // icon area and transform it as follows:
487 // |[IC|ON] [IC|ON] [IC|ON]
488 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
489 // Here the "*" represents the offset into the icon area, and since it's
490 // between a and b, we want to return "1".
492 // Transformed "icon area":
495 // |[ICON] |[ICON] |[ICON] |
496 // If we shift both our offset and our divider points later by half an icon
497 // plus one spacing unit, then it becomes very easy to calculate how many
498 // divider points we've passed, because they're the multiples of "one icon
500 int before_icon_unclamped =
501 (offset_into_icon_area + (IconWidth(false) / 2) +
502 kItemSpacing) / IconWidth(true);
504 // We need to figure out how many icons are visible on the relevant row.
505 // In the main container, this will just be the visible actions.
506 int visible_icons_on_row = VisibleBrowserActionsAfterAnimation();
507 if (in_overflow_mode()) {
508 // If this is the final row of the overflow, then this is the remainder of
509 // visible icons. Otherwise, it's a full row (kIconsPerRow).
510 visible_icons_on_row =
512 static_cast<size_t>(visible_icons_on_row /
513 icons_per_overflow_menu_row_) ?
514 visible_icons_on_row % icons_per_overflow_menu_row_ :
515 icons_per_overflow_menu_row_;
518 // Because the user can drag outside the container bounds, we need to clamp
519 // to the valid range. Note that the maximum allowable value is (num icons),
520 // not (num icons - 1), because we represent the indicator being past the
521 // last icon as being "before the (last + 1) icon".
523 std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row);
526 if (!drop_position_.get() ||
527 !(drop_position_->row == row_index &&
528 drop_position_->icon_in_row == before_icon_in_row)) {
529 drop_position_.reset(new DropPosition(row_index, before_icon_in_row));
533 return ui::DragDropTypes::DRAG_MOVE;
536 void BrowserActionsContainer::OnDragExited() {
537 StopShowFolderDropMenuTimer();
538 drop_position_.reset();
542 int BrowserActionsContainer::OnPerformDrop(
543 const ui::DropTargetEvent& event) {
544 BrowserActionDragData data;
545 if (!data.Read(event.data()))
546 return ui::DragDropTypes::DRAG_NONE;
548 // Make sure we have the same view as we started with.
549 DCHECK_EQ(browser_action_views_[data.index()]->extension()->id(),
553 size_t i = drop_position_->row * icons_per_overflow_menu_row_ +
554 drop_position_->icon_in_row;
555 if (in_overflow_mode())
556 i += main_container_->VisibleBrowserActionsAfterAnimation();
557 // |i| now points to the item to the right of the drop indicator*, which is
558 // correct when dragging an icon to the left. When dragging to the right,
559 // however, we want the icon being dragged to get the index of the item to
560 // the left of the drop indicator, so we subtract one.
561 // * Well, it can also point to the end, but not when dragging to the left. :)
562 if (i > data.index())
565 if (profile_->IsOffTheRecord())
566 i = model_->IncognitoIndexToOriginal(i);
568 // If this was a drag between containers, we will have to adjust the number of
570 bool drag_between_containers =
571 !browser_action_views_[data.index()]->visible();
572 model_->MoveExtensionIcon(
573 browser_action_views_[data.index()]->extension(), i);
575 if (drag_between_containers) {
576 // Add one for the dropped icon.
577 size_t new_icon_count = VisibleBrowserActionsAfterAnimation() + 1;
579 // Let the main container update the model.
580 if (in_overflow_mode())
581 main_container_->NotifyActionMovedToOverflow();
582 else if (!profile_->IsOffTheRecord()) // This is the main container.
583 model_->SetVisibleIconCount(model_->GetVisibleIconCount() + 1);
585 // The size changed, so we need to animate.
586 Animate(gfx::Tween::EASE_OUT, new_icon_count);
589 OnDragExited(); // Perform clean up after dragging.
590 return ui::DragDropTypes::DRAG_MOVE;
593 void BrowserActionsContainer::GetAccessibleState(
594 ui::AXViewState* state) {
595 state->role = ui::AX_ROLE_GROUP;
596 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
599 void BrowserActionsContainer::OnMenuButtonClicked(views::View* source,
600 const gfx::Point& point) {
601 if (source == chevron_) {
603 new BrowserActionOverflowMenuController(this,
606 browser_action_views_,
607 VisibleBrowserActions(),
609 overflow_menu_->set_observer(this);
610 overflow_menu_->RunMenu(GetWidget());
614 void BrowserActionsContainer::WriteDragDataForView(View* sender,
615 const gfx::Point& press_pt,
616 OSExchangeData* data) {
619 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
620 BrowserActionView* view = browser_action_views_[i];
621 if (view == sender) {
622 // Set the dragging image for the icon.
623 gfx::ImageSkia badge(view->GetIconWithBadge());
624 drag_utils::SetDragImageOnDataObject(badge,
625 press_pt.OffsetFromOrigin(),
628 // Fill in the remaining info.
629 BrowserActionDragData drag_data(view->extension()->id(), i);
630 drag_data.Write(profile_, data);
636 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
637 const gfx::Point& p) {
638 return ui::DragDropTypes::DRAG_MOVE;
641 bool BrowserActionsContainer::CanStartDragForView(View* sender,
642 const gfx::Point& press_pt,
643 const gfx::Point& p) {
644 // We don't allow dragging while we're highlighting.
645 return !model_->is_highlighting();
648 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
649 if (!done_resizing) {
650 resize_amount_ = resize_amount;
651 OnBrowserActionVisibilityChanged();
655 // Up until now we've only been modifying the resize_amount, but now it is
656 // time to set the container size to the size we have resized to, and then
657 // animate to the nearest icon count size if necessary (which may be 0).
658 int max_width = IconCountToWidth(-1, false);
660 std::min(std::max(0, container_width_ - resize_amount), max_width);
662 // Save off the desired number of visible icons. We do this now instead of at
663 // the end of the animation so that even if the browser is shut down while
664 // animating, the right value will be restored on next run.
665 // NOTE: Don't save the icon count in incognito because there may be fewer
666 // icons in that mode. The result is that the container in a normal window is
667 // always at least as wide as in an incognito window.
668 int visible_icons = WidthToIconCount(container_width_);
669 if (!profile_->IsOffTheRecord())
670 model_->SetVisibleIconCount(visible_icons);
671 Animate(gfx::Tween::EASE_OUT, visible_icons);
674 void BrowserActionsContainer::AnimationProgressed(
675 const gfx::Animation* animation) {
676 DCHECK_EQ(resize_animation_.get(), animation);
677 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
678 (container_width_ - animation_target_size_));
679 OnBrowserActionVisibilityChanged();
682 void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
683 container_width_ = animation_target_size_;
684 animation_target_size_ = 0;
686 suppress_chevron_ = false;
687 SetChevronVisibility();
688 OnBrowserActionVisibilityChanged();
690 FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
692 OnBrowserActionsContainerAnimationEnded());
695 void BrowserActionsContainer::NotifyMenuDeleted(
696 BrowserActionOverflowMenuController* controller) {
697 DCHECK_EQ(overflow_menu_, controller);
698 overflow_menu_ = NULL;
701 content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
702 return browser_->tab_strip_model()->GetActiveWebContents();
705 extensions::ActiveTabPermissionGranter*
706 BrowserActionsContainer::GetActiveTabPermissionGranter() {
707 content::WebContents* web_contents =
708 browser_->tab_strip_model()->GetActiveWebContents();
711 return extensions::TabHelper::FromWebContents(web_contents)->
712 active_tab_permission_granter();
715 ExtensionPopup* BrowserActionsContainer::TestGetPopup() {
716 return popup_owner_ ? popup_owner_->view_controller()->popup() : NULL;
719 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
720 model_->SetVisibleIconCountForTest(icons);
723 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
724 // If the views haven't been initialized yet, wait for the next call to
725 // paint (one will be triggered by entering highlight mode).
726 if (model_->is_highlighting() && !browser_action_views_.empty()) {
727 views::Painter::PaintPainterAt(
728 canvas, highlight_painter_.get(), GetLocalBounds());
731 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
732 // dragging (like we do for tab dragging).
733 if (drop_position_.get()) {
734 // The two-pixel width drop indicator.
735 static const int kDropIndicatorWidth = 2;
737 // Convert back to a pixel offset into the container. First find the X
738 // coordinate of the drop icon.
739 int drop_icon_x = ToolbarView::kStandardSpacing +
740 (drop_position_->icon_in_row * IconWidth(true));
741 // Next, find the space before the drop icon. This will either be
742 // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this
743 // is the first icon.
744 // NOTE: Right now, these are the same. But let's do this right for if they
746 int space_before_drop_icon = drop_position_->icon_in_row == 0 ?
747 ToolbarView::kStandardSpacing : kItemSpacing;
748 // Now place the drop indicator halfway between this and the end of the
749 // previous icon. If there is an odd amount of available space between the
750 // two icons (or the icon and the address bar) after subtracting the drop
751 // indicator width, this calculation puts the extra pixel on the left side
752 // of the indicator, since when the indicator is between the address bar and
753 // the first icon, it looks better closer to the icon.
754 int drop_indicator_x = drop_icon_x -
755 ((space_before_drop_icon + kDropIndicatorWidth) / 2);
756 int row_height = IconHeight();
757 int drop_indicator_y = row_height * drop_position_->row;
758 gfx::Rect indicator_bounds(drop_indicator_x,
762 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
764 // Color of the drop indicator.
765 static const SkColor kDropIndicatorColor = SK_ColorBLACK;
766 canvas->FillRect(indicator_bounds, kDropIndicatorColor);
770 void BrowserActionsContainer::OnThemeChanged() {
774 void BrowserActionsContainer::ViewHierarchyChanged(
775 const ViewHierarchyChangedDetails& details) {
776 // No extensions (e.g., incognito).
780 if (details.is_add && details.child == this) {
781 // Initial toolbar button creation and placement in the widget hierarchy.
782 // We do this here instead of in the constructor because AddBrowserAction
783 // calls Layout on the Toolbar, which needs this object to be constructed
784 // before its Layout function is called.
785 CreateBrowserActionViews();
790 int BrowserActionsContainer::IconWidth(bool include_padding) {
791 static bool initialized = false;
792 static int icon_width = 0;
795 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
796 IDR_BROWSER_ACTION)->width();
798 return icon_width + (include_padding ? kItemSpacing : 0);
802 int BrowserActionsContainer::IconHeight() {
803 static bool initialized = false;
804 static int icon_height = 0;
807 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
808 IDR_BROWSER_ACTION)->height();
813 void BrowserActionsContainer::ToolbarExtensionAdded(const Extension* extension,
816 for (size_t i = 0; i < browser_action_views_.size(); ++i) {
817 DCHECK(browser_action_views_[i]->extension() != extension) <<
818 "Asked to add a browser action view for an extension that already "
824 if (!ShouldDisplayBrowserAction(extension))
827 // Add the new browser action to the vector and the view hierarchy.
828 if (profile_->IsOffTheRecord())
829 index = model_->OriginalIndexToIncognito(index);
830 BrowserActionView* view =
831 new BrowserActionView(extension,
832 extensions::ExtensionActionManager::Get(profile_)->
833 GetExtensionAction(*extension),
836 browser_action_views_.insert(browser_action_views_.begin() + index, view);
837 AddChildViewAt(view, index);
839 // If we are still initializing the container, don't bother animating.
840 if (!model_->extensions_initialized())
843 // If this is just an upgrade, then don't worry about resizing.
844 if (!extensions::ExtensionSystem::Get(profile_)->runtime_data()->
845 IsBeingUpgraded(extension)) {
846 // We need to resize if either:
847 // - The container is set to display all icons (visible count = -1), or
848 // - The container will need to expand to include the chevron. This can
849 // happen when the container is set to display <n> icons, where <n> is
850 // the number of icons before the new icon. With the new icon, the chevron
851 // will need to be displayed.
852 int model_icon_count = model_->GetVisibleIconCount();
853 if (model_icon_count == -1 ||
854 (static_cast<size_t>(model_icon_count) < browser_action_views_.size() &&
855 (chevron_ && !chevron_->visible()))) {
856 suppress_chevron_ = true;
857 Animate(gfx::Tween::LINEAR, GetIconCount());
862 // Otherwise, we don't have to resize, so just redraw the (possibly modified)
864 OnBrowserActionVisibilityChanged();
867 void BrowserActionsContainer::ToolbarExtensionRemoved(
868 const Extension* extension) {
871 size_t visible_actions = VisibleBrowserActionsAfterAnimation();
872 for (BrowserActionViews::iterator i(browser_action_views_.begin());
873 i != browser_action_views_.end(); ++i) {
874 if ((*i)->extension() == extension) {
876 browser_action_views_.erase(i);
878 // If the extension is being upgraded we don't want the bar to shrink
879 // because the icon is just going to get re-added to the same location.
880 if (extensions::ExtensionSystem::Get(profile_)->runtime_data()->
881 IsBeingUpgraded(extension))
884 if (browser_action_views_.size() > visible_actions) {
885 // If we have more icons than we can show, then we must not be changing
886 // the container size (since we either removed an icon from the main
887 // area and one from the overflow list will have shifted in, or we
888 // removed an entry directly from the overflow list).
889 OnBrowserActionVisibilityChanged();
891 // Either we went from overflow to no-overflow, or we shrunk the no-
892 // overflow container by 1. Either way the size changed, so animate.
894 chevron_->SetVisible(false);
895 Animate(gfx::Tween::EASE_OUT, browser_action_views_.size());
897 return; // We have found the action to remove, bail out.
902 void BrowserActionsContainer::ToolbarExtensionMoved(const Extension* extension,
904 if (!ShouldDisplayBrowserAction(extension))
907 if (profile_->IsOffTheRecord())
908 index = model_->OriginalIndexToIncognito(index);
910 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
912 BrowserActionViews::iterator iter = browser_action_views_.begin();
914 while (iter != browser_action_views_.end() &&
915 (*iter)->extension() != extension) {
920 DCHECK(iter != browser_action_views_.end());
921 if (old_index == index)
922 return; // Already in place.
924 BrowserActionView* moved_view = *iter;
925 browser_action_views_.erase(iter);
926 browser_action_views_.insert(
927 browser_action_views_.begin() + index, moved_view);
933 void BrowserActionsContainer::ToolbarExtensionUpdated(
934 const Extension* extension) {
935 BrowserActionView* view = GetViewForExtension(extension);
940 bool BrowserActionsContainer::ShowExtensionActionPopup(
941 const Extension* extension,
942 bool grant_active_tab) {
943 // Don't override another popup, and only show in the active window.
944 if (popup_owner_ || !browser_->window()->IsActive())
947 BrowserActionView* view = GetViewForExtension(extension);
948 return view && view->view_controller()->ExecuteAction(ExtensionPopup::SHOW,
952 void BrowserActionsContainer::ToolbarVisibleCountChanged() {
953 if (GetPreferredWidth() != container_width_)
954 Animate(gfx::Tween::EASE_OUT, GetIconCount());
957 void BrowserActionsContainer::ToolbarHighlightModeChanged(
958 bool is_highlighting) {
959 // The visual highlighting is done in OnPaint(). It's a bit of a pain that
960 // we delete and recreate everything here, but given everything else going on
961 // (the lack of highlight, n more extensions appearing, etc), it's not worth
962 // the extra complexity to create and insert only the new extensions.
963 DeleteBrowserActionViews();
964 CreateBrowserActionViews();
965 Animate(gfx::Tween::LINEAR, GetIconCount());
968 Browser* BrowserActionsContainer::GetBrowser() {
972 void BrowserActionsContainer::LoadImages() {
973 ui::ThemeProvider* tp = GetThemeProvider();
974 if (!tp || !chevron_)
977 chevron_->SetImage(views::Button::STATE_NORMAL,
978 *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
980 const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
981 highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages));
984 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
985 SetVisible(!browser_action_views_.empty());
987 owner_view_->Layout();
988 owner_view_->SchedulePaint();
990 // In overflow mode, we don't have an owner view, but we still have to
997 int BrowserActionsContainer::GetPreferredWidth() {
998 size_t visible_actions = GetIconCount();
999 return IconCountToWidth(
1001 chevron_ && visible_actions < browser_action_views_.size());
1004 void BrowserActionsContainer::SetChevronVisibility() {
1006 chevron_->SetVisible(
1007 VisibleBrowserActionsAfterAnimation() < browser_action_views_.size());
1011 void BrowserActionsContainer::CloseOverflowMenu() {
1013 overflow_menu_->CancelMenu();
1016 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
1017 show_menu_task_factory_.InvalidateWeakPtrs();
1020 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
1021 base::MessageLoop::current()->PostDelayedTask(
1023 base::Bind(&BrowserActionsContainer::ShowDropFolder,
1024 show_menu_task_factory_.GetWeakPtr()),
1025 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
1028 void BrowserActionsContainer::ShowDropFolder() {
1029 DCHECK(!overflow_menu_);
1031 new BrowserActionOverflowMenuController(this,
1034 browser_action_views_,
1035 VisibleBrowserActions(),
1037 overflow_menu_->set_observer(this);
1038 overflow_menu_->RunMenu(GetWidget());
1041 int BrowserActionsContainer::IconCountToWidth(int icons,
1042 bool display_chevron) const {
1044 icons = browser_action_views_.size();
1045 if ((icons == 0) && !display_chevron)
1046 return ToolbarView::kStandardSpacing;
1048 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
1049 int chevron_size = chevron_ && display_chevron ?
1050 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
1051 // In overflow mode, our padding is to use item spacing on either end (just so
1052 // we can see the drop indicator). Otherwise we use the standard toolbar
1054 // Note: These are actually the same thing, but, on the offchance one
1055 // changes, let's get it right.
1057 2 * (in_overflow_mode() ? kItemSpacing : ToolbarView::kStandardSpacing);
1058 return icons_size + chevron_size + padding;
1061 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
1062 // Check for widths large enough to show the entire icon set.
1063 if (pixels >= IconCountToWidth(-1, false))
1064 return browser_action_views_.size();
1066 // We reserve space for the padding on either side of the toolbar...
1067 int available_space = pixels - (ToolbarView::kStandardSpacing * 2);
1068 // ... and, if the chevron is enabled, the chevron.
1070 available_space -= (chevron_->GetPreferredSize().width() + kChevronSpacing);
1072 // Now we add an extra between-item padding value so the space can be divided
1073 // evenly by (size of icon with padding).
1074 return static_cast<size_t>(
1075 std::max(0, available_space + kItemSpacing) / IconWidth(true));
1078 int BrowserActionsContainer::MinimumNonemptyWidth() const {
1080 return ToolbarView::kStandardSpacing;
1081 return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing +
1082 chevron_->GetPreferredSize().width();
1085 void BrowserActionsContainer::Animate(gfx::Tween::Type tween_type,
1086 size_t num_visible_icons) {
1087 int target_size = IconCountToWidth(num_visible_icons,
1088 num_visible_icons < browser_action_views_.size());
1089 if (resize_animation_ && !disable_animations_during_testing_) {
1090 // Animate! We have to set the animation_target_size_ after calling Reset(),
1091 // because that could end up calling AnimationEnded which clears the value.
1092 resize_animation_->Reset();
1093 resize_animation_->SetTweenType(tween_type);
1094 animation_target_size_ = target_size;
1095 resize_animation_->Show();
1097 animation_target_size_ = target_size;
1098 AnimationEnded(resize_animation_.get());
1102 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
1103 const Extension* extension) const {
1104 // Only display incognito-enabled extensions while in incognito mode.
1105 return !profile_->IsOffTheRecord() ||
1106 extensions::util::IsIncognitoEnabled(extension->id(), profile_);
1109 size_t BrowserActionsContainer::GetIconCount() const {
1113 const extensions::ExtensionList& extensions = model_->toolbar_items();
1115 // Find the absolute value for the model's visible count.
1116 int model_visible_size = model_->GetVisibleIconCount();
1117 size_t absolute_model_visible_size =
1118 model_visible_size == -1 ? extensions.size() : model_visible_size;
1120 // Find the number of icons which could be displayed.
1121 size_t displayable_icon_count = 0u;
1122 size_t main_displayed = 0u;
1123 for (size_t i = 0; i < extensions.size(); ++i) {
1124 // Should there be an icon for this extension at all?
1125 if (ShouldDisplayBrowserAction(extensions[i].get())) {
1126 ++displayable_icon_count;
1127 // Should we display it on the main bar? If this is an incognito window,
1128 // icons have the same overflow status they do in a regular window.
1129 main_displayed += i < absolute_model_visible_size ? 1u : 0u;
1133 // If this is an existing (initialized) container from an incognito profile,
1134 // we can't trust the model (because the incognito bars don't adjust model
1135 // settings). Instead, we go off what we currently have displayed.
1136 if (initialized_ && profile_->IsOffTheRecord()) {
1137 main_displayed = in_overflow_mode() ?
1138 main_container_->VisibleBrowserActionsAfterAnimation() :
1139 VisibleBrowserActionsAfterAnimation();
1142 // The overflow displays any (displayable) icons not shown by the main bar.
1143 return in_overflow_mode() ?
1144 displayable_icon_count - main_displayed : main_displayed;