OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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/extensions/extension_action_platform_delegate_
views.h" |
| 6 |
| 7 #include "base/logging.h" |
| 8 #include "base/strings/utf_string_conversions.h" |
| 9 #include "chrome/browser/extensions/api/commands/command_service.h" |
| 10 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" |
| 11 #include "chrome/browser/extensions/extension_action.h" |
| 12 #include "chrome/browser/profiles/profile.h" |
| 13 #include "chrome/browser/sessions/session_tab_helper.h" |
| 14 #include "chrome/browser/ui/browser.h" |
| 15 #include "chrome/browser/ui/extensions/accelerator_priority.h" |
| 16 #include "chrome/browser/ui/views/toolbar/toolbar_action_view_delegate_views.h" |
| 17 #include "chrome/browser/ui/views/toolbar/toolbar_view.h" |
| 18 #include "chrome/common/extensions/api/extension_action/action_info.h" |
| 19 #include "content/public/browser/notification_details.h" |
| 20 #include "content/public/browser/notification_source.h" |
| 21 #include "extensions/browser/notification_types.h" |
| 22 #include "extensions/common/extension.h" |
| 23 #include "extensions/common/manifest_constants.h" |
| 24 #include "ui/gfx/image/image_skia.h" |
| 25 #include "ui/gfx/image/image_skia_operations.h" |
| 26 #include "ui/views/controls/menu/menu_controller.h" |
| 27 #include "ui/views/controls/menu/menu_runner.h" |
| 28 #include "ui/views/view.h" |
| 29 #include "ui/views/widget/widget.h" |
| 30 |
| 31 using extensions::ActionInfo; |
| 32 using extensions::CommandService; |
| 33 |
| 34 namespace { |
| 35 |
| 36 // The ExtensionActionPlatformDelegateViews which is currently showing its |
| 37 // context menu, if any. |
| 38 // Since only one context menu can be shown (even across browser windows), it's |
| 39 // safe to have this be a global singleton. |
| 40 ExtensionActionPlatformDelegateViews* context_menu_owner = NULL; |
| 41 |
| 42 } // namespace |
| 43 |
| 44 // static |
| 45 scoped_ptr<ExtensionActionPlatformDelegate> |
| 46 ExtensionActionPlatformDelegate::Create( |
| 47 ExtensionActionViewController* controller) { |
| 48 return make_scoped_ptr(new ExtensionActionPlatformDelegateViews(controller)); |
| 49 } |
| 50 |
| 51 ExtensionActionPlatformDelegateViews::ExtensionActionPlatformDelegateViews( |
| 52 ExtensionActionViewController* controller) |
| 53 : controller_(controller), |
| 54 popup_(nullptr), |
| 55 weak_factory_(this) { |
| 56 content::NotificationSource notification_source = content::Source<Profile>( |
| 57 controller_->browser()->profile()->GetOriginalProfile()); |
| 58 registrar_.Add(this, |
| 59 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED, |
| 60 notification_source); |
| 61 registrar_.Add(this, |
| 62 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED, |
| 63 notification_source); |
| 64 } |
| 65 |
| 66 ExtensionActionPlatformDelegateViews::~ExtensionActionPlatformDelegateViews() { |
| 67 if (context_menu_owner == this) |
| 68 context_menu_owner = NULL; |
| 69 controller_->HidePopup(); |
| 70 UnregisterCommand(false); |
| 71 } |
| 72 |
| 73 gfx::NativeView ExtensionActionPlatformDelegateViews::GetPopupNativeView() { |
| 74 return popup_ ? popup_->GetWidget()->GetNativeView() : nullptr; |
| 75 } |
| 76 |
| 77 bool ExtensionActionPlatformDelegateViews::IsMenuRunning() const { |
| 78 return menu_runner_.get() != NULL; |
| 79 } |
| 80 |
| 81 void ExtensionActionPlatformDelegateViews::RegisterCommand() { |
| 82 // If we've already registered, do nothing. |
| 83 if (action_keybinding_.get()) |
| 84 return; |
| 85 |
| 86 extensions::Command extension_command; |
| 87 views::FocusManager* focus_manager = |
| 88 GetDelegateViews()->GetFocusManagerForAccelerator(); |
| 89 if (focus_manager && controller_->GetExtensionCommand(&extension_command)) { |
| 90 action_keybinding_.reset( |
| 91 new ui::Accelerator(extension_command.accelerator())); |
| 92 focus_manager->RegisterAccelerator( |
| 93 *action_keybinding_, |
| 94 GetAcceleratorPriority(extension_command.accelerator(), |
| 95 controller_->extension()), |
| 96 this); |
| 97 } |
| 98 } |
| 99 |
| 100 void ExtensionActionPlatformDelegateViews::OnDelegateSet() { |
| 101 GetDelegateViews()->GetAsView()->set_context_menu_controller(this); |
| 102 } |
| 103 |
| 104 bool ExtensionActionPlatformDelegateViews::IsShowingPopup() const { |
| 105 return popup_ != nullptr; |
| 106 } |
| 107 |
| 108 void ExtensionActionPlatformDelegateViews::CloseActivePopup() { |
| 109 GetDelegateViews()->HideActivePopup(); |
| 110 } |
| 111 |
| 112 void ExtensionActionPlatformDelegateViews::CloseOwnPopup() { |
| 113 // We should only be asked to close the popup if we're showing one. |
| 114 DCHECK(popup_); |
| 115 CleanupPopup(true); |
| 116 } |
| 117 |
| 118 bool ExtensionActionPlatformDelegateViews::ShowPopupWithUrl( |
| 119 ExtensionActionViewController::PopupShowAction show_action, |
| 120 const GURL& popup_url, |
| 121 bool grant_tab_permissions) { |
| 122 views::BubbleBorder::Arrow arrow = base::i18n::IsRTL() ? |
| 123 views::BubbleBorder::TOP_LEFT : views::BubbleBorder::TOP_RIGHT; |
| 124 |
| 125 views::View* reference_view = GetDelegateViews()->GetReferenceViewForPopup(); |
| 126 |
| 127 ExtensionPopup::ShowAction popup_show_action = |
| 128 show_action == ExtensionActionViewController::SHOW_POPUP ? |
| 129 ExtensionPopup::SHOW : ExtensionPopup::SHOW_AND_INSPECT; |
| 130 popup_ = ExtensionPopup::ShowPopup(popup_url, |
| 131 controller_->browser(), |
| 132 reference_view, |
| 133 arrow, |
| 134 popup_show_action); |
| 135 popup_->GetWidget()->AddObserver(this); |
| 136 |
| 137 GetDelegateViews()->OnPopupShown(grant_tab_permissions); |
| 138 |
| 139 return true; |
| 140 } |
| 141 |
| 142 void ExtensionActionPlatformDelegateViews::Observe( |
| 143 int type, |
| 144 const content::NotificationSource& source, |
| 145 const content::NotificationDetails& details) { |
| 146 DCHECK(type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED || |
| 147 type == extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED); |
| 148 std::pair<const std::string, const std::string>* payload = |
| 149 content::Details<std::pair<const std::string, const std::string> >( |
| 150 details).ptr(); |
| 151 if (controller_->extension()->id() == payload->first && |
| 152 payload->second == |
| 153 extensions::manifest_values::kBrowserActionCommandEvent) { |
| 154 if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED) |
| 155 RegisterCommand(); |
| 156 else |
| 157 UnregisterCommand(true); |
| 158 } |
| 159 } |
| 160 |
| 161 bool ExtensionActionPlatformDelegateViews::AcceleratorPressed( |
| 162 const ui::Accelerator& accelerator) { |
| 163 // We shouldn't be handling any accelerators if the view is hidden, unless |
| 164 // this is a browser action. |
| 165 DCHECK(controller_->extension_action()->action_type() == |
| 166 ActionInfo::TYPE_BROWSER || |
| 167 GetDelegateViews()->GetAsView()->visible()); |
| 168 |
| 169 // Normal priority shortcuts must be handled via standard browser commands to |
| 170 // be processed at the proper time. |
| 171 if (GetAcceleratorPriority(accelerator, controller_->extension()) == |
| 172 ui::AcceleratorManager::kNormalPriority) |
| 173 return false; |
| 174 |
| 175 controller_->ExecuteAction(true); |
| 176 return true; |
| 177 } |
| 178 |
| 179 bool ExtensionActionPlatformDelegateViews::CanHandleAccelerators() const { |
| 180 // Page actions can only handle accelerators when they are visible. |
| 181 // Browser actions can handle accelerators even when not visible, since they |
| 182 // might be hidden in an overflow menu. |
| 183 return controller_->extension_action()->action_type() == |
| 184 ActionInfo::TYPE_PAGE ? GetDelegateViews()->GetAsView()->visible() : |
| 185 true; |
| 186 } |
| 187 |
| 188 void ExtensionActionPlatformDelegateViews::OnWidgetDestroying( |
| 189 views::Widget* widget) { |
| 190 DCHECK(popup_); |
| 191 DCHECK_EQ(popup_->GetWidget(), widget); |
| 192 CleanupPopup(false); |
| 193 } |
| 194 |
| 195 void ExtensionActionPlatformDelegateViews::ShowContextMenuForView( |
| 196 views::View* source, |
| 197 const gfx::Point& point, |
| 198 ui::MenuSourceType source_type) { |
| 199 // If there's another active menu that won't be dismissed by opening this one, |
| 200 // then we can't show this one right away, since we can only show one nested |
| 201 // menu at a time. |
| 202 // If the other menu is an extension action's context menu, then we'll run |
| 203 // this one after that one closes. If it's a different type of menu, then we |
| 204 // close it and give up, for want of a better solution. (Luckily, this is |
| 205 // rare). |
| 206 // TODO(devlin): Update this when views code no longer runs menus in a nested |
| 207 // loop. |
| 208 if (context_menu_owner) { |
| 209 context_menu_owner->followup_context_menu_task_ = |
| 210 base::Bind(&ExtensionActionPlatformDelegateViews::DoShowContextMenu, |
| 211 weak_factory_.GetWeakPtr(), |
| 212 source_type); |
| 213 } |
| 214 if (CloseActiveMenuIfNeeded()) |
| 215 return; |
| 216 |
| 217 // Otherwise, no other menu is showing, and we can proceed normally. |
| 218 DoShowContextMenu(source_type); |
| 219 } |
| 220 |
| 221 void ExtensionActionPlatformDelegateViews::DoShowContextMenu( |
| 222 ui::MenuSourceType source_type) { |
| 223 if (!controller_->extension()->ShowConfigureContextMenus()) |
| 224 return; |
| 225 |
| 226 DCHECK(!context_menu_owner); |
| 227 context_menu_owner = this; |
| 228 |
| 229 // We shouldn't have both a popup and a context menu showing. |
| 230 GetDelegateViews()->HideActivePopup(); |
| 231 |
| 232 // Reconstructs the menu every time because the menu's contents are dynamic. |
| 233 scoped_refptr<ExtensionContextMenuModel> context_menu_model( |
| 234 new ExtensionContextMenuModel( |
| 235 controller_->extension(), controller_->browser(), controller_)); |
| 236 |
| 237 gfx::Point screen_loc; |
| 238 views::View::ConvertPointToScreen(GetDelegateViews()->GetAsView(), |
| 239 &screen_loc); |
| 240 |
| 241 int run_types = |
| 242 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU; |
| 243 if (GetDelegateViews()->IsShownInMenu()) |
| 244 run_types |= views::MenuRunner::IS_NESTED; |
| 245 |
| 246 views::Widget* parent = GetDelegateViews()->GetParentForContextMenu(); |
| 247 |
| 248 menu_runner_.reset( |
| 249 new views::MenuRunner(context_menu_model.get(), run_types)); |
| 250 |
| 251 if (menu_runner_->RunMenuAt( |
| 252 parent, |
| 253 GetDelegateViews()->GetContextMenuButton(), |
| 254 gfx::Rect(screen_loc, GetDelegateViews()->GetAsView()->size()), |
| 255 views::MENU_ANCHOR_TOPLEFT, |
| 256 source_type) == views::MenuRunner::MENU_DELETED) { |
| 257 return; |
| 258 } |
| 259 |
| 260 context_menu_owner = NULL; |
| 261 menu_runner_.reset(); |
| 262 |
| 263 // If another extension action wants to show its context menu, allow it to. |
| 264 if (!followup_context_menu_task_.is_null()) { |
| 265 base::Closure task = followup_context_menu_task_; |
| 266 followup_context_menu_task_ = base::Closure(); |
| 267 task.Run(); |
| 268 } |
| 269 } |
| 270 |
| 271 void ExtensionActionPlatformDelegateViews::UnregisterCommand( |
| 272 bool only_if_removed) { |
| 273 views::FocusManager* focus_manager = |
| 274 GetDelegateViews()->GetFocusManagerForAccelerator(); |
| 275 if (!focus_manager || !action_keybinding_.get()) |
| 276 return; |
| 277 |
| 278 // If |only_if_removed| is true, it means that we only need to unregister |
| 279 // ourselves as an accelerator if the command was removed. Otherwise, we need |
| 280 // to unregister ourselves no matter what (likely because we are shutting |
| 281 // down). |
| 282 extensions::Command extension_command; |
| 283 if (!only_if_removed || |
| 284 !controller_->GetExtensionCommand(&extension_command)) { |
| 285 focus_manager->UnregisterAccelerator(*action_keybinding_, this); |
| 286 action_keybinding_.reset(); |
| 287 } |
| 288 } |
| 289 |
| 290 bool ExtensionActionPlatformDelegateViews::CloseActiveMenuIfNeeded() { |
| 291 // If this view is shown inside another menu, there's a possibility that there |
| 292 // is another context menu showing that we have to close before we can |
| 293 // activate a different menu. |
| 294 if (GetDelegateViews()->IsShownInMenu()) { |
| 295 views::MenuController* menu_controller = |
| 296 views::MenuController::GetActiveInstance(); |
| 297 // If this is shown inside a menu, then there should always be an active |
| 298 // menu controller. |
| 299 DCHECK(menu_controller); |
| 300 if (menu_controller->in_nested_run()) { |
| 301 // There is another menu showing. Close the outermost menu (since we are |
| 302 // shown in the same menu, we don't want to close the whole thing). |
| 303 menu_controller->Cancel(views::MenuController::EXIT_OUTERMOST); |
| 304 return true; |
| 305 } |
| 306 } |
| 307 |
| 308 return false; |
| 309 } |
| 310 |
| 311 void ExtensionActionPlatformDelegateViews::CleanupPopup(bool close_widget) { |
| 312 DCHECK(popup_); |
| 313 GetDelegateViews()->CleanupPopup(); |
| 314 popup_->GetWidget()->RemoveObserver(this); |
| 315 if (close_widget) |
| 316 popup_->GetWidget()->Close(); |
| 317 popup_ = NULL; |
| 318 } |
| 319 |
| 320 ToolbarActionViewDelegateViews* |
| 321 ExtensionActionPlatformDelegateViews::GetDelegateViews() const { |
| 322 return static_cast<ToolbarActionViewDelegateViews*>( |
| 323 controller_->view_delegate()); |
| 324 } |
OLD | NEW |