Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / extensions / extension_action_view_controller.cc
blob22decd3e8b3722b3f9954be79cad5646f71c9bc0
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,
38 Browser* browser,
39 ExtensionAction* extension_action,
40 ToolbarActionsBar* toolbar_actions_bar)
41 : extension_(extension),
42 browser_(browser),
43 extension_action_(extension_action),
44 toolbar_actions_bar_(toolbar_actions_bar),
45 popup_host_(nullptr),
46 view_delegate_(nullptr),
47 platform_delegate_(ExtensionActionPlatformDelegate::Create(this)),
48 icon_factory_(browser->profile(), extension, extension_action, this),
49 icon_observer_(nullptr),
50 extension_registry_(
51 extensions::ExtensionRegistry::Get(browser_->profile())),
52 popup_host_observer_(this),
53 weak_factory_(this) {
54 DCHECK(extension_action);
55 DCHECK(extension_action->action_type() == ActionInfo::TYPE_PAGE ||
56 extension_action->action_type() == ActionInfo::TYPE_BROWSER);
57 DCHECK(extension);
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));
71 if (delegate) {
72 view_delegate_ = delegate;
73 platform_delegate_->OnDelegateSet();
74 } else {
75 if (is_showing_popup())
76 HidePopup();
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())
86 return gfx::Image();
88 return gfx::Image(
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();
104 std::string title =
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())
117 return false;
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())
134 return false;
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.
147 if (popup_host_)
148 OnPopupClosed();
152 gfx::NativeView ExtensionActionViewController::GetPopupNativeView() {
153 return popup_host_ ? popup_host_->view()->GetNativeView() : nullptr;
156 ui::MenuModel* ExtensionActionViewController::GetContextMenu() {
157 if (!ExtensionIsValid() || !extension()->ShowConfigureContextMenus())
158 return nullptr;
160 extensions::ExtensionContextMenuModel::ButtonVisibility visibility =
161 extensions::ExtensionContextMenuModel::VISIBLE;
162 if (toolbar_actions_bar_) {
163 if (toolbar_actions_bar_->popped_out_action() == this)
164 visibility = extensions::ExtensionContextMenuModel::TRANSITIVELY_VISIBLE;
165 else if (!toolbar_actions_bar_->IsActionVisibleOnMainBar(this))
166 visibility = extensions::ExtensionContextMenuModel::OVERFLOWED;
167 // Else, VISIBLE is correct.
169 // Reconstruct the menu every time because the menu's contents are dynamic.
170 context_menu_model_.reset(new extensions::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())
189 return;
191 view_delegate_->UpdateState();
194 bool ExtensionActionViewController::ExecuteAction(PopupShowAction show_action,
195 bool grant_tab_permissions) {
196 if (!ExtensionIsValid())
197 return false;
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);
208 return false;
211 void ExtensionActionViewController::RegisterCommand() {
212 if (!ExtensionIsValid())
213 return;
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
228 // be ready.
229 if (view_delegate_)
230 view_delegate_->UpdateState();
231 if (icon_observer_)
232 icon_observer_->OnIconUpdated();
235 void ExtensionActionViewController::OnExtensionHostDestroyed(
236 const extensions::ExtensionHost* host) {
237 OnPopupClosed();
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();
247 } else {
248 DCHECK_EQ(ActionInfo::TYPE_PAGE, extension_action_->action_type());
249 // In the traditional toolbar, page actions only know how to close their own
250 // popups.
251 HidePopup();
255 bool ExtensionActionViewController::GetExtensionCommand(
256 extensions::Command* command) {
257 DCHECK(command);
258 if (!ExtensionIsValid())
259 return false;
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));
284 return this;
287 bool ExtensionActionViewController::TriggerPopupWithUrl(
288 PopupShowAction show_action,
289 const GURL& popup_url,
290 bool grant_tab_permissions) {
291 if (!ExtensionIsValid())
292 return false;
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.
298 HideActivePopup();
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
302 // already open).
303 if (already_showing)
304 return false;
306 scoped_ptr<extensions::ExtensionViewHost> host(
307 extensions::ExtensionViewHostFactory::CreatePopupHost(popup_url,
308 browser_));
309 if (!host)
310 return false;
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(
322 this,
323 base::Bind(&ExtensionActionViewController::ShowPopup,
324 weak_factory_.GetWeakPtr(),
325 base::Passed(host.Pass()),
326 grant_tab_permissions,
327 show_action));
328 } else {
329 ShowPopup(host.Pass(), grant_tab_permissions, show_action);
332 return true;
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.
341 if (!popup_host_)
342 return;
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(
372 badge_text,
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).
388 bool is_overflow =
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();