• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/stl_util-inl.h"
8 #include "base/string_util.h"
9 #include "base/utf_string_conversions.h"
10 #include "chrome/browser/extensions/extension_browser_event_router.h"
11 #include "chrome/browser/extensions/extension_host.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/extension_tabs_module.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #include "chrome/browser/ui/view_ids.h"
18 #include "chrome/browser/ui/views/detachable_toolbar_view.h"
19 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
20 #include "chrome/browser/ui/views/extensions/extension_popup.h"
21 #include "chrome/browser/ui/views/toolbar_view.h"
22 #include "chrome/common/extensions/extension_action.h"
23 #include "chrome/common/extensions/extension_resource.h"
24 #include "chrome/common/pref_names.h"
25 #include "content/browser/renderer_host/render_view_host.h"
26 #include "content/browser/renderer_host/render_widget_host_view.h"
27 #include "content/browser/tab_contents/tab_contents.h"
28 #include "content/common/notification_source.h"
29 #include "content/common/notification_type.h"
30 #include "grit/app_resources.h"
31 #include "grit/generated_resources.h"
32 #include "third_party/skia/include/core/SkBitmap.h"
33 #include "third_party/skia/include/effects/SkGradientShader.h"
34 #include "ui/base/accessibility/accessible_view_state.h"
35 #include "ui/base/animation/slide_animation.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/base/theme_provider.h"
39 #include "ui/gfx/canvas.h"
40 #include "ui/gfx/canvas_skia.h"
41 #include "views/controls/button/menu_button.h"
42 #include "views/controls/button/text_button.h"
43 #include "views/controls/menu/menu_2.h"
44 #include "views/drag_utils.h"
45 #include "views/metrics.h"
46 #include "views/window/window.h"
47 
48 #include "grit/theme_resources.h"
49 
50 // Horizontal spacing between most items in the container, as well as after the
51 // last item or chevron (if visible).
52 static const int kItemSpacing = ToolbarView::kStandardSpacing;
53 // Horizontal spacing before the chevron (if visible).
54 static const int kChevronSpacing = kItemSpacing - 2;
55 
56 // static
57 bool BrowserActionsContainer::disable_animations_during_testing_ = false;
58 
59 ////////////////////////////////////////////////////////////////////////////////
60 // BrowserActionButton
61 
BrowserActionButton(const Extension * extension,BrowserActionsContainer * panel)62 BrowserActionButton::BrowserActionButton(const Extension* extension,
63                                          BrowserActionsContainer* panel)
64     : ALLOW_THIS_IN_INITIALIZER_LIST(
65           MenuButton(this, std::wstring(), NULL, false)),
66       browser_action_(extension->browser_action()),
67       extension_(extension),
68       ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)),
69       showing_context_menu_(false),
70       panel_(panel) {
71   set_border(NULL);
72   set_alignment(TextButton::ALIGN_CENTER);
73 
74   // No UpdateState() here because View hierarchy not setup yet. Our parent
75   // should call UpdateState() after creation.
76 
77   registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED,
78                  Source<ExtensionAction>(browser_action_));
79 }
80 
Destroy()81 void BrowserActionButton::Destroy() {
82   if (showing_context_menu_) {
83     context_menu_menu_->CancelMenu();
84     MessageLoop::current()->DeleteSoon(FROM_HERE, this);
85   } else {
86     delete this;
87   }
88 }
89 
ViewHierarchyChanged(bool is_add,View * parent,View * child)90 void BrowserActionButton::ViewHierarchyChanged(
91     bool is_add, View* parent, View* child) {
92   if (is_add && child == this) {
93     // The Browser Action API does not allow the default icon path to be
94     // changed at runtime, so we can load this now and cache it.
95     std::string relative_path = browser_action_->default_icon_path();
96     if (relative_path.empty())
97       return;
98 
99     // LoadImage is not guaranteed to be synchronous, so we might see the
100     // callback OnImageLoaded execute immediately. It (through UpdateState)
101     // expects parent() to return the owner for this button, so this
102     // function is as early as we can start this request.
103     tracker_.LoadImage(extension_, extension_->GetResource(relative_path),
104                        gfx::Size(Extension::kBrowserActionIconMaxSize,
105                                  Extension::kBrowserActionIconMaxSize),
106                        ImageLoadingTracker::DONT_CACHE);
107   }
108 
109   MenuButton::ViewHierarchyChanged(is_add, parent, child);
110 }
111 
ButtonPressed(views::Button * sender,const views::Event & event)112 void BrowserActionButton::ButtonPressed(views::Button* sender,
113                                         const views::Event& event) {
114   panel_->OnBrowserActionExecuted(this, false);
115 }
116 
OnImageLoaded(SkBitmap * image,const ExtensionResource & resource,int index)117 void BrowserActionButton::OnImageLoaded(SkBitmap* image,
118                                         const ExtensionResource& resource,
119                                         int index) {
120   if (image)
121     default_icon_ = *image;
122 
123   // Call back to UpdateState() because a more specific icon might have been set
124   // while the load was outstanding.
125   UpdateState();
126 }
127 
UpdateState()128 void BrowserActionButton::UpdateState() {
129   int tab_id = panel_->GetCurrentTabId();
130   if (tab_id < 0)
131     return;
132 
133   SkBitmap icon(browser_action()->GetIcon(tab_id));
134   if (icon.isNull())
135     icon = default_icon_;
136   if (!icon.isNull()) {
137     SkPaint paint;
138     paint.setXfermode(SkXfermode::Create(SkXfermode::kSrcOver_Mode));
139     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
140 
141     SkBitmap bg;
142     rb.GetBitmapNamed(IDR_BROWSER_ACTION)->copyTo(&bg,
143         SkBitmap::kARGB_8888_Config);
144     SkCanvas bg_canvas(bg);
145     bg_canvas.drawBitmap(icon, SkIntToScalar((bg.width() - icon.width()) / 2),
146         SkIntToScalar((bg.height() - icon.height()) / 2), &paint);
147     SetIcon(bg);
148 
149     SkBitmap bg_h;
150     rb.GetBitmapNamed(IDR_BROWSER_ACTION_H)->copyTo(&bg_h,
151         SkBitmap::kARGB_8888_Config);
152     SkCanvas bg_h_canvas(bg_h);
153     bg_h_canvas.drawBitmap(icon,
154         SkIntToScalar((bg_h.width() - icon.width()) / 2),
155         SkIntToScalar((bg_h.height() - icon.height()) / 2), &paint);
156     SetHoverIcon(bg_h);
157 
158     SkBitmap bg_p;
159     rb.GetBitmapNamed(IDR_BROWSER_ACTION_P)->copyTo(&bg_p,
160         SkBitmap::kARGB_8888_Config);
161     SkCanvas bg_p_canvas(bg_p);
162     bg_p_canvas.drawBitmap(icon,
163         SkIntToScalar((bg_p.width() - icon.width()) / 2),
164         SkIntToScalar((bg_p.height() - icon.height()) / 2), &paint);
165     SetPushedIcon(bg_p);
166   }
167 
168   // If the browser action name is empty, show the extension name instead.
169   string16 name = UTF8ToUTF16(browser_action()->GetTitle(tab_id));
170   if (name.empty())
171     name = UTF8ToUTF16(extension()->name());
172   SetTooltipText(UTF16ToWideHack(name));
173   parent()->SchedulePaint();
174 }
175 
IsPopup()176 bool BrowserActionButton::IsPopup() {
177   int tab_id = panel_->GetCurrentTabId();
178   return (tab_id < 0) ? false : browser_action_->HasPopup(tab_id);
179 }
180 
GetPopupUrl()181 GURL BrowserActionButton::GetPopupUrl() {
182   int tab_id = panel_->GetCurrentTabId();
183   return (tab_id < 0) ? GURL() : browser_action_->GetPopupUrl(tab_id);
184 }
185 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)186 void BrowserActionButton::Observe(NotificationType type,
187                                   const NotificationSource& source,
188                                   const NotificationDetails& details) {
189   DCHECK(type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED);
190   UpdateState();
191   // The browser action may have become visible/hidden so we need to make
192   // sure the state gets updated.
193   panel_->OnBrowserActionVisibilityChanged();
194 }
195 
Activate()196 bool BrowserActionButton::Activate() {
197   if (!IsPopup())
198     return true;
199 
200   panel_->OnBrowserActionExecuted(this, false);
201 
202   // TODO(erikkay): Run a nested modal loop while the mouse is down to
203   // enable menu-like drag-select behavior.
204 
205   // The return value of this method is returned via OnMousePressed.
206   // We need to return false here since we're handing off focus to another
207   // widget/view, and true will grab it right back and try to send events
208   // to us.
209   return false;
210 }
211 
OnMousePressed(const views::MouseEvent & event)212 bool BrowserActionButton::OnMousePressed(const views::MouseEvent& event) {
213   if (!event.IsRightMouseButton()) {
214     return IsPopup() ?
215         MenuButton::OnMousePressed(event) : TextButton::OnMousePressed(event);
216   }
217 
218   // Get the top left point of this button in screen coordinates.
219   gfx::Point point = gfx::Point(0, 0);
220   ConvertPointToScreen(this, &point);
221 
222   // Make the menu appear below the button.
223   point.Offset(0, height());
224 
225   ShowContextMenu(point, true);
226   return false;
227 }
228 
OnMouseReleased(const views::MouseEvent & event)229 void BrowserActionButton::OnMouseReleased(const views::MouseEvent& event) {
230   if (IsPopup() || showing_context_menu_) {
231     // TODO(erikkay) this never actually gets called (probably because of the
232     // loss of focus).
233     MenuButton::OnMouseReleased(event);
234   } else {
235     TextButton::OnMouseReleased(event);
236   }
237 }
238 
OnMouseExited(const views::MouseEvent & event)239 void BrowserActionButton::OnMouseExited(const views::MouseEvent& event) {
240   if (IsPopup() || showing_context_menu_)
241     MenuButton::OnMouseExited(event);
242   else
243     TextButton::OnMouseExited(event);
244 }
245 
OnKeyReleased(const views::KeyEvent & event)246 bool BrowserActionButton::OnKeyReleased(const views::KeyEvent& event) {
247   return IsPopup() ?
248       MenuButton::OnKeyReleased(event) : TextButton::OnKeyReleased(event);
249 }
250 
ShowContextMenu(const gfx::Point & p,bool is_mouse_gesture)251 void BrowserActionButton::ShowContextMenu(const gfx::Point& p,
252                                           bool is_mouse_gesture) {
253   if (!extension()->ShowConfigureContextMenus())
254     return;
255 
256   showing_context_menu_ = true;
257   SetButtonPushed();
258 
259   // Reconstructs the menu every time because the menu's contents are dynamic.
260   context_menu_contents_ =
261       new ExtensionContextMenuModel(extension(), panel_->browser(), panel_);
262   context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get()));
263   context_menu_menu_->RunContextMenuAt(p);
264 
265   SetButtonNotPushed();
266   showing_context_menu_ = false;
267 }
268 
SetButtonPushed()269 void BrowserActionButton::SetButtonPushed() {
270   SetState(views::CustomButton::BS_PUSHED);
271   menu_visible_ = true;
272 }
273 
SetButtonNotPushed()274 void BrowserActionButton::SetButtonNotPushed() {
275   SetState(views::CustomButton::BS_NORMAL);
276   menu_visible_ = false;
277 }
278 
~BrowserActionButton()279 BrowserActionButton::~BrowserActionButton() {
280 }
281 
282 
283 ////////////////////////////////////////////////////////////////////////////////
284 // BrowserActionView
285 
BrowserActionView(const Extension * extension,BrowserActionsContainer * panel)286 BrowserActionView::BrowserActionView(const Extension* extension,
287                                      BrowserActionsContainer* panel)
288     : panel_(panel) {
289   button_ = new BrowserActionButton(extension, panel);
290   button_->SetDragController(panel_);
291   AddChildView(button_);
292   button_->UpdateState();
293 }
294 
~BrowserActionView()295 BrowserActionView::~BrowserActionView() {
296   RemoveChildView(button_);
297   button_->Destroy();
298 }
299 
GetIconWithBadge()300 gfx::Canvas* BrowserActionView::GetIconWithBadge() {
301   int tab_id = panel_->GetCurrentTabId();
302 
303   SkBitmap icon = button_->extension()->browser_action()->GetIcon(tab_id);
304   if (icon.isNull())
305     icon = button_->default_icon();
306 
307   gfx::Canvas* canvas = new gfx::CanvasSkia(icon.width(), icon.height(), false);
308   canvas->DrawBitmapInt(icon, 0, 0);
309 
310   if (tab_id >= 0) {
311     gfx::Rect bounds(icon.width(), icon.height() + ToolbarView::kVertSpacing);
312     button_->extension()->browser_action()->PaintBadge(canvas, bounds, tab_id);
313   }
314 
315   return canvas;
316 }
317 
Layout()318 void BrowserActionView::Layout() {
319   // We can't rely on button_->GetPreferredSize() here because that's not set
320   // correctly until the first call to
321   // BrowserActionsContainer::RefreshBrowserActionViews(), whereas this can be
322   // called before that when the initial bounds are set (and then not after,
323   // since the bounds don't change).  So instead of setting the height from the
324   // button's preferred size, we use IconHeight(), since that's how big the
325   // button should be regardless of what it's displaying.
326   button_->SetBounds(0, ToolbarView::kVertSpacing, width(),
327                      BrowserActionsContainer::IconHeight());
328 }
329 
GetAccessibleState(ui::AccessibleViewState * state)330 void BrowserActionView::GetAccessibleState(ui::AccessibleViewState* state) {
331   state->name = l10n_util::GetStringUTF16(
332       IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION);
333   state->role = ui::AccessibilityTypes::ROLE_GROUPING;
334 }
335 
PaintChildren(gfx::Canvas * canvas)336 void BrowserActionView::PaintChildren(gfx::Canvas* canvas) {
337   View::PaintChildren(canvas);
338   ExtensionAction* action = button()->browser_action();
339   int tab_id = panel_->GetCurrentTabId();
340   if (tab_id >= 0)
341     action->PaintBadge(canvas, gfx::Rect(width(), height()), tab_id);
342 }
343 
344 ////////////////////////////////////////////////////////////////////////////////
345 // BrowserActionsContainer
346 
BrowserActionsContainer(Browser * browser,View * owner_view)347 BrowserActionsContainer::BrowserActionsContainer(Browser* browser,
348                                                  View* owner_view)
349     : profile_(browser->profile()),
350       browser_(browser),
351       owner_view_(owner_view),
352       popup_(NULL),
353       popup_button_(NULL),
354       model_(NULL),
355       container_width_(0),
356       chevron_(NULL),
357       overflow_menu_(NULL),
358       suppress_chevron_(false),
359       resize_amount_(0),
360       animation_target_size_(0),
361       drop_indicator_position_(-1),
362       ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
363       ALLOW_THIS_IN_INITIALIZER_LIST(show_menu_task_factory_(this)) {
364   SetID(VIEW_ID_BROWSER_ACTION_TOOLBAR);
365 
366   if (profile_->GetExtensionService()) {
367     model_ = profile_->GetExtensionService()->toolbar_model();
368     model_->AddObserver(this);
369   }
370 
371   resize_animation_.reset(new ui::SlideAnimation(this));
372   resize_area_ = new views::ResizeArea(this);
373   AddChildView(resize_area_);
374 
375   chevron_ = new views::MenuButton(NULL, std::wstring(), this, false);
376   chevron_->set_border(NULL);
377   chevron_->EnableCanvasFlippingForRTLUI(true);
378   chevron_->SetAccessibleName(
379       l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON));
380   chevron_->SetVisible(false);
381   AddChildView(chevron_);
382 }
383 
~BrowserActionsContainer()384 BrowserActionsContainer::~BrowserActionsContainer() {
385   if (model_)
386     model_->RemoveObserver(this);
387   StopShowFolderDropMenuTimer();
388   HidePopup();
389   DeleteBrowserActionViews();
390 }
391 
392 // Static.
RegisterUserPrefs(PrefService * prefs)393 void BrowserActionsContainer::RegisterUserPrefs(PrefService* prefs) {
394   prefs->RegisterIntegerPref(prefs::kBrowserActionContainerWidth, 0);
395 }
396 
Init()397 void BrowserActionsContainer::Init() {
398   LoadImages();
399 
400   // We wait to set the container width until now so that the chevron images
401   // will be loaded.  The width calculation needs to know the chevron size.
402   if (model_ &&
403       !profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) {
404     // Migration code to the new VisibleIconCount pref.
405     // TODO(mpcomplete): remove this after users are upgraded to 5.0.
406     int predefined_width =
407         profile_->GetPrefs()->GetInteger(prefs::kBrowserActionContainerWidth);
408     if (predefined_width != 0)
409       model_->SetVisibleIconCount(WidthToIconCount(predefined_width));
410   }
411   if (model_ && model_->extensions_initialized())
412     SetContainerWidth();
413 }
414 
GetCurrentTabId() const415 int BrowserActionsContainer::GetCurrentTabId() const {
416   TabContents* tab_contents = browser_->GetSelectedTabContents();
417   return tab_contents ? tab_contents->controller().session_id().id() : -1;
418 }
419 
GetBrowserActionView(ExtensionAction * action)420 BrowserActionView* BrowserActionsContainer::GetBrowserActionView(
421     ExtensionAction* action) {
422   for (BrowserActionViews::iterator iter = browser_action_views_.begin();
423        iter != browser_action_views_.end(); ++iter) {
424     if ((*iter)->button()->browser_action() == action)
425       return *iter;
426   }
427   return NULL;
428 }
429 
RefreshBrowserActionViews()430 void BrowserActionsContainer::RefreshBrowserActionViews() {
431   for (size_t i = 0; i < browser_action_views_.size(); ++i)
432     browser_action_views_[i]->button()->UpdateState();
433 }
434 
CreateBrowserActionViews()435 void BrowserActionsContainer::CreateBrowserActionViews() {
436   DCHECK(browser_action_views_.empty());
437   if (!model_)
438     return;
439 
440   for (ExtensionList::iterator iter = model_->begin(); iter != model_->end();
441        ++iter) {
442     if (!ShouldDisplayBrowserAction(*iter))
443       continue;
444 
445     BrowserActionView* view = new BrowserActionView(*iter, this);
446     browser_action_views_.push_back(view);
447     AddChildView(view);
448   }
449 }
450 
DeleteBrowserActionViews()451 void BrowserActionsContainer::DeleteBrowserActionViews() {
452   if (!browser_action_views_.empty()) {
453     for (size_t i = 0; i < browser_action_views_.size(); ++i)
454       RemoveChildView(browser_action_views_[i]);
455     STLDeleteContainerPointers(browser_action_views_.begin(),
456                                browser_action_views_.end());
457     browser_action_views_.clear();
458   }
459 }
460 
OnBrowserActionVisibilityChanged()461 void BrowserActionsContainer::OnBrowserActionVisibilityChanged() {
462   SetVisible(!browser_action_views_.empty());
463   owner_view_->Layout();
464   owner_view_->SchedulePaint();
465 }
466 
VisibleBrowserActions() const467 size_t BrowserActionsContainer::VisibleBrowserActions() const {
468   size_t visible_actions = 0;
469   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
470     if (browser_action_views_[i]->IsVisible())
471       ++visible_actions;
472   }
473   return visible_actions;
474 }
475 
OnBrowserActionExecuted(BrowserActionButton * button,bool inspect_with_devtools)476 void BrowserActionsContainer::OnBrowserActionExecuted(
477     BrowserActionButton* button,
478     bool inspect_with_devtools) {
479   ExtensionAction* browser_action = button->browser_action();
480 
481   // Popups just display.  No notification to the extension.
482   // TODO(erikkay): should there be?
483   if (!button->IsPopup()) {
484     ExtensionService* service = profile_->GetExtensionService();
485     service->browser_event_router()->BrowserActionExecuted(
486         profile_, browser_action->extension_id(), browser_);
487     return;
488   }
489 
490   // If we're showing the same popup, just hide it and return.
491   bool same_showing = popup_ && button == popup_button_;
492 
493   // Always hide the current popup, even if it's not the same.
494   // Only one popup should be visible at a time.
495   HidePopup();
496 
497   if (same_showing)
498     return;
499 
500   // We can get the execute event for browser actions that are not visible,
501   // since buttons can be activated from the overflow menu (chevron). In that
502   // case we show the popup as originating from the chevron.
503   View* reference_view = button->parent()->IsVisible() ? button : chevron_;
504   gfx::Point origin;
505   View::ConvertPointToScreen(reference_view, &origin);
506   gfx::Rect rect = reference_view->bounds();
507   rect.set_origin(origin);
508 
509   BubbleBorder::ArrowLocation arrow_location = base::i18n::IsRTL() ?
510       BubbleBorder::TOP_LEFT : BubbleBorder::TOP_RIGHT;
511 
512   popup_ = ExtensionPopup::Show(button->GetPopupUrl(), browser_, rect,
513                                 arrow_location, inspect_with_devtools,
514                                 this);
515   popup_button_ = button;
516   popup_button_->SetButtonPushed();
517 }
518 
GetPreferredSize()519 gfx::Size BrowserActionsContainer::GetPreferredSize() {
520   if (browser_action_views_.empty())
521     return gfx::Size(ToolbarView::kStandardSpacing, 0);
522 
523   // We calculate the size of the view by taking the current width and
524   // subtracting resize_amount_ (the latter represents how far the user is
525   // resizing the view or, if animating the snapping, how far to animate it).
526   // But we also clamp it to a minimum size and the maximum size, so that the
527   // container can never shrink too far or take up more space than it needs. In
528   // other words: ContainerMinSize() < width() - resize < ClampTo(MAX).
529   int clamped_width = std::min(
530       std::max(ContainerMinSize(), container_width_ - resize_amount_),
531       IconCountToWidth(-1, false));
532   return gfx::Size(clamped_width, 0);
533 }
534 
Layout()535 void BrowserActionsContainer::Layout() {
536   if (browser_action_views_.empty()) {
537     SetVisible(false);
538     return;
539   }
540 
541   SetVisible(true);
542   resize_area_->SetBounds(0, ToolbarView::kVertSpacing, kItemSpacing,
543                           IconHeight());
544 
545   // If the icons don't all fit, show the chevron (unless suppressed).
546   int max_x = GetPreferredSize().width();
547   if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_) {
548     chevron_->SetVisible(true);
549     gfx::Size chevron_size(chevron_->GetPreferredSize());
550     max_x -=
551         ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing;
552     chevron_->SetBounds(
553         width() - ToolbarView::kStandardSpacing - chevron_size.width(),
554         ToolbarView::kVertSpacing, chevron_size.width(), chevron_size.height());
555   } else {
556     chevron_->SetVisible(false);
557   }
558 
559   // Now draw the icons for the browser actions in the available space.
560   int icon_width = IconWidth(false);
561   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
562     BrowserActionView* view = browser_action_views_[i];
563     int x = ToolbarView::kStandardSpacing + (i * IconWidth(true));
564     if (x + icon_width <= max_x) {
565       view->SetBounds(x, 0, icon_width, height());
566       view->SetVisible(true);
567     } else {
568       view->SetVisible(false);
569     }
570   }
571 }
572 
GetDropFormats(int * formats,std::set<OSExchangeData::CustomFormat> * custom_formats)573 bool BrowserActionsContainer::GetDropFormats(
574     int* formats,
575     std::set<OSExchangeData::CustomFormat>* custom_formats) {
576   custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
577 
578   return true;
579 }
580 
AreDropTypesRequired()581 bool BrowserActionsContainer::AreDropTypesRequired() {
582   return true;
583 }
584 
CanDrop(const OSExchangeData & data)585 bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) {
586   BrowserActionDragData drop_data;
587   return drop_data.Read(data) ? drop_data.IsFromProfile(profile_) : false;
588 }
589 
OnDragEntered(const views::DropTargetEvent & event)590 void BrowserActionsContainer::OnDragEntered(
591     const views::DropTargetEvent& event) {
592 }
593 
OnDragUpdated(const views::DropTargetEvent & event)594 int BrowserActionsContainer::OnDragUpdated(
595     const views::DropTargetEvent& event) {
596   // First check if we are above the chevron (overflow) menu.
597   if (GetEventHandlerForPoint(event.location()) == chevron_) {
598     if (show_menu_task_factory_.empty() && !overflow_menu_)
599       StartShowFolderDropMenuTimer();
600     return ui::DragDropTypes::DRAG_MOVE;
601   }
602   StopShowFolderDropMenuTimer();
603 
604   // Figure out where to display the indicator.  This is a complex calculation:
605 
606   // First, we figure out how much space is to the left of the icon area, so we
607   // can calculate the true offset into the icon area.
608   int width_before_icons = ToolbarView::kStandardSpacing +
609       (base::i18n::IsRTL() ?
610           (chevron_->GetPreferredSize().width() + kChevronSpacing) : 0);
611   int offset_into_icon_area = event.x() - width_before_icons;
612 
613   // Next, we determine which icon to place the indicator in front of.  We want
614   // to place the indicator in front of icon n when the cursor is between the
615   // midpoints of icons (n - 1) and n.  To do this we take the offset into the
616   // icon area and transform it as follows:
617   //
618   // Real icon area:
619   //   0   a     *  b        c
620   //   |   |        |        |
621   //   |[IC|ON]  [IC|ON]  [IC|ON]
622   // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc.
623   // Here the "*" represents the offset into the icon area, and since it's
624   // between a and b, we want to return "1".
625   //
626   // Transformed "icon area":
627   //   0        a     *  b        c
628   //   |        |        |        |
629   //   |[ICON]  |[ICON]  |[ICON]  |
630   // If we shift both our offset and our divider points later by half an icon
631   // plus one spacing unit, then it becomes very easy to calculate how many
632   // divider points we've passed, because they're the multiples of "one icon
633   // plus padding".
634   int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) +
635       kItemSpacing) / IconWidth(true);
636 
637   // Because the user can drag outside the container bounds, we need to clamp to
638   // the valid range.  Note that the maximum allowable value is (num icons), not
639   // (num icons - 1), because we represent the indicator being past the last
640   // icon as being "before the (last + 1) icon".
641   int before_icon = std::min(std::max(before_icon_unclamped, 0),
642                              static_cast<int>(VisibleBrowserActions()));
643 
644   // Now we convert back to a pixel offset into the container.  We want to place
645   // the center of the drop indicator at the midpoint of the space before our
646   // chosen icon.
647   SetDropIndicator(width_before_icons + (before_icon * IconWidth(true)) -
648       (kItemSpacing / 2));
649 
650   return ui::DragDropTypes::DRAG_MOVE;
651 }
652 
OnDragExited()653 void BrowserActionsContainer::OnDragExited() {
654   StopShowFolderDropMenuTimer();
655   drop_indicator_position_ = -1;
656   SchedulePaint();
657 }
658 
OnPerformDrop(const views::DropTargetEvent & event)659 int BrowserActionsContainer::OnPerformDrop(
660     const views::DropTargetEvent& event) {
661   BrowserActionDragData data;
662   if (!data.Read(event.data()))
663     return ui::DragDropTypes::DRAG_NONE;
664 
665   // Make sure we have the same view as we started with.
666   DCHECK_EQ(browser_action_views_[data.index()]->button()->extension()->id(),
667             data.id());
668   DCHECK(model_);
669 
670   size_t i = 0;
671   for (; i < browser_action_views_.size(); ++i) {
672     int view_x = browser_action_views_[i]->GetMirroredBounds().x();
673     if (!browser_action_views_[i]->IsVisible() ||
674         (base::i18n::IsRTL() ? (view_x < drop_indicator_position_) :
675             (view_x >= drop_indicator_position_))) {
676       // We have reached the end of the visible icons or found one that has a
677       // higher x position than the drop point.
678       break;
679     }
680   }
681 
682   // |i| now points to the item to the right of the drop indicator*, which is
683   // correct when dragging an icon to the left. When dragging to the right,
684   // however, we want the icon being dragged to get the index of the item to
685   // the left of the drop indicator, so we subtract one.
686   // * Well, it can also point to the end, but not when dragging to the left. :)
687   if (i > data.index())
688     --i;
689 
690   if (profile_->IsOffTheRecord())
691     i = model_->IncognitoIndexToOriginal(i);
692 
693   model_->MoveBrowserAction(
694       browser_action_views_[data.index()]->button()->extension(), i);
695 
696   OnDragExited();  // Perform clean up after dragging.
697   return ui::DragDropTypes::DRAG_MOVE;
698 }
699 
GetAccessibleState(ui::AccessibleViewState * state)700 void BrowserActionsContainer::GetAccessibleState(
701     ui::AccessibleViewState* state) {
702   state->role = ui::AccessibilityTypes::ROLE_GROUPING;
703   state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS);
704 }
705 
RunMenu(View * source,const gfx::Point & pt)706 void BrowserActionsContainer::RunMenu(View* source, const gfx::Point& pt) {
707   if (source == chevron_) {
708     overflow_menu_ = new BrowserActionOverflowMenuController(
709         this, chevron_, browser_action_views_, VisibleBrowserActions());
710     overflow_menu_->set_observer(this);
711     overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), false);
712   }
713 }
714 
WriteDragDataForView(View * sender,const gfx::Point & press_pt,OSExchangeData * data)715 void BrowserActionsContainer::WriteDragDataForView(View* sender,
716                                                    const gfx::Point& press_pt,
717                                                    OSExchangeData* data) {
718   DCHECK(data);
719 
720   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
721     BrowserActionButton* button = browser_action_views_[i]->button();
722     if (button == sender) {
723       // Set the dragging image for the icon.
724       scoped_ptr<gfx::Canvas> canvas(
725           browser_action_views_[i]->GetIconWithBadge());
726       drag_utils::SetDragImageOnDataObject(*canvas, button->size(), press_pt,
727                                            data);
728 
729       // Fill in the remaining info.
730       BrowserActionDragData drag_data(
731           browser_action_views_[i]->button()->extension()->id(), i);
732       drag_data.Write(profile_, data);
733       break;
734     }
735   }
736 }
737 
GetDragOperationsForView(View * sender,const gfx::Point & p)738 int BrowserActionsContainer::GetDragOperationsForView(View* sender,
739                                                       const gfx::Point& p) {
740   return ui::DragDropTypes::DRAG_MOVE;
741 }
742 
CanStartDragForView(View * sender,const gfx::Point & press_pt,const gfx::Point & p)743 bool BrowserActionsContainer::CanStartDragForView(View* sender,
744                                                   const gfx::Point& press_pt,
745                                                   const gfx::Point& p) {
746   return true;
747 }
748 
OnResize(int resize_amount,bool done_resizing)749 void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) {
750   if (!done_resizing) {
751     resize_amount_ = resize_amount;
752     OnBrowserActionVisibilityChanged();
753     return;
754   }
755 
756   // Up until now we've only been modifying the resize_amount, but now it is
757   // time to set the container size to the size we have resized to, and then
758   // animate to the nearest icon count size if necessary (which may be 0).
759   int max_width = IconCountToWidth(-1, false);
760   container_width_ =
761       std::min(std::max(0, container_width_ - resize_amount), max_width);
762   SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT,
763                             WidthToIconCount(container_width_));
764 }
765 
AnimationProgressed(const ui::Animation * animation)766 void BrowserActionsContainer::AnimationProgressed(
767     const ui::Animation* animation) {
768   DCHECK_EQ(resize_animation_.get(), animation);
769   resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() *
770       (container_width_ - animation_target_size_));
771   OnBrowserActionVisibilityChanged();
772 }
773 
AnimationEnded(const ui::Animation * animation)774 void BrowserActionsContainer::AnimationEnded(const ui::Animation* animation) {
775   container_width_ = animation_target_size_;
776   animation_target_size_ = 0;
777   resize_amount_ = 0;
778   OnBrowserActionVisibilityChanged();
779   suppress_chevron_ = false;
780 }
781 
NotifyMenuDeleted(BrowserActionOverflowMenuController * controller)782 void BrowserActionsContainer::NotifyMenuDeleted(
783     BrowserActionOverflowMenuController* controller) {
784   DCHECK(controller == overflow_menu_);
785   overflow_menu_ = NULL;
786 }
787 
InspectPopup(ExtensionAction * action)788 void BrowserActionsContainer::InspectPopup(ExtensionAction* action) {
789   OnBrowserActionExecuted(GetBrowserActionView(action)->button(), true);
790 }
791 
ExtensionPopupIsClosing(ExtensionPopup * popup)792 void BrowserActionsContainer::ExtensionPopupIsClosing(ExtensionPopup* popup) {
793   // ExtensionPopup is ref-counted, so we don't need to delete it.
794   DCHECK_EQ(popup_, popup);
795   popup_ = NULL;
796   popup_button_->SetButtonNotPushed();
797   popup_button_ = NULL;
798 }
799 
MoveBrowserAction(const std::string & extension_id,size_t new_index)800 void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id,
801                                                 size_t new_index) {
802   ExtensionService* service = profile_->GetExtensionService();
803   if (service) {
804     const Extension* extension = service->GetExtensionById(extension_id, false);
805     model_->MoveBrowserAction(extension, new_index);
806     SchedulePaint();
807   }
808 }
809 
HidePopup()810 void BrowserActionsContainer::HidePopup() {
811   if (popup_)
812     popup_->Close();
813 }
814 
TestExecuteBrowserAction(int index)815 void BrowserActionsContainer::TestExecuteBrowserAction(int index) {
816   BrowserActionButton* button = browser_action_views_[index]->button();
817   OnBrowserActionExecuted(button, false);
818 }
819 
TestSetIconVisibilityCount(size_t icons)820 void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) {
821   model_->SetVisibleIconCount(icons);
822   chevron_->SetVisible(icons < browser_action_views_.size());
823   container_width_ = IconCountToWidth(icons, chevron_->IsVisible());
824   Layout();
825   SchedulePaint();
826 }
827 
OnPaint(gfx::Canvas * canvas)828 void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) {
829   // TODO(sky/glen): Instead of using a drop indicator, animate the icons while
830   // dragging (like we do for tab dragging).
831   if (drop_indicator_position_ > -1) {
832     // The two-pixel width drop indicator.
833     static const int kDropIndicatorWidth = 2;
834     gfx::Rect indicator_bounds(
835         drop_indicator_position_ - (kDropIndicatorWidth / 2),
836         ToolbarView::kVertSpacing, kDropIndicatorWidth, IconHeight());
837 
838     // Color of the drop indicator.
839     static const SkColor kDropIndicatorColor = SK_ColorBLACK;
840     canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(),
841                         indicator_bounds.y(), indicator_bounds.width(),
842                         indicator_bounds.height());
843   }
844 }
845 
OnThemeChanged()846 void BrowserActionsContainer::OnThemeChanged() {
847   LoadImages();
848 }
849 
ViewHierarchyChanged(bool is_add,views::View * parent,views::View * child)850 void BrowserActionsContainer::ViewHierarchyChanged(bool is_add,
851                                                    views::View* parent,
852                                                    views::View* child) {
853   // No extensions (e.g., incognito).
854   if (!model_)
855     return;
856 
857   if (is_add && child == this) {
858     // Initial toolbar button creation and placement in the widget hierarchy.
859     // We do this here instead of in the constructor because AddBrowserAction
860     // calls Layout on the Toolbar, which needs this object to be constructed
861     // before its Layout function is called.
862     CreateBrowserActionViews();
863   }
864 }
865 
866 // static
IconWidth(bool include_padding)867 int BrowserActionsContainer::IconWidth(bool include_padding) {
868   static bool initialized = false;
869   static int icon_width = 0;
870   if (!initialized) {
871     initialized = true;
872     icon_width = ResourceBundle::GetSharedInstance().GetBitmapNamed(
873         IDR_BROWSER_ACTION)->width();
874   }
875   return icon_width + (include_padding ? kItemSpacing : 0);
876 }
877 
878 // static
IconHeight()879 int BrowserActionsContainer::IconHeight() {
880   static bool initialized = false;
881   static int icon_height = 0;
882   if (!initialized) {
883     initialized = true;
884     icon_height = ResourceBundle::GetSharedInstance().GetBitmapNamed(
885         IDR_BROWSER_ACTION)->height();
886   }
887   return icon_height;
888 }
889 
BrowserActionAdded(const Extension * extension,int index)890 void BrowserActionsContainer::BrowserActionAdded(const Extension* extension,
891                                                  int index) {
892 #if defined(DEBUG)
893   for (size_t i = 0; i < browser_action_views_.size(); ++i) {
894     DCHECK(browser_action_views_[i]->button()->extension() != extension) <<
895            "Asked to add a browser action view for an extension that already "
896            "exists.";
897   }
898 #endif
899   CloseOverflowMenu();
900 
901   if (!ShouldDisplayBrowserAction(extension))
902     return;
903 
904   size_t visible_actions = VisibleBrowserActions();
905 
906   // Add the new browser action to the vector and the view hierarchy.
907   if (profile_->IsOffTheRecord())
908     index = model_->OriginalIndexToIncognito(index);
909   BrowserActionView* view = new BrowserActionView(extension, this);
910   browser_action_views_.insert(browser_action_views_.begin() + index, view);
911   AddChildViewAt(view, index);
912 
913   // If we are still initializing the container, don't bother animating.
914   if (!model_->extensions_initialized())
915     return;
916 
917   // Enlarge the container if it was already at maximum size and we're not in
918   // the middle of upgrading.
919   if ((model_->GetVisibleIconCount() < 0) &&
920       !profile_->GetExtensionService()->IsBeingUpgraded(extension)) {
921     suppress_chevron_ = true;
922     SaveDesiredSizeAndAnimate(ui::Tween::LINEAR, visible_actions + 1);
923   } else {
924     // Just redraw the (possibly modified) visible icon set.
925     OnBrowserActionVisibilityChanged();
926   }
927 }
928 
BrowserActionRemoved(const Extension * extension)929 void BrowserActionsContainer::BrowserActionRemoved(const Extension* extension) {
930   CloseOverflowMenu();
931 
932   if (popup_ && popup_->host()->extension() == extension)
933     HidePopup();
934 
935   size_t visible_actions = VisibleBrowserActions();
936   for (BrowserActionViews::iterator iter = browser_action_views_.begin();
937        iter != browser_action_views_.end(); ++iter) {
938     if ((*iter)->button()->extension() == extension) {
939       RemoveChildView(*iter);
940       delete *iter;
941       browser_action_views_.erase(iter);
942 
943       // If the extension is being upgraded we don't want the bar to shrink
944       // because the icon is just going to get re-added to the same location.
945       if (profile_->GetExtensionService()->IsBeingUpgraded(extension))
946         return;
947 
948       if (browser_action_views_.size() > visible_actions) {
949         // If we have more icons than we can show, then we must not be changing
950         // the container size (since we either removed an icon from the main
951         // area and one from the overflow list will have shifted in, or we
952         // removed an entry directly from the overflow list).
953         OnBrowserActionVisibilityChanged();
954       } else {
955         // Either we went from overflow to no-overflow, or we shrunk the no-
956         // overflow container by 1.  Either way the size changed, so animate.
957         chevron_->SetVisible(false);
958         SaveDesiredSizeAndAnimate(ui::Tween::EASE_OUT,
959                                   browser_action_views_.size());
960       }
961       return;
962     }
963   }
964 }
965 
BrowserActionMoved(const Extension * extension,int index)966 void BrowserActionsContainer::BrowserActionMoved(const Extension* extension,
967                                                  int index) {
968   if (!ShouldDisplayBrowserAction(extension))
969     return;
970 
971   if (profile_->IsOffTheRecord())
972     index = model_->OriginalIndexToIncognito(index);
973 
974   DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size()));
975 
976   DeleteBrowserActionViews();
977   CreateBrowserActionViews();
978   Layout();
979   SchedulePaint();
980 }
981 
ModelLoaded()982 void BrowserActionsContainer::ModelLoaded() {
983   SetContainerWidth();
984 }
985 
LoadImages()986 void BrowserActionsContainer::LoadImages() {
987   ui::ThemeProvider* tp = GetThemeProvider();
988   chevron_->SetIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW));
989   chevron_->SetHoverIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_H));
990   chevron_->SetPushedIcon(*tp->GetBitmapNamed(IDR_BROWSER_ACTIONS_OVERFLOW_P));
991 }
992 
SetContainerWidth()993 void BrowserActionsContainer::SetContainerWidth() {
994   int visible_actions = model_->GetVisibleIconCount();
995   if (visible_actions < 0)  // All icons should be visible.
996     visible_actions = model_->size();
997   chevron_->SetVisible(static_cast<size_t>(visible_actions) < model_->size());
998   container_width_ = IconCountToWidth(visible_actions, chevron_->IsVisible());
999 }
1000 
CloseOverflowMenu()1001 void BrowserActionsContainer::CloseOverflowMenu() {
1002   if (overflow_menu_)
1003     overflow_menu_->CancelMenu();
1004 }
1005 
StopShowFolderDropMenuTimer()1006 void BrowserActionsContainer::StopShowFolderDropMenuTimer() {
1007   show_menu_task_factory_.RevokeAll();
1008 }
1009 
StartShowFolderDropMenuTimer()1010 void BrowserActionsContainer::StartShowFolderDropMenuTimer() {
1011   int delay = views::GetMenuShowDelay();
1012   MessageLoop::current()->PostDelayedTask(FROM_HERE,
1013       show_menu_task_factory_.NewRunnableMethod(
1014           &BrowserActionsContainer::ShowDropFolder),
1015       delay);
1016 }
1017 
ShowDropFolder()1018 void BrowserActionsContainer::ShowDropFolder() {
1019   DCHECK(!overflow_menu_);
1020   SetDropIndicator(-1);
1021   overflow_menu_ = new BrowserActionOverflowMenuController(
1022       this, chevron_, browser_action_views_, VisibleBrowserActions());
1023   overflow_menu_->set_observer(this);
1024   overflow_menu_->RunMenu(GetWindow()->GetNativeWindow(), true);
1025 }
1026 
SetDropIndicator(int x_pos)1027 void BrowserActionsContainer::SetDropIndicator(int x_pos) {
1028   if (drop_indicator_position_ != x_pos) {
1029     drop_indicator_position_ = x_pos;
1030     SchedulePaint();
1031   }
1032 }
1033 
IconCountToWidth(int icons,bool display_chevron) const1034 int BrowserActionsContainer::IconCountToWidth(int icons,
1035                                               bool display_chevron) const {
1036   if (icons < 0)
1037     icons = browser_action_views_.size();
1038   if ((icons == 0) && !display_chevron)
1039     return ToolbarView::kStandardSpacing;
1040   int icons_size =
1041       (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing);
1042   int chevron_size = display_chevron ?
1043       (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0;
1044   return ToolbarView::kStandardSpacing + icons_size + chevron_size +
1045       ToolbarView::kStandardSpacing;
1046 }
1047 
WidthToIconCount(int pixels) const1048 size_t BrowserActionsContainer::WidthToIconCount(int pixels) const {
1049   // Check for widths large enough to show the entire icon set.
1050   if (pixels >= IconCountToWidth(-1, false))
1051     return browser_action_views_.size();
1052 
1053   // We need to reserve space for the resize area, chevron, and the spacing on
1054   // either side of the chevron.
1055   int available_space = pixels - ToolbarView::kStandardSpacing -
1056       chevron_->GetPreferredSize().width() - kChevronSpacing -
1057       ToolbarView::kStandardSpacing;
1058   // Now we add an extra between-item padding value so the space can be divided
1059   // evenly by (size of icon with padding).
1060   return static_cast<size_t>(
1061       std::max(0, available_space + kItemSpacing) / IconWidth(true));
1062 }
1063 
ContainerMinSize() const1064 int BrowserActionsContainer::ContainerMinSize() const {
1065   return ToolbarView::kStandardSpacing + kChevronSpacing +
1066       chevron_->GetPreferredSize().width() + ToolbarView::kStandardSpacing;
1067 }
1068 
SaveDesiredSizeAndAnimate(ui::Tween::Type tween_type,size_t num_visible_icons)1069 void BrowserActionsContainer::SaveDesiredSizeAndAnimate(
1070     ui::Tween::Type tween_type,
1071     size_t num_visible_icons) {
1072   // Save off the desired number of visible icons.  We do this now instead of at
1073   // the end of the animation so that even if the browser is shut down while
1074   // animating, the right value will be restored on next run.
1075   // NOTE: Don't save the icon count in incognito because there may be fewer
1076   // icons in that mode. The result is that the container in a normal window is
1077   // always at least as wide as in an incognito window.
1078   if (!profile_->IsOffTheRecord())
1079     model_->SetVisibleIconCount(num_visible_icons);
1080 
1081   int target_size = IconCountToWidth(num_visible_icons,
1082       num_visible_icons < browser_action_views_.size());
1083   if (!disable_animations_during_testing_) {
1084     // Animate! We have to set the animation_target_size_ after calling Reset(),
1085     // because that could end up calling AnimationEnded which clears the value.
1086     resize_animation_->Reset();
1087     resize_animation_->SetTweenType(tween_type);
1088     animation_target_size_ = target_size;
1089     resize_animation_->Show();
1090   } else {
1091     animation_target_size_ = target_size;
1092     AnimationEnded(resize_animation_.get());
1093   }
1094 }
1095 
ShouldDisplayBrowserAction(const Extension * extension)1096 bool BrowserActionsContainer::ShouldDisplayBrowserAction(
1097     const Extension* extension) {
1098   // Only display incognito-enabled extensions while in incognito mode.
1099   return
1100       (!profile_->IsOffTheRecord() ||
1101        profile_->GetExtensionService()->IsIncognitoEnabled(extension->id()));
1102 }
1103