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/bind.h"
11 #include "base/lazy_instance.h"
12 #include "base/memory/ref_counted.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/metrics/histogram.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/app/chrome_command_ids.h"
19 #include "chrome/browser/extensions/extension_install_prompt.h"
20 #include "chrome/browser/extensions/extension_install_ui.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
23 #include "chrome/browser/extensions/extension_util.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/browser.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/global_error/global_error.h"
28 #include "chrome/browser/ui/global_error/global_error_service.h"
29 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
30 #include "chrome/browser/ui/tabs/tab_strip_model.h"
31 #include "chrome/grit/chromium_strings.h"
32 #include "chrome/grit/generated_resources.h"
33 #include "content/public/browser/notification_details.h"
34 #include "content/public/browser/notification_observer.h"
35 #include "content/public/browser/notification_registrar.h"
36 #include "content/public/browser/notification_source.h"
37 #include "extensions/browser/extension_util.h"
38 #include "extensions/browser/image_loader.h"
39 #include "extensions/browser/notification_types.h"
40 #include "extensions/browser/uninstall_reason.h"
41 #include "extensions/common/constants.h"
42 #include "extensions/common/extension.h"
43 #include "extensions/common/extension_icon_set.h"
44 #include "extensions/common/manifest_handlers/icons_handler.h"
45 #include "extensions/common/permissions/permission_message_provider.h"
46 #include "extensions/common/permissions/permission_set.h"
47 #include "extensions/common/permissions/permissions_data.h"
48 #include "ui/base/l10n/l10n_util.h"
49 #include "ui/gfx/image/image.h"
50 #include "ui/gfx/image/image_skia_operations.h"
51 #include "ui/gfx/size.h"
53 using extensions::Extension
;
57 static const int kIconSize
= extension_misc::EXTENSION_ICON_SMALL
;
59 static base::LazyInstance
<
60 std::bitset
<IDC_EXTENSION_DISABLED_LAST
-
61 IDC_EXTENSION_DISABLED_FIRST
+ 1> >
62 menu_command_ids
= LAZY_INSTANCE_INITIALIZER
;
64 // Get an available menu ID.
65 int GetMenuCommandID() {
67 for (id
= IDC_EXTENSION_DISABLED_FIRST
;
68 id
<= IDC_EXTENSION_DISABLED_LAST
; ++id
) {
69 if (!menu_command_ids
.Get()[id
- IDC_EXTENSION_DISABLED_FIRST
]) {
70 menu_command_ids
.Get().set(id
- IDC_EXTENSION_DISABLED_FIRST
);
74 // This should not happen.
75 DCHECK(id
<= IDC_EXTENSION_DISABLED_LAST
) <<
76 "No available menu command IDs for ExtensionDisabledGlobalError";
77 return IDC_EXTENSION_DISABLED_LAST
;
80 // Make a menu ID available when it is no longer used.
81 void ReleaseMenuCommandID(int id
) {
82 menu_command_ids
.Get().reset(id
- IDC_EXTENSION_DISABLED_FIRST
);
87 // ExtensionDisabledDialogDelegate --------------------------------------------
89 class ExtensionDisabledDialogDelegate
90 : public ExtensionInstallPrompt::Delegate
,
91 public base::RefCountedThreadSafe
<ExtensionDisabledDialogDelegate
> {
93 ExtensionDisabledDialogDelegate(ExtensionService
* service
,
94 scoped_ptr
<ExtensionInstallPrompt
> install_ui
,
95 const Extension
* extension
);
98 friend class base::RefCountedThreadSafe
<ExtensionDisabledDialogDelegate
>;
100 virtual ~ExtensionDisabledDialogDelegate();
102 // ExtensionInstallPrompt::Delegate:
103 virtual void InstallUIProceed() override
;
104 virtual void InstallUIAbort(bool user_initiated
) override
;
106 // The UI for showing the install dialog when enabling.
107 scoped_ptr
<ExtensionInstallPrompt
> install_ui_
;
109 ExtensionService
* service_
;
110 const Extension
* extension_
;
113 ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
114 ExtensionService
* service
,
115 scoped_ptr
<ExtensionInstallPrompt
> install_ui
,
116 const Extension
* extension
)
117 : install_ui_(install_ui
.Pass()),
119 extension_(extension
) {
120 AddRef(); // Balanced in Proceed or Abort.
121 install_ui_
->ConfirmReEnable(this, extension_
);
124 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
127 void ExtensionDisabledDialogDelegate::InstallUIProceed() {
128 service_
->GrantPermissionsAndEnableExtension(extension_
);
132 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated
) {
133 std::string histogram_name
= user_initiated
134 ? "Extensions.Permissions_ReEnableCancel2"
135 : "Extensions.Permissions_ReEnableAbort2";
136 ExtensionService::RecordPermissionMessagesHistogram(
137 extension_
, histogram_name
.c_str());
139 // Do nothing. The extension will remain disabled.
143 // ExtensionDisabledGlobalError -----------------------------------------------
145 class ExtensionDisabledGlobalError
146 : public GlobalErrorWithStandardBubble
,
147 public content::NotificationObserver
,
148 public extensions::ExtensionUninstallDialog::Delegate
{
150 ExtensionDisabledGlobalError(ExtensionService
* service
,
151 const Extension
* extension
,
152 bool is_remote_install
,
153 const gfx::Image
& icon
);
154 virtual ~ExtensionDisabledGlobalError();
156 // GlobalError implementation.
157 virtual Severity
GetSeverity() override
;
158 virtual bool HasMenuItem() override
;
159 virtual int MenuItemCommandID() override
;
160 virtual base::string16
MenuItemLabel() override
;
161 virtual void ExecuteMenuItem(Browser
* browser
) override
;
162 virtual gfx::Image
GetBubbleViewIcon() override
;
163 virtual base::string16
GetBubbleViewTitle() override
;
164 virtual std::vector
<base::string16
> GetBubbleViewMessages() override
;
165 virtual base::string16
GetBubbleViewAcceptButtonLabel() override
;
166 virtual base::string16
GetBubbleViewCancelButtonLabel() override
;
167 virtual void OnBubbleViewDidClose(Browser
* browser
) override
;
168 virtual void BubbleViewAcceptButtonPressed(Browser
* browser
) override
;
169 virtual void BubbleViewCancelButtonPressed(Browser
* browser
) override
;
170 virtual bool ShouldCloseOnDeactivate() const override
;
172 // ExtensionUninstallDialog::Delegate implementation.
173 virtual void ExtensionUninstallAccepted() override
;
174 virtual void ExtensionUninstallCanceled() override
;
176 // content::NotificationObserver implementation.
177 virtual void Observe(int type
,
178 const content::NotificationSource
& source
,
179 const content::NotificationDetails
& details
) override
;
182 ExtensionService
* service_
;
183 const Extension
* extension_
;
184 bool is_remote_install_
;
187 // How the user responded to the error; used for metrics.
192 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
194 UserResponse user_response_
;
196 scoped_ptr
<extensions::ExtensionUninstallDialog
> uninstall_dialog_
;
198 // Menu command ID assigned for this extension's error.
199 int menu_command_id_
;
201 content::NotificationRegistrar registrar_
;
204 // TODO(yoz): create error at startup for disabled extensions.
205 ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
206 ExtensionService
* service
,
207 const Extension
* extension
,
208 bool is_remote_install
,
209 const gfx::Image
& icon
)
211 extension_(extension
),
212 is_remote_install_(is_remote_install
),
214 user_response_(IGNORED
),
215 menu_command_id_(GetMenuCommandID()) {
216 if (icon_
.IsEmpty()) {
218 gfx::ImageSkiaOperations::CreateResizedImage(
219 extension_
->is_app() ?
220 extensions::util::GetDefaultAppIcon() :
221 extensions::util::GetDefaultExtensionIcon(),
222 skia::ImageOperations::RESIZE_BEST
,
223 gfx::Size(kIconSize
, kIconSize
)));
226 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
227 content::Source
<Profile
>(service
->profile()));
229 extensions::NOTIFICATION_EXTENSION_REMOVED
,
230 content::Source
<Profile
>(service
->profile()));
233 ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
234 ReleaseMenuCommandID(menu_command_id_
);
235 if (is_remote_install_
) {
236 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponseRemoteInstall",
238 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
);
240 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
242 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
);
246 GlobalError::Severity
ExtensionDisabledGlobalError::GetSeverity() {
250 bool ExtensionDisabledGlobalError::HasMenuItem() {
254 int ExtensionDisabledGlobalError::MenuItemCommandID() {
255 return menu_command_id_
;
258 base::string16
ExtensionDisabledGlobalError::MenuItemLabel() {
259 std::string extension_name
= extension_
->name();
260 // Ampersands need to be escaped to avoid being treated like
261 // mnemonics in the menu.
262 base::ReplaceChars(extension_name
, "&", "&&", &extension_name
);
264 if (is_remote_install_
) {
265 return l10n_util::GetStringFUTF16(
266 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE
,
267 base::UTF8ToUTF16(extension_name
));
269 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE
,
270 base::UTF8ToUTF16(extension_name
));
274 void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser
* browser
) {
275 ShowBubbleView(browser
);
278 gfx::Image
ExtensionDisabledGlobalError::GetBubbleViewIcon() {
282 base::string16
ExtensionDisabledGlobalError::GetBubbleViewTitle() {
283 if (is_remote_install_
) {
284 return l10n_util::GetStringFUTF16(
285 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE
,
286 base::UTF8ToUTF16(extension_
->name()));
288 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE
,
289 base::UTF8ToUTF16(extension_
->name()));
293 std::vector
<base::string16
>
294 ExtensionDisabledGlobalError::GetBubbleViewMessages() {
295 std::vector
<base::string16
> messages
;
296 std::vector
<base::string16
> permission_warnings
=
297 extensions::PermissionMessageProvider::Get()->GetWarningMessages(
298 extension_
->permissions_data()->active_permissions().get(),
299 extension_
->GetType());
300 if (is_remote_install_
) {
301 messages
.push_back(l10n_util::GetStringFUTF16(
303 ? IDS_APP_DISABLED_REMOTE_INSTALL_ERROR_LABEL
304 : IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_LABEL
,
305 base::UTF8ToUTF16(extension_
->name())));
306 if (!permission_warnings
.empty())
308 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO
));
310 messages
.push_back(l10n_util::GetStringFUTF16(
311 extension_
->is_app() ? IDS_APP_DISABLED_ERROR_LABEL
312 : IDS_EXTENSION_DISABLED_ERROR_LABEL
,
313 base::UTF8ToUTF16(extension_
->name())));
314 messages
.push_back(l10n_util::GetStringUTF16(
315 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO
));
317 for (size_t i
= 0; i
< permission_warnings
.size(); ++i
) {
318 messages
.push_back(l10n_util::GetStringFUTF16(
319 IDS_EXTENSION_PERMISSION_LINE
, permission_warnings
[i
]));
324 base::string16
ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
325 if (is_remote_install_
) {
326 return l10n_util::GetStringUTF16(
327 IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON
);
329 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON
);
333 base::string16
ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
334 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL
);
337 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser
* browser
) {
340 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
342 // Delay extension reenabling so this bubble closes properly.
343 base::MessageLoop::current()->PostTask(FROM_HERE
,
344 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension
,
345 service_
->AsWeakPtr(), extension_
));
348 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
350 uninstall_dialog_
.reset(extensions::ExtensionUninstallDialog::Create(
351 service_
->profile(), browser
->window()->GetNativeWindow(), this));
352 // Delay showing the uninstall dialog, so that this function returns
353 // immediately, to close the bubble properly. See crbug.com/121544.
354 base::MessageLoop::current()->PostTask(
356 base::Bind(&extensions::ExtensionUninstallDialog::ConfirmUninstall
,
357 uninstall_dialog_
->AsWeakPtr(),
361 bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
362 // Since this indicates that an extension was disabled, we should definitely
363 // have the user acknowledge it, rather than having the bubble disappear when
364 // a new window pops up.
368 void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() {
369 service_
->UninstallExtension(extension_
->id(),
370 extensions::UNINSTALL_REASON_EXTENSION_DISABLED
,
371 base::Bind(&base::DoNothing
),
375 void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() {
376 // Nothing happens, and the error is still there.
379 void ExtensionDisabledGlobalError::Observe(
381 const content::NotificationSource
& source
,
382 const content::NotificationDetails
& details
) {
383 // The error is invalidated if the extension has been loaded or removed.
384 DCHECK(type
== extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
||
385 type
== extensions::NOTIFICATION_EXTENSION_REMOVED
);
386 const Extension
* extension
= content::Details
<const Extension
>(details
).ptr();
387 if (extension
!= extension_
)
389 GlobalErrorServiceFactory::GetForProfile(service_
->profile())->
390 RemoveGlobalError(this);
392 if (type
== extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
)
393 user_response_
= REENABLE
;
394 else if (type
== extensions::NOTIFICATION_EXTENSION_REMOVED
)
395 user_response_
= UNINSTALL
;
399 // Globals --------------------------------------------------------------------
401 namespace extensions
{
403 void AddExtensionDisabledErrorWithIcon(base::WeakPtr
<ExtensionService
> service
,
404 const std::string
& extension_id
,
405 bool is_remote_install
,
406 const gfx::Image
& icon
) {
409 const Extension
* extension
= service
->GetInstalledExtension(extension_id
);
411 GlobalErrorServiceFactory::GetForProfile(service
->profile())
412 ->AddGlobalError(new ExtensionDisabledGlobalError(
413 service
.get(), extension
, is_remote_install
, icon
));
417 void AddExtensionDisabledError(ExtensionService
* service
,
418 const Extension
* extension
,
419 bool is_remote_install
) {
420 // Do not display notifications for ephemeral apps that have been disabled.
421 // Instead, a prompt will be shown the next time the app is launched.
422 if (util::IsEphemeralApp(extension
->id(), service
->profile()))
425 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
426 extension
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
427 gfx::Size
size(kIconSize
, kIconSize
);
428 ImageLoader::Get(service
->profile())
429 ->LoadImageAsync(extension
,
432 base::Bind(&AddExtensionDisabledErrorWithIcon
,
433 service
->AsWeakPtr(),
438 void ShowExtensionDisabledDialog(ExtensionService
* service
,
439 content::WebContents
* web_contents
,
440 const Extension
* extension
) {
441 scoped_ptr
<ExtensionInstallPrompt
> install_ui(
442 new ExtensionInstallPrompt(web_contents
));
443 // This object manages its own lifetime.
444 new ExtensionDisabledDialogDelegate(service
, install_ui
.Pass(), extension
);
447 } // namespace extensions