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.
5 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
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/extensions/extension_view.h"
13 #include "chrome/browser/extensions/extension_view_host.h"
14 #include "chrome/browser/extensions/extension_view_host_factory.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_tab_helper.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/extensions/accelerator_priority.h"
19 #include "chrome/browser/ui/extensions/extension_action_platform_delegate.h"
20 #include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
21 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
22 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
23 #include "chrome/common/extensions/api/extension_action/action_info.h"
24 #include "chrome/common/icon_with_badge_image_source.h"
25 #include "extensions/browser/extension_host.h"
26 #include "extensions/browser/extension_registry.h"
27 #include "extensions/common/extension.h"
28 #include "extensions/common/feature_switch.h"
29 #include "extensions/common/manifest_constants.h"
30 #include "ui/gfx/image/image_skia.h"
31 #include "ui/gfx/image/image_skia_operations.h"
33 using extensions::ActionInfo
;
34 using extensions::CommandService
;
36 ExtensionActionViewController::ExtensionActionViewController(
37 const extensions::Extension
* extension
,
39 ExtensionAction
* extension_action
,
40 ToolbarActionsBar
* toolbar_actions_bar
)
41 : extension_(extension
),
43 extension_action_(extension_action
),
44 toolbar_actions_bar_(toolbar_actions_bar
),
46 view_delegate_(nullptr),
47 platform_delegate_(ExtensionActionPlatformDelegate::Create(this)),
48 icon_factory_(browser
->profile(), extension
, extension_action
, this),
49 icon_observer_(nullptr),
51 extensions::ExtensionRegistry::Get(browser_
->profile())),
52 popup_host_observer_(this),
54 DCHECK(extension_action
);
55 DCHECK(extension_action
->action_type() == ActionInfo::TYPE_PAGE
||
56 extension_action
->action_type() == ActionInfo::TYPE_BROWSER
);
60 ExtensionActionViewController::~ExtensionActionViewController() {
61 DCHECK(!is_showing_popup());
64 std::string
ExtensionActionViewController::GetId() const {
65 return extension_
->id();
68 void ExtensionActionViewController::SetDelegate(
69 ToolbarActionViewDelegate
* delegate
) {
70 DCHECK((delegate
== nullptr) ^ (view_delegate_
== nullptr));
72 view_delegate_
= delegate
;
73 platform_delegate_
->OnDelegateSet();
75 if (is_showing_popup())
77 platform_delegate_
.reset();
78 view_delegate_
= nullptr;
82 gfx::Image
ExtensionActionViewController::GetIcon(
83 content::WebContents
* web_contents
,
84 const gfx::Size
& size
) {
85 if (!ExtensionIsValid())
89 gfx::ImageSkia(GetIconImageSource(web_contents
, size
).release(), size
));
92 base::string16
ExtensionActionViewController::GetActionName() const {
93 if (!ExtensionIsValid())
94 return base::string16();
96 return base::UTF8ToUTF16(extension_
->name());
99 base::string16
ExtensionActionViewController::GetAccessibleName(
100 content::WebContents
* web_contents
) const {
101 if (!ExtensionIsValid())
102 return base::string16();
105 extension_action()->GetTitle(SessionTabHelper::IdForTab(web_contents
));
106 return base::UTF8ToUTF16(title
.empty() ? extension()->name() : title
);
109 base::string16
ExtensionActionViewController::GetTooltip(
110 content::WebContents
* web_contents
) const {
111 return GetAccessibleName(web_contents
);
114 bool ExtensionActionViewController::IsEnabled(
115 content::WebContents
* web_contents
) const {
116 if (!ExtensionIsValid())
119 return extension_action_
->GetIsVisible(
120 SessionTabHelper::IdForTab(web_contents
)) ||
121 extensions::ExtensionActionAPI::Get(browser_
->profile())->
122 ExtensionWantsToRun(extension(), web_contents
);
125 bool ExtensionActionViewController::WantsToRun(
126 content::WebContents
* web_contents
) const {
127 return extensions::ExtensionActionAPI::Get(browser_
->profile())->
128 ExtensionWantsToRun(extension(), web_contents
);
131 bool ExtensionActionViewController::HasPopup(
132 content::WebContents
* web_contents
) const {
133 if (!ExtensionIsValid())
136 int tab_id
= SessionTabHelper::IdForTab(web_contents
);
137 return (tab_id
< 0) ? false : extension_action_
->HasPopup(tab_id
);
140 void ExtensionActionViewController::HidePopup() {
141 if (is_showing_popup()) {
142 popup_host_
->Close();
143 // We need to do these actions synchronously (instead of closing and then
144 // performing the rest of the cleanup in OnExtensionHostDestroyed()) because
145 // the extension host may close asynchronously, and we need to keep the view
146 // delegate up-to-date.
152 gfx::NativeView
ExtensionActionViewController::GetPopupNativeView() {
153 return popup_host_
? popup_host_
->view()->GetNativeView() : nullptr;
156 ui::MenuModel
* ExtensionActionViewController::GetContextMenu() {
157 if (!ExtensionIsValid() || !extension()->ShowConfigureContextMenus())
160 ExtensionContextMenuModel::ButtonVisibility visibility
=
161 ExtensionContextMenuModel::VISIBLE
;
162 if (toolbar_actions_bar_
) {
163 if (toolbar_actions_bar_
->popped_out_action() == this)
164 visibility
= ExtensionContextMenuModel::TRANSITIVELY_VISIBLE
;
165 else if (!toolbar_actions_bar_
->IsActionVisibleOnMainBar(this))
166 visibility
= ExtensionContextMenuModel::OVERFLOWED
;
167 // Else, VISIBLE is correct.
169 // Reconstruct the menu every time because the menu's contents are dynamic.
170 context_menu_model_
= make_scoped_refptr(new ExtensionContextMenuModel(
171 extension(), browser_
, visibility
, this));
172 return context_menu_model_
.get();
175 void ExtensionActionViewController::OnContextMenuClosed() {
176 if (toolbar_actions_bar_
&&
177 toolbar_actions_bar_
->popped_out_action() == this &&
178 !is_showing_popup()) {
179 toolbar_actions_bar_
->UndoPopOut();
183 bool ExtensionActionViewController::ExecuteAction(bool by_user
) {
184 return ExecuteAction(SHOW_POPUP
, by_user
);
187 void ExtensionActionViewController::UpdateState() {
188 if (!ExtensionIsValid())
191 view_delegate_
->UpdateState();
194 bool ExtensionActionViewController::ExecuteAction(PopupShowAction show_action
,
195 bool grant_tab_permissions
) {
196 if (!ExtensionIsValid())
199 if (extensions::ExtensionActionAPI::Get(browser_
->profile())
200 ->ExecuteExtensionAction(
201 extension_
.get(), browser_
, grant_tab_permissions
) ==
202 ExtensionAction::ACTION_SHOW_POPUP
) {
203 GURL popup_url
= extension_action_
->GetPopupUrl(
204 SessionTabHelper::IdForTab(view_delegate_
->GetCurrentWebContents()));
205 return GetPreferredPopupViewController()
206 ->TriggerPopupWithUrl(show_action
, popup_url
, grant_tab_permissions
);
211 void ExtensionActionViewController::RegisterCommand() {
212 if (!ExtensionIsValid())
215 platform_delegate_
->RegisterCommand();
218 bool ExtensionActionViewController::DisabledClickOpensMenu() const {
219 return extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
222 void ExtensionActionViewController::InspectPopup() {
223 ExecuteAction(SHOW_POPUP_AND_INSPECT
, true);
226 void ExtensionActionViewController::OnIconUpdated() {
227 // We update the view first, so that if the observer relies on its UI it can
230 view_delegate_
->UpdateState();
232 icon_observer_
->OnIconUpdated();
235 void ExtensionActionViewController::OnExtensionHostDestroyed(
236 const extensions::ExtensionHost
* host
) {
240 bool ExtensionActionViewController::ExtensionIsValid() const {
241 return extension_registry_
->enabled_extensions().Contains(extension_
->id());
244 void ExtensionActionViewController::HideActivePopup() {
245 if (toolbar_actions_bar_
) {
246 toolbar_actions_bar_
->HideActivePopup();
248 DCHECK_EQ(ActionInfo::TYPE_PAGE
, extension_action_
->action_type());
249 // In the traditional toolbar, page actions only know how to close their own
255 bool ExtensionActionViewController::GetExtensionCommand(
256 extensions::Command
* command
) {
258 if (!ExtensionIsValid())
261 CommandService
* command_service
= CommandService::Get(browser_
->profile());
262 if (extension_action_
->action_type() == ActionInfo::TYPE_PAGE
) {
263 return command_service
->GetPageActionCommand(
264 extension_
->id(), CommandService::ACTIVE
, command
, NULL
);
266 return command_service
->GetBrowserActionCommand(
267 extension_
->id(), CommandService::ACTIVE
, command
, NULL
);
270 scoped_ptr
<IconWithBadgeImageSource
>
271 ExtensionActionViewController::GetIconImageSourceForTesting(
272 content::WebContents
* web_contents
,
273 const gfx::Size
& size
) {
274 return GetIconImageSource(web_contents
, size
);
277 ExtensionActionViewController
*
278 ExtensionActionViewController::GetPreferredPopupViewController() {
279 if (toolbar_actions_bar_
&& toolbar_actions_bar_
->in_overflow_mode()) {
280 return static_cast<ExtensionActionViewController
*>(
281 toolbar_actions_bar_
->GetMainControllerForAction(this));
287 bool ExtensionActionViewController::TriggerPopupWithUrl(
288 PopupShowAction show_action
,
289 const GURL
& popup_url
,
290 bool grant_tab_permissions
) {
291 if (!ExtensionIsValid())
294 bool already_showing
= is_showing_popup();
296 // Always hide the current popup, even if it's not owned by this extension.
297 // Only one popup should be visible at a time.
300 // If we were showing a popup already, then we treat the action to open the
301 // same one as a desire to close it (like clicking a menu button that was
306 scoped_ptr
<extensions::ExtensionViewHost
> host(
307 extensions::ExtensionViewHostFactory::CreatePopupHost(popup_url
,
312 popup_host_
= host
.get();
313 popup_host_observer_
.Add(popup_host_
);
314 if (toolbar_actions_bar_
)
315 toolbar_actions_bar_
->SetPopupOwner(this);
317 if (toolbar_actions_bar_
&&
318 !toolbar_actions_bar_
->IsActionVisibleOnMainBar(this) &&
319 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
320 platform_delegate_
->CloseOverflowMenu();
321 toolbar_actions_bar_
->PopOutAction(
323 base::Bind(&ExtensionActionViewController::ShowPopup
,
324 weak_factory_
.GetWeakPtr(),
325 base::Passed(host
.Pass()),
326 grant_tab_permissions
,
329 ShowPopup(host
.Pass(), grant_tab_permissions
, show_action
);
335 void ExtensionActionViewController::ShowPopup(
336 scoped_ptr
<extensions::ExtensionViewHost
> popup_host
,
337 bool grant_tab_permissions
,
338 PopupShowAction show_action
) {
339 // It's possible that the popup should be closed before it finishes opening
340 // (since it can open asynchronously). Check before proceeding.
343 platform_delegate_
->ShowPopup(
344 popup_host
.Pass(), grant_tab_permissions
, show_action
);
345 view_delegate_
->OnPopupShown(grant_tab_permissions
);
348 void ExtensionActionViewController::OnPopupClosed() {
349 popup_host_observer_
.Remove(popup_host_
);
350 popup_host_
= nullptr;
351 if (toolbar_actions_bar_
) {
352 toolbar_actions_bar_
->SetPopupOwner(nullptr);
353 if (toolbar_actions_bar_
->popped_out_action() == this &&
354 !view_delegate_
->IsMenuRunning())
355 toolbar_actions_bar_
->UndoPopOut();
357 view_delegate_
->OnPopupClosed();
360 scoped_ptr
<IconWithBadgeImageSource
>
361 ExtensionActionViewController::GetIconImageSource(
362 content::WebContents
* web_contents
,
363 const gfx::Size
& size
) {
364 int tab_id
= SessionTabHelper::IdForTab(web_contents
);
365 scoped_ptr
<IconWithBadgeImageSource
> image_source(
366 new IconWithBadgeImageSource(size
));
367 image_source
->SetIcon(icon_factory_
.GetIcon(tab_id
));
368 scoped_ptr
<IconWithBadgeImageSource::Badge
> badge
;
369 std::string badge_text
= extension_action_
->GetBadgeText(tab_id
);
370 if (!badge_text
.empty()) {
371 badge
.reset(new IconWithBadgeImageSource::Badge(
373 extension_action_
->GetBadgeTextColor(tab_id
),
374 extension_action_
->GetBadgeBackgroundColor(tab_id
)));
376 image_source
->SetBadge(badge
.Pass());
378 // Greyscaling disabled actions and having a special wants-to-run decoration
379 // are gated on the toolbar redesign.
380 if (extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
381 // If the extension doesn't want to run on the active web contents, we
382 // grayscale it to indicate that.
383 image_source
->set_grayscale(!IsEnabled(web_contents
));
384 // If the action *does* want to run on the active web contents and is also
385 // overflowed, we add a decoration so that the user can see which overflowed
386 // action wants to run (since they wouldn't be able to see the change from
387 // grayscale to color).
389 toolbar_actions_bar_
&& toolbar_actions_bar_
->in_overflow_mode();
390 image_source
->set_paint_decoration(WantsToRun(web_contents
) && is_overflow
);
393 return image_source
.Pass();