1 // Copyright (c) 2012 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/extensions/extension_disabled_ui.h"
10 #include "base/lazy_instance.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/metrics/histogram.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/app/chrome_command_ids.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/extensions/extension_install_prompt.h"
19 #include "chrome/browser/extensions/extension_install_ui.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
22 #include "chrome/browser/extensions/image_loader.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/global_error/global_error.h"
26 #include "chrome/browser/ui/global_error/global_error_service.h"
27 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
28 #include "chrome/browser/ui/tabs/tab_strip_model.h"
29 #include "chrome/common/extensions/extension_icon_set.h"
30 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
31 #include "content/public/browser/notification_details.h"
32 #include "content/public/browser/notification_observer.h"
33 #include "content/public/browser/notification_registrar.h"
34 #include "content/public/browser/notification_source.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/permissions/permission_message_provider.h"
37 #include "extensions/common/permissions/permission_set.h"
38 #include "grit/chromium_strings.h"
39 #include "grit/generated_resources.h"
40 #include "grit/theme_resources.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/gfx/image/image.h"
43 #include "ui/gfx/image/image_skia_operations.h"
44 #include "ui/gfx/size.h"
46 using extensions::Extension
;
50 static const int kIconSize
= extension_misc::EXTENSION_ICON_SMALL
;
52 static base::LazyInstance
<
53 std::bitset
<IDC_EXTENSION_DISABLED_LAST
-
54 IDC_EXTENSION_DISABLED_FIRST
+ 1> >
55 menu_command_ids
= LAZY_INSTANCE_INITIALIZER
;
57 // Get an available menu ID.
58 int GetMenuCommandID() {
60 for (id
= IDC_EXTENSION_DISABLED_FIRST
;
61 id
<= IDC_EXTENSION_DISABLED_LAST
; ++id
) {
62 if (!menu_command_ids
.Get()[id
- IDC_EXTENSION_DISABLED_FIRST
]) {
63 menu_command_ids
.Get().set(id
- IDC_EXTENSION_DISABLED_FIRST
);
67 // This should not happen.
68 DCHECK(id
<= IDC_EXTENSION_DISABLED_LAST
) <<
69 "No available menu command IDs for ExtensionDisabledGlobalError";
70 return IDC_EXTENSION_DISABLED_LAST
;
73 // Make a menu ID available when it is no longer used.
74 void ReleaseMenuCommandID(int id
) {
75 menu_command_ids
.Get().reset(id
- IDC_EXTENSION_DISABLED_FIRST
);
80 // ExtensionDisabledDialogDelegate --------------------------------------------
82 class ExtensionDisabledDialogDelegate
83 : public ExtensionInstallPrompt::Delegate
,
84 public base::RefCountedThreadSafe
<ExtensionDisabledDialogDelegate
> {
86 ExtensionDisabledDialogDelegate(ExtensionService
* service
,
87 scoped_ptr
<ExtensionInstallPrompt
> install_ui
,
88 const Extension
* extension
);
91 friend class base::RefCountedThreadSafe
<ExtensionDisabledDialogDelegate
>;
93 virtual ~ExtensionDisabledDialogDelegate();
95 // ExtensionInstallPrompt::Delegate:
96 virtual void InstallUIProceed() OVERRIDE
;
97 virtual void InstallUIAbort(bool user_initiated
) OVERRIDE
;
99 // The UI for showing the install dialog when enabling.
100 scoped_ptr
<ExtensionInstallPrompt
> install_ui_
;
102 ExtensionService
* service_
;
103 const Extension
* extension_
;
106 ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
107 ExtensionService
* service
,
108 scoped_ptr
<ExtensionInstallPrompt
> install_ui
,
109 const Extension
* extension
)
110 : install_ui_(install_ui
.Pass()),
112 extension_(extension
) {
113 AddRef(); // Balanced in Proceed or Abort.
114 install_ui_
->ConfirmReEnable(this, extension_
);
117 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
120 void ExtensionDisabledDialogDelegate::InstallUIProceed() {
121 service_
->GrantPermissionsAndEnableExtension(extension_
);
125 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated
) {
126 std::string histogram_name
= user_initiated
?
127 "Extensions.Permissions_ReEnableCancel" :
128 "Extensions.Permissions_ReEnableAbort";
129 ExtensionService::RecordPermissionMessagesHistogram(
130 extension_
, histogram_name
.c_str());
132 // Do nothing. The extension will remain disabled.
136 // ExtensionDisabledGlobalError -----------------------------------------------
138 class ExtensionDisabledGlobalError
: public GlobalErrorWithStandardBubble
,
139 public content::NotificationObserver
,
140 public ExtensionUninstallDialog::Delegate
{
142 ExtensionDisabledGlobalError(ExtensionService
* service
,
143 const Extension
* extension
,
144 const gfx::Image
& icon
);
145 virtual ~ExtensionDisabledGlobalError();
147 // GlobalError implementation.
148 virtual Severity
GetSeverity() OVERRIDE
;
149 virtual bool HasMenuItem() OVERRIDE
;
150 virtual int MenuItemCommandID() OVERRIDE
;
151 virtual base::string16
MenuItemLabel() OVERRIDE
;
152 virtual void ExecuteMenuItem(Browser
* browser
) OVERRIDE
;
153 virtual gfx::Image
GetBubbleViewIcon() OVERRIDE
;
154 virtual base::string16
GetBubbleViewTitle() OVERRIDE
;
155 virtual std::vector
<base::string16
> GetBubbleViewMessages() OVERRIDE
;
156 virtual base::string16
GetBubbleViewAcceptButtonLabel() OVERRIDE
;
157 virtual base::string16
GetBubbleViewCancelButtonLabel() OVERRIDE
;
158 virtual void OnBubbleViewDidClose(Browser
* browser
) OVERRIDE
;
159 virtual void BubbleViewAcceptButtonPressed(Browser
* browser
) OVERRIDE
;
160 virtual void BubbleViewCancelButtonPressed(Browser
* browser
) OVERRIDE
;
162 // ExtensionUninstallDialog::Delegate implementation.
163 virtual void ExtensionUninstallAccepted() OVERRIDE
;
164 virtual void ExtensionUninstallCanceled() OVERRIDE
;
166 // content::NotificationObserver implementation.
167 virtual void Observe(int type
,
168 const content::NotificationSource
& source
,
169 const content::NotificationDetails
& details
) OVERRIDE
;
172 ExtensionService
* service_
;
173 const Extension
* extension_
;
176 // How the user responded to the error; used for metrics.
181 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
183 UserResponse user_response_
;
185 scoped_ptr
<ExtensionUninstallDialog
> uninstall_dialog_
;
187 // Menu command ID assigned for this extension's error.
188 int menu_command_id_
;
190 content::NotificationRegistrar registrar_
;
193 // TODO(yoz): create error at startup for disabled extensions.
194 ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
195 ExtensionService
* service
,
196 const Extension
* extension
,
197 const gfx::Image
& icon
)
199 extension_(extension
),
201 user_response_(IGNORED
),
202 menu_command_id_(GetMenuCommandID()) {
203 if (icon_
.IsEmpty()) {
205 gfx::ImageSkiaOperations::CreateResizedImage(
206 extension_
->is_app() ?
207 extensions::IconsInfo::GetDefaultAppIcon() :
208 extensions::IconsInfo::GetDefaultExtensionIcon(),
209 skia::ImageOperations::RESIZE_BEST
,
210 gfx::Size(kIconSize
, kIconSize
)));
212 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED
,
213 content::Source
<Profile
>(service
->profile()));
214 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED
,
215 content::Source
<Profile
>(service
->profile()));
218 ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
219 ReleaseMenuCommandID(menu_command_id_
);
220 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
222 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
);
225 GlobalError::Severity
ExtensionDisabledGlobalError::GetSeverity() {
229 bool ExtensionDisabledGlobalError::HasMenuItem() {
233 int ExtensionDisabledGlobalError::MenuItemCommandID() {
234 return menu_command_id_
;
237 base::string16
ExtensionDisabledGlobalError::MenuItemLabel() {
238 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE
,
239 base::UTF8ToUTF16(extension_
->name()));
242 void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser
* browser
) {
243 ShowBubbleView(browser
);
246 gfx::Image
ExtensionDisabledGlobalError::GetBubbleViewIcon() {
250 base::string16
ExtensionDisabledGlobalError::GetBubbleViewTitle() {
251 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE
,
252 base::UTF8ToUTF16(extension_
->name()));
255 std::vector
<base::string16
>
256 ExtensionDisabledGlobalError::GetBubbleViewMessages() {
257 std::vector
<base::string16
> messages
;
258 messages
.push_back(l10n_util::GetStringFUTF16(
259 extension_
->is_app() ?
260 IDS_APP_DISABLED_ERROR_LABEL
: IDS_EXTENSION_DISABLED_ERROR_LABEL
,
261 base::UTF8ToUTF16(extension_
->name())));
262 messages
.push_back(l10n_util::GetStringUTF16(
263 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO
));
264 std::vector
<base::string16
> permission_warnings
=
265 extensions::PermissionMessageProvider::Get()->GetWarningMessages(
266 extension_
->GetActivePermissions(), extension_
->GetType());
267 for (size_t i
= 0; i
< permission_warnings
.size(); ++i
) {
268 messages
.push_back(l10n_util::GetStringFUTF16(
269 IDS_EXTENSION_PERMISSION_LINE
, permission_warnings
[i
]));
274 base::string16
ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
275 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON
);
278 base::string16
ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
279 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL
);
282 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser
* browser
) {
285 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
287 // Delay extension reenabling so this bubble closes properly.
288 base::MessageLoop::current()->PostTask(FROM_HERE
,
289 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension
,
290 service_
->AsWeakPtr(), extension_
));
293 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
295 #if !defined(OS_ANDROID)
296 uninstall_dialog_
.reset(
297 ExtensionUninstallDialog::Create(service_
->profile(), browser
, this));
298 // Delay showing the uninstall dialog, so that this function returns
299 // immediately, to close the bubble properly. See crbug.com/121544.
300 base::MessageLoop::current()->PostTask(FROM_HERE
,
301 base::Bind(&ExtensionUninstallDialog::ConfirmUninstall
,
302 uninstall_dialog_
->AsWeakPtr(), extension_
));
303 #endif // !defined(OS_ANDROID)
306 void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() {
307 service_
->UninstallExtension(extension_
->id(), false, NULL
);
310 void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() {
311 // Nothing happens, and the error is still there.
314 void ExtensionDisabledGlobalError::Observe(
316 const content::NotificationSource
& source
,
317 const content::NotificationDetails
& details
) {
318 // The error is invalidated if the extension has been loaded or removed.
319 DCHECK(type
== chrome::NOTIFICATION_EXTENSION_LOADED
||
320 type
== chrome::NOTIFICATION_EXTENSION_REMOVED
);
321 const Extension
* extension
= content::Details
<const Extension
>(details
).ptr();
322 if (extension
!= extension_
)
324 GlobalErrorServiceFactory::GetForProfile(service_
->profile())->
325 RemoveGlobalError(this);
327 if (type
== chrome::NOTIFICATION_EXTENSION_LOADED
)
328 user_response_
= REENABLE
;
329 else if (type
== chrome::NOTIFICATION_EXTENSION_REMOVED
)
330 user_response_
= UNINSTALL
;
334 // Globals --------------------------------------------------------------------
336 namespace extensions
{
338 void AddExtensionDisabledErrorWithIcon(base::WeakPtr
<ExtensionService
> service
,
339 const std::string
& extension_id
,
340 const gfx::Image
& icon
) {
343 const Extension
* extension
= service
->GetInstalledExtension(extension_id
);
345 GlobalErrorServiceFactory::GetForProfile(service
->profile())
347 new ExtensionDisabledGlobalError(service
.get(), extension
, icon
));
351 void AddExtensionDisabledError(ExtensionService
* service
,
352 const Extension
* extension
) {
353 // Do not display notifications for ephemeral apps that have been disabled.
354 // Instead, a prompt will be shown the next time the app is launched.
355 if (extension
->is_ephemeral())
358 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
359 extension
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
360 gfx::Size
size(kIconSize
, kIconSize
);
361 ImageLoader::Get(service
->profile())->LoadImageAsync(
362 extension
, image
, size
,
363 base::Bind(&AddExtensionDisabledErrorWithIcon
,
364 service
->AsWeakPtr(), extension
->id()));
367 void ShowExtensionDisabledDialog(ExtensionService
* service
,
368 content::WebContents
* web_contents
,
369 const Extension
* extension
) {
370 scoped_ptr
<ExtensionInstallPrompt
> install_ui(
371 new ExtensionInstallPrompt(web_contents
));
372 // This object manages its own lifetime.
373 new ExtensionDisabledDialogDelegate(service
, install_ui
.Pass(), extension
);
376 } // namespace extensions