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