Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / extensions / extension_action_platform_delegate_views.cc
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   // TOP_RIGHT is correct for both RTL and LTR, because the views platform
123   // performs the flipping in RTL cases.
124   views::BubbleBorder::Arrow arrow = views::BubbleBorder::TOP_RIGHT;
125
126   views::View* reference_view = GetDelegateViews()->GetReferenceViewForPopup();
127
128   ExtensionPopup::ShowAction popup_show_action =
129       show_action == ExtensionActionViewController::SHOW_POPUP ?
130           ExtensionPopup::SHOW : ExtensionPopup::SHOW_AND_INSPECT;
131   popup_ = ExtensionPopup::ShowPopup(popup_url,
132                                      controller_->browser(),
133                                      reference_view,
134                                      arrow,
135                                      popup_show_action);
136   popup_->GetWidget()->AddObserver(this);
137
138   GetDelegateViews()->OnPopupShown(grant_tab_permissions);
139
140   return true;
141 }
142
143 void ExtensionActionPlatformDelegateViews::Observe(
144     int type,
145     const content::NotificationSource& source,
146     const content::NotificationDetails& details) {
147   DCHECK(type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED ||
148          type == extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED);
149   std::pair<const std::string, const std::string>* payload =
150       content::Details<std::pair<const std::string, const std::string> >(
151           details).ptr();
152   if (controller_->extension()->id() == payload->first &&
153       payload->second ==
154           extensions::manifest_values::kBrowserActionCommandEvent) {
155     if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED)
156       RegisterCommand();
157     else
158       UnregisterCommand(true);
159   }
160 }
161
162 bool ExtensionActionPlatformDelegateViews::AcceleratorPressed(
163     const ui::Accelerator& accelerator) {
164   // We shouldn't be handling any accelerators if the view is hidden, unless
165   // this is a browser action.
166   DCHECK(controller_->extension_action()->action_type() ==
167              ActionInfo::TYPE_BROWSER ||
168          GetDelegateViews()->GetAsView()->visible());
169
170   // Normal priority shortcuts must be handled via standard browser commands to
171   // be processed at the proper time.
172   if (GetAcceleratorPriority(accelerator, controller_->extension()) ==
173       ui::AcceleratorManager::kNormalPriority)
174     return false;
175
176   controller_->ExecuteAction(true);
177   return true;
178 }
179
180 bool ExtensionActionPlatformDelegateViews::CanHandleAccelerators() const {
181   // Page actions can only handle accelerators when they are visible.
182   // Browser actions can handle accelerators even when not visible, since they
183   // might be hidden in an overflow menu.
184   return controller_->extension_action()->action_type() ==
185       ActionInfo::TYPE_PAGE ? GetDelegateViews()->GetAsView()->visible() :
186           true;
187 }
188
189 void ExtensionActionPlatformDelegateViews::OnWidgetDestroying(
190     views::Widget* widget) {
191   DCHECK(popup_);
192   DCHECK_EQ(popup_->GetWidget(), widget);
193   CleanupPopup(false);
194 }
195
196 void ExtensionActionPlatformDelegateViews::ShowContextMenuForView(
197     views::View* source,
198     const gfx::Point& point,
199     ui::MenuSourceType source_type) {
200   // If there's another active menu that won't be dismissed by opening this one,
201   // then we can't show this one right away, since we can only show one nested
202   // menu at a time.
203   // If the other menu is an extension action's context menu, then we'll run
204   // this one after that one closes. If it's a different type of menu, then we
205   // close it and give up, for want of a better solution. (Luckily, this is
206   // rare).
207   // TODO(devlin): Update this when views code no longer runs menus in a nested
208   // loop.
209   if (context_menu_owner) {
210     context_menu_owner->followup_context_menu_task_ =
211         base::Bind(&ExtensionActionPlatformDelegateViews::DoShowContextMenu,
212                    weak_factory_.GetWeakPtr(),
213                    source_type);
214   }
215   if (CloseActiveMenuIfNeeded())
216     return;
217
218   // Otherwise, no other menu is showing, and we can proceed normally.
219   DoShowContextMenu(source_type);
220 }
221
222 void ExtensionActionPlatformDelegateViews::DoShowContextMenu(
223     ui::MenuSourceType source_type) {
224   if (!controller_->extension()->ShowConfigureContextMenus())
225     return;
226
227   DCHECK(!context_menu_owner);
228   context_menu_owner = this;
229
230   // We shouldn't have both a popup and a context menu showing.
231   GetDelegateViews()->HideActivePopup();
232
233   // Reconstructs the menu every time because the menu's contents are dynamic.
234   scoped_refptr<ExtensionContextMenuModel> context_menu_model(
235       new ExtensionContextMenuModel(
236           controller_->extension(), controller_->browser(), controller_));
237
238   gfx::Point screen_loc;
239   views::View::ConvertPointToScreen(GetDelegateViews()->GetAsView(),
240                                     &screen_loc);
241
242   int run_types =
243       views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU;
244   if (GetDelegateViews()->IsShownInMenu())
245     run_types |= views::MenuRunner::IS_NESTED;
246
247   views::Widget* parent = GetDelegateViews()->GetParentForContextMenu();
248
249   menu_runner_.reset(
250       new views::MenuRunner(context_menu_model.get(), run_types));
251
252   if (menu_runner_->RunMenuAt(
253           parent,
254           GetDelegateViews()->GetContextMenuButton(),
255           gfx::Rect(screen_loc, GetDelegateViews()->GetAsView()->size()),
256           views::MENU_ANCHOR_TOPLEFT,
257           source_type) == views::MenuRunner::MENU_DELETED) {
258     return;
259   }
260
261   context_menu_owner = NULL;
262   menu_runner_.reset();
263
264   // If another extension action wants to show its context menu, allow it to.
265   if (!followup_context_menu_task_.is_null()) {
266     base::Closure task = followup_context_menu_task_;
267     followup_context_menu_task_ = base::Closure();
268     task.Run();
269   }
270 }
271
272 void ExtensionActionPlatformDelegateViews::UnregisterCommand(
273     bool only_if_removed) {
274   views::FocusManager* focus_manager =
275       GetDelegateViews()->GetFocusManagerForAccelerator();
276   if (!focus_manager || !action_keybinding_.get())
277     return;
278
279   // If |only_if_removed| is true, it means that we only need to unregister
280   // ourselves as an accelerator if the command was removed. Otherwise, we need
281   // to unregister ourselves no matter what (likely because we are shutting
282   // down).
283   extensions::Command extension_command;
284   if (!only_if_removed ||
285       !controller_->GetExtensionCommand(&extension_command)) {
286     focus_manager->UnregisterAccelerator(*action_keybinding_, this);
287     action_keybinding_.reset();
288   }
289 }
290
291 bool ExtensionActionPlatformDelegateViews::CloseActiveMenuIfNeeded() {
292   // If this view is shown inside another menu, there's a possibility that there
293   // is another context menu showing that we have to close before we can
294   // activate a different menu.
295   if (GetDelegateViews()->IsShownInMenu()) {
296     views::MenuController* menu_controller =
297         views::MenuController::GetActiveInstance();
298     // If this is shown inside a menu, then there should always be an active
299     // menu controller.
300     DCHECK(menu_controller);
301     if (menu_controller->in_nested_run()) {
302       // There is another menu showing. Close the outermost menu (since we are
303       // shown in the same menu, we don't want to close the whole thing).
304       menu_controller->Cancel(views::MenuController::EXIT_OUTERMOST);
305       return true;
306     }
307   }
308
309   return false;
310 }
311
312 void ExtensionActionPlatformDelegateViews::CleanupPopup(bool close_widget) {
313   DCHECK(popup_);
314   GetDelegateViews()->CleanupPopup();
315   popup_->GetWidget()->RemoveObserver(this);
316   if (close_widget)
317     popup_->GetWidget()->Close();
318   popup_ = NULL;
319 }
320
321 ToolbarActionViewDelegateViews*
322 ExtensionActionPlatformDelegateViews::GetDelegateViews() const {
323   return static_cast<ToolbarActionViewDelegateViews*>(
324       controller_->view_delegate());
325 }