Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(280)

Side by Side Diff: chrome/browser/ui/views/extensions/extension_action_view_controller.cc

Issue 661493004: Add infrastructure for Chrome Actions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/views/extensions/extension_action_view_controller.h" 5 #include "chrome/browser/ui/views/extensions/extension_action_view_controller.h"
6 6
7 #include "base/logging.h" 7 #include "base/logging.h"
8 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/browser/extensions/api/commands/command_service.h" 9 #include "chrome/browser/extensions/api/commands/command_service.h"
9 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" 10 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
10 #include "chrome/browser/extensions/extension_action.h" 11 #include "chrome/browser/extensions/extension_action.h"
11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/sessions/session_tab_helper.h" 13 #include "chrome/browser/sessions/session_tab_helper.h"
13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/extensions/accelerator_priority.h" 15 #include "chrome/browser/ui/extensions/accelerator_priority.h"
15 #include "chrome/browser/ui/views/extensions/extension_action_view_delegate.h" 16 #include "chrome/browser/ui/views/toolbar/toolbar_action_view_delegate.h"
17 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
16 #include "chrome/common/extensions/api/extension_action/action_info.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"
17 #include "extensions/common/extension.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"
18 #include "ui/views/controls/menu/menu_controller.h" 26 #include "ui/views/controls/menu/menu_controller.h"
19 #include "ui/views/controls/menu/menu_runner.h" 27 #include "ui/views/controls/menu/menu_runner.h"
20 #include "ui/views/view.h" 28 #include "ui/views/view.h"
21 #include "ui/views/widget/widget.h" 29 #include "ui/views/widget/widget.h"
22 30
23 using extensions::ActionInfo; 31 using extensions::ActionInfo;
24 using extensions::CommandService; 32 using extensions::CommandService;
25 33
26 namespace { 34 namespace {
27 35
28 // The ExtensionActionViewController which is currently showing its context 36 // The ExtensionActionViewController which is currently showing its context
29 // menu, if any. 37 // menu, if any.
30 // Since only one context menu can be shown (even across browser windows), it's 38 // Since only one context menu can be shown (even across browser windows), it's
31 // safe to have this be a global singleton. 39 // safe to have this be a global singleton.
32 ExtensionActionViewController* context_menu_owner = NULL; 40 ExtensionActionViewController* context_menu_owner = NULL;
33 41
34 } // namespace 42 } // namespace
35 43
36 ExtensionActionViewController::ExtensionActionViewController( 44 ExtensionActionViewController::ExtensionActionViewController(
37 const extensions::Extension* extension, 45 const extensions::Extension* extension,
38 Browser* browser, 46 Browser* browser,
39 ExtensionAction* extension_action, 47 ExtensionAction* extension_action)
40 ExtensionActionViewDelegate* delegate)
41 : extension_(extension), 48 : extension_(extension),
42 browser_(browser), 49 browser_(browser),
43 extension_action_(extension_action), 50 extension_action_(extension_action),
44 delegate_(delegate), 51 delegate_(nullptr),
45 icon_factory_(browser->profile(), extension, extension_action, this), 52 icon_factory_(browser->profile(), extension, extension_action, this),
46 popup_(NULL), 53 icon_observer_(nullptr),
54 popup_(nullptr),
47 weak_factory_(this) { 55 weak_factory_(this) {
48 DCHECK(extension_action); 56 DCHECK(extension_action);
49 DCHECK(extension_action->action_type() == ActionInfo::TYPE_PAGE || 57 DCHECK(extension_action->action_type() == ActionInfo::TYPE_PAGE ||
50 extension_action->action_type() == ActionInfo::TYPE_BROWSER); 58 extension_action->action_type() == ActionInfo::TYPE_BROWSER);
51 DCHECK(extension); 59 DCHECK(extension);
60
61 content::NotificationSource notification_source =
62 content::Source<Profile>(browser->profile()->GetOriginalProfile());
63 registrar_.Add(this,
64 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
65 notification_source);
66 registrar_.Add(this,
67 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
68 notification_source);
52 } 69 }
53 70
54 ExtensionActionViewController::~ExtensionActionViewController() { 71 ExtensionActionViewController::~ExtensionActionViewController() {
55 if (context_menu_owner == this) 72 if (context_menu_owner == this)
56 context_menu_owner = NULL; 73 context_menu_owner = NULL;
57 HidePopup(); 74 HidePopup();
58 UnregisterCommand(false); 75 UnregisterCommand(false);
59 } 76 }
60 77
61 void ExtensionActionViewController::InspectPopup() { 78 const std::string& ExtensionActionViewController::GetId() const {
62 ExecuteAction(ExtensionPopup::SHOW_AND_INSPECT, true); 79 return extension_->id();
63 } 80 }
64 81
65 void ExtensionActionViewController::ExecuteActionByUser() { 82 void ExtensionActionViewController::SetDelegate(
66 ExecuteAction(ExtensionPopup::SHOW, true); 83 ToolbarActionViewDelegate* delegate) {
84 delegate_ = delegate;
85 delegate_->GetAsView()->set_context_menu_controller(this);
67 } 86 }
68 87
69 bool ExtensionActionViewController::ExecuteAction( 88 gfx::Image ExtensionActionViewController::GetIcon(
70 ExtensionPopup::ShowAction show_action, bool grant_tab_permissions) { 89 content::WebContents* web_contents) {
71 if (extensions::ExtensionActionAPI::Get(browser_->profile())-> 90 return icon_factory_.GetIcon(SessionTabHelper::IdForTab(web_contents));
72 ExecuteExtensionAction(extension_, browser_, grant_tab_permissions) == 91 }
73 ExtensionAction::ACTION_SHOW_POPUP) { 92
74 GURL popup_url = extension_action_->GetPopupUrl(GetCurrentTabId()); 93 gfx::ImageSkia ExtensionActionViewController::GetIconWithBadge() {
75 return delegate_->GetPreferredPopupViewController()->ShowPopupWithUrl( 94 content::WebContents* web_contents = delegate_->GetCurrentWebContents();
76 show_action, popup_url, grant_tab_permissions); 95 gfx::Size spacing(0, ToolbarView::kVertSpacing);
77 } 96 gfx::ImageSkia icon = *GetIcon(web_contents).ToImageSkia();
78 return false; 97 if (!IsEnabled(web_contents))
98 icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25);
99 return extension_action_->GetIconWithBadge(
100 icon, SessionTabHelper::IdForTab(web_contents), spacing);
101 }
102
103 base::string16 ExtensionActionViewController::GetAccessibleName(
104 content::WebContents* web_contents) const {
105 std::string title =
106 extension_action()->GetTitle(SessionTabHelper::IdForTab(web_contents));
107 return base::UTF8ToUTF16(title.empty() ? extension()->name() : title);
108 }
109
110 base::string16 ExtensionActionViewController::GetTooltip(
111 content::WebContents* web_contents) const {
112 return GetAccessibleName(web_contents);
113 }
114
115 bool ExtensionActionViewController::IsEnabled(
116 content::WebContents* web_contents) const {
117 return extension_action_->GetIsVisible(
118 SessionTabHelper::IdForTab(web_contents));
119 }
120
121 bool ExtensionActionViewController::HasPopup(
122 content::WebContents* web_contents) const {
123 int tab_id = SessionTabHelper::IdForTab(web_contents);
124 return (tab_id < 0) ? false : extension_action_->HasPopup(tab_id);
79 } 125 }
80 126
81 void ExtensionActionViewController::HidePopup() { 127 void ExtensionActionViewController::HidePopup() {
82 if (popup_) 128 if (popup_)
83 CleanupPopup(true); 129 CleanupPopup(true);
84 } 130 }
85 131
86 gfx::Image ExtensionActionViewController::GetIcon(int tab_id) { 132 gfx::NativeView ExtensionActionViewController::GetPopupNativeView() {
87 return icon_factory_.GetIcon(tab_id); 133 return popup_ ? popup_->GetWidget()->GetNativeView() : nullptr;
88 } 134 }
89 135
90 int ExtensionActionViewController::GetCurrentTabId() const { 136 bool ExtensionActionViewController::IsMenuRunning() const {
91 content::WebContents* web_contents = delegate_->GetCurrentWebContents(); 137 return menu_runner_.get() != NULL;
92 return web_contents ? SessionTabHelper::IdForTab(web_contents) : -1; 138 }
139
140 bool ExtensionActionViewController::CanDrag() const {
141 return true;
142 }
143
144 bool ExtensionActionViewController::ExecuteAction(bool by_user) {
145 return ExecuteAction(ExtensionPopup::SHOW, by_user);
146 }
147
148 void ExtensionActionViewController::PaintExtra(
149 gfx::Canvas* canvas,
150 const gfx::Rect& bounds,
151 content::WebContents* web_contents) const {
152 int tab_id = SessionTabHelper::IdForTab(web_contents);
153 if (tab_id >= 0)
154 extension_action_->PaintBadge(canvas, bounds, tab_id);
93 } 155 }
94 156
95 void ExtensionActionViewController::RegisterCommand() { 157 void ExtensionActionViewController::RegisterCommand() {
96 // If we've already registered, do nothing. 158 // If we've already registered, do nothing.
97 if (action_keybinding_.get()) 159 if (action_keybinding_.get())
98 return; 160 return;
99 161
100 extensions::Command extension_command; 162 extensions::Command extension_command;
101 views::FocusManager* focus_manager = 163 views::FocusManager* focus_manager =
102 delegate_->GetFocusManagerForAccelerator(); 164 delegate_->GetFocusManagerForAccelerator();
103 if (focus_manager && GetExtensionCommand(&extension_command)) { 165 if (focus_manager && GetExtensionCommand(&extension_command)) {
104 action_keybinding_.reset( 166 action_keybinding_.reset(
105 new ui::Accelerator(extension_command.accelerator())); 167 new ui::Accelerator(extension_command.accelerator()));
106 focus_manager->RegisterAccelerator( 168 focus_manager->RegisterAccelerator(
107 *action_keybinding_, 169 *action_keybinding_,
108 GetAcceleratorPriority(extension_command.accelerator(), extension_), 170 GetAcceleratorPriority(extension_command.accelerator(), extension_),
109 this); 171 this);
110 } 172 }
111 } 173 }
112 174
113 void ExtensionActionViewController::UnregisterCommand(bool only_if_removed) { 175 void ExtensionActionViewController::InspectPopup() {
114 views::FocusManager* focus_manager = 176 ExecuteAction(ExtensionPopup::SHOW_AND_INSPECT, true);
115 delegate_->GetFocusManagerForAccelerator(); 177 }
116 if (!focus_manager || !action_keybinding_.get())
117 return;
118 178
119 // If |only_if_removed| is true, it means that we only need to unregister 179 bool ExtensionActionViewController::ExecuteAction(
120 // ourselves as an accelerator if the command was removed. Otherwise, we need 180 ExtensionPopup::ShowAction show_action, bool grant_tab_permissions) {
121 // to unregister ourselves no matter what (likely because we are shutting 181 if (extensions::ExtensionActionAPI::Get(browser_->profile())->
122 // down). 182 ExecuteExtensionAction(extension_, browser_, grant_tab_permissions) ==
123 extensions::Command extension_command; 183 ExtensionAction::ACTION_SHOW_POPUP) {
124 if (!only_if_removed || !GetExtensionCommand(&extension_command)) { 184 GURL popup_url = extension_action_->GetPopupUrl(
125 focus_manager->UnregisterAccelerator(*action_keybinding_, this); 185 SessionTabHelper::IdForTab(delegate_->GetCurrentWebContents()));
126 action_keybinding_.reset(); 186 return static_cast<ExtensionActionViewController*>(
187 delegate_->GetPreferredPopupViewController())->ShowPopupWithUrl(
188 show_action, popup_url, grant_tab_permissions);
127 } 189 }
190 return false;
128 } 191 }
129 192
130 void ExtensionActionViewController::OnIconUpdated() { 193 void ExtensionActionViewController::OnIconUpdated() {
131 delegate_->OnIconUpdated(); 194 delegate_->OnIconUpdated();
195 if (icon_observer_)
196 icon_observer_->OnIconUpdated();
132 } 197 }
133 198
134 bool ExtensionActionViewController::AcceleratorPressed( 199 bool ExtensionActionViewController::AcceleratorPressed(
135 const ui::Accelerator& accelerator) { 200 const ui::Accelerator& accelerator) {
136 // We shouldn't be handling any accelerators if the view is hidden, unless 201 // We shouldn't be handling any accelerators if the view is hidden, unless
137 // this is a browser action. 202 // this is a browser action.
138 DCHECK(extension_action_->action_type() == ActionInfo::TYPE_BROWSER || 203 DCHECK(extension_action_->action_type() == ActionInfo::TYPE_BROWSER ||
139 delegate_->GetAsView()->visible()); 204 delegate_->GetAsView()->visible());
140 205
141 // Normal priority shortcuts must be handled via standard browser commands to 206 // Normal priority shortcuts must be handled via standard browser commands to
142 // be processed at the proper time. 207 // be processed at the proper time.
143 if (GetAcceleratorPriority(accelerator, extension()) == 208 if (GetAcceleratorPriority(accelerator, extension()) ==
144 ui::AcceleratorManager::kNormalPriority) 209 ui::AcceleratorManager::kNormalPriority)
145 return false; 210 return false;
146 211
147 ExecuteActionByUser(); 212 ExecuteAction(true);
148 return true; 213 return true;
149 } 214 }
150 215
151 bool ExtensionActionViewController::CanHandleAccelerators() const { 216 bool ExtensionActionViewController::CanHandleAccelerators() const {
152 // Page actions can only handle accelerators when they are visible. 217 // Page actions can only handle accelerators when they are visible.
153 // Browser actions can handle accelerators even when not visible, since they 218 // Browser actions can handle accelerators even when not visible, since they
154 // might be hidden in an overflow menu. 219 // might be hidden in an overflow menu.
155 return extension_action_->action_type() == ActionInfo::TYPE_PAGE ? 220 return extension_action_->action_type() == ActionInfo::TYPE_PAGE ?
156 delegate_->GetAsView()->visible() : true; 221 delegate_->GetAsView()->visible() : true;
157 } 222 }
158 223
159 void ExtensionActionViewController::OnWidgetDestroying(views::Widget* widget) { 224 void ExtensionActionViewController::OnWidgetDestroying(views::Widget* widget) {
160 DCHECK(popup_); 225 DCHECK(popup_);
161 DCHECK_EQ(popup_->GetWidget(), widget); 226 DCHECK_EQ(popup_->GetWidget(), widget);
162 CleanupPopup(false); 227 CleanupPopup(false);
163 } 228 }
164 229
165 void ExtensionActionViewController::ShowContextMenuForView( 230 void ExtensionActionViewController::ShowContextMenuForView(
166 views::View* source, 231 views::View* source,
167 const gfx::Point& point, 232 const gfx::Point& point,
168 ui::MenuSourceType source_type) { 233 ui::MenuSourceType source_type) {
169
170 // If there's another active menu that won't be dismissed by opening this one, 234 // If there's another active menu that won't be dismissed by opening this one,
171 // then we can't show this one right away, since we can only show one nested 235 // then we can't show this one right away, since we can only show one nested
172 // menu at a time. 236 // menu at a time.
173 // If the other menu is an extension action's context menu, then we'll run 237 // If the other menu is an extension action's context menu, then we'll run
174 // this one after that one closes. If it's a different type of menu, then we 238 // this one after that one closes. If it's a different type of menu, then we
175 // close it and give up, for want of a better solution. (Luckily, this is 239 // close it and give up, for want of a better solution. (Luckily, this is
176 // rare). 240 // rare).
177 // TODO(devlin): Update this when views code no longer runs menus in a nested 241 // TODO(devlin): Update this when views code no longer runs menus in a nested
178 // loop. 242 // loop.
179 if (context_menu_owner) { 243 if (context_menu_owner) {
180 context_menu_owner->followup_context_menu_task_ = 244 context_menu_owner->followup_context_menu_task_ =
181 base::Bind(&ExtensionActionViewController::DoShowContextMenu, 245 base::Bind(&ExtensionActionViewController::DoShowContextMenu,
182 weak_factory_.GetWeakPtr(), 246 weak_factory_.GetWeakPtr(),
183 source_type); 247 source_type);
184 } 248 }
185 if (CloseActiveMenuIfNeeded()) 249 if (CloseActiveMenuIfNeeded())
186 return; 250 return;
187 251
188 // Otherwise, no other menu is showing, and we can proceed normally. 252 // Otherwise, no other menu is showing, and we can proceed normally.
189 DoShowContextMenu(source_type); 253 DoShowContextMenu(source_type);
190 } 254 }
191 255
256 void ExtensionActionViewController::Observe(
257 int type,
258 const content::NotificationSource& source,
259 const content::NotificationDetails& details) {
260 DCHECK(type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED ||
261 type == extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED);
262 std::pair<const std::string, const std::string>* payload =
263 content::Details<std::pair<const std::string, const std::string> >(
264 details).ptr();
265 if (extension_->id() == payload->first &&
266 payload->second ==
267 extensions::manifest_values::kBrowserActionCommandEvent) {
268 if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED)
269 RegisterCommand();
270 else
271 UnregisterCommand(true);
272 }
273 }
274
192 void ExtensionActionViewController::DoShowContextMenu( 275 void ExtensionActionViewController::DoShowContextMenu(
193 ui::MenuSourceType source_type) { 276 ui::MenuSourceType source_type) {
194 if (!extension_->ShowConfigureContextMenus()) 277 if (!extension_->ShowConfigureContextMenus())
195 return; 278 return;
196 279
197 DCHECK(!context_menu_owner); 280 DCHECK(!context_menu_owner);
198 context_menu_owner = this; 281 context_menu_owner = this;
199 282
200 // We shouldn't have both a popup and a context menu showing. 283 // We shouldn't have both a popup and a context menu showing.
201 delegate_->HideActivePopup(); 284 delegate_->HideActivePopup();
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
275 DCHECK(command); 358 DCHECK(command);
276 CommandService* command_service = CommandService::Get(browser_->profile()); 359 CommandService* command_service = CommandService::Get(browser_->profile());
277 if (extension_action_->action_type() == ActionInfo::TYPE_PAGE) { 360 if (extension_action_->action_type() == ActionInfo::TYPE_PAGE) {
278 return command_service->GetPageActionCommand( 361 return command_service->GetPageActionCommand(
279 extension_->id(), CommandService::ACTIVE_ONLY, command, NULL); 362 extension_->id(), CommandService::ACTIVE_ONLY, command, NULL);
280 } 363 }
281 return command_service->GetBrowserActionCommand( 364 return command_service->GetBrowserActionCommand(
282 extension_->id(), CommandService::ACTIVE_ONLY, command, NULL); 365 extension_->id(), CommandService::ACTIVE_ONLY, command, NULL);
283 } 366 }
284 367
368 void ExtensionActionViewController::UnregisterCommand(bool only_if_removed) {
369 views::FocusManager* focus_manager =
370 delegate_->GetFocusManagerForAccelerator();
371 if (!focus_manager || !action_keybinding_.get())
372 return;
373
374 // If |only_if_removed| is true, it means that we only need to unregister
375 // ourselves as an accelerator if the command was removed. Otherwise, we need
376 // to unregister ourselves no matter what (likely because we are shutting
377 // down).
378 extensions::Command extension_command;
379 if (!only_if_removed || !GetExtensionCommand(&extension_command)) {
380 focus_manager->UnregisterAccelerator(*action_keybinding_, this);
381 action_keybinding_.reset();
382 }
383 }
384
285 bool ExtensionActionViewController::CloseActiveMenuIfNeeded() { 385 bool ExtensionActionViewController::CloseActiveMenuIfNeeded() {
286 // If this view is shown inside another menu, there's a possibility that there 386 // If this view is shown inside another menu, there's a possibility that there
287 // is another context menu showing that we have to close before we can 387 // is another context menu showing that we have to close before we can
288 // activate a different menu. 388 // activate a different menu.
289 if (delegate_->IsShownInMenu()) { 389 if (delegate_->IsShownInMenu()) {
290 views::MenuController* menu_controller = 390 views::MenuController* menu_controller =
291 views::MenuController::GetActiveInstance(); 391 views::MenuController::GetActiveInstance();
292 // If this is shown inside a menu, then there should always be an active 392 // If this is shown inside a menu, then there should always be an active
293 // menu controller. 393 // menu controller.
294 DCHECK(menu_controller); 394 DCHECK(menu_controller);
295 if (menu_controller->in_nested_run()) { 395 if (menu_controller->in_nested_run()) {
296 // There is another menu showing. Close the outermost menu (since we are 396 // There is another menu showing. Close the outermost menu (since we are
297 // shown in the same menu, we don't want to close the whole thing). 397 // shown in the same menu, we don't want to close the whole thing).
298 menu_controller->Cancel(views::MenuController::EXIT_OUTERMOST); 398 menu_controller->Cancel(views::MenuController::EXIT_OUTERMOST);
299 return true; 399 return true;
300 } 400 }
301 } 401 }
302 402
303 return false; 403 return false;
304 } 404 }
305 405
306 void ExtensionActionViewController::CleanupPopup(bool close_widget) { 406 void ExtensionActionViewController::CleanupPopup(bool close_widget) {
307 DCHECK(popup_); 407 DCHECK(popup_);
308 delegate_->CleanupPopup(); 408 delegate_->CleanupPopup();
309 popup_->GetWidget()->RemoveObserver(this); 409 popup_->GetWidget()->RemoveObserver(this);
310 if (close_widget) 410 if (close_widget)
311 popup_->GetWidget()->Close(); 411 popup_->GetWidget()->Close();
312 popup_ = NULL; 412 popup_ = NULL;
313 } 413 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698