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