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/location.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/metrics/histogram.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "chrome/app/chrome_command_ids.h"
21 #include "chrome/browser/extensions/extension_install_prompt.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
24 #include "chrome/browser/extensions/extension_util.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_window.h"
28 #include "chrome/browser/ui/global_error/global_error.h"
29 #include "chrome/browser/ui/global_error/global_error_service.h"
30 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/grit/chromium_strings.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/notification_details.h"
35 #include "content/public/browser/notification_observer.h"
36 #include "content/public/browser/notification_registrar.h"
37 #include "content/public/browser/notification_source.h"
38 #include "extensions/browser/extension_util.h"
39 #include "extensions/browser/image_loader.h"
40 #include "extensions/browser/notification_types.h"
41 #include "extensions/browser/uninstall_reason.h"
42 #include "extensions/common/constants.h"
43 #include "extensions/common/extension.h"
44 #include "extensions/common/extension_icon_set.h"
45 #include "extensions/common/manifest_handlers/icons_handler.h"
46 #include "extensions/common/permissions/coalesced_permission_message.h"
47 #include "extensions/common/permissions/permissions_data.h"
48 #include "ui/base/l10n/l10n_util.h"
49 #include "ui/gfx/geometry/size.h"
50 #include "ui/gfx/image/image.h"
51 #include "ui/gfx/image/image_skia_operations.h"
53 using extensions::CoalescedPermissionMessage
;
54 using extensions::CoalescedPermissionMessages
;
55 using extensions::Extension
;
59 static const int kIconSize
= extension_misc::EXTENSION_ICON_SMALL
;
61 static base::LazyInstance
<
62 std::bitset
<IDC_EXTENSION_DISABLED_LAST
-
63 IDC_EXTENSION_DISABLED_FIRST
+ 1> >
64 menu_command_ids
= LAZY_INSTANCE_INITIALIZER
;
66 // Get an available menu ID.
67 int GetMenuCommandID() {
69 for (id
= IDC_EXTENSION_DISABLED_FIRST
;
70 id
<= IDC_EXTENSION_DISABLED_LAST
; ++id
) {
71 if (!menu_command_ids
.Get()[id
- IDC_EXTENSION_DISABLED_FIRST
]) {
72 menu_command_ids
.Get().set(id
- IDC_EXTENSION_DISABLED_FIRST
);
76 // This should not happen.
77 DCHECK(id
<= IDC_EXTENSION_DISABLED_LAST
) <<
78 "No available menu command IDs for ExtensionDisabledGlobalError";
79 return IDC_EXTENSION_DISABLED_LAST
;
82 // Make a menu ID available when it is no longer used.
83 void ReleaseMenuCommandID(int id
) {
84 menu_command_ids
.Get().reset(id
- IDC_EXTENSION_DISABLED_FIRST
);
89 // ExtensionDisabledDialogDelegate --------------------------------------------
91 class ExtensionDisabledDialogDelegate
92 : public ExtensionInstallPrompt::Delegate
,
93 public base::RefCountedThreadSafe
<ExtensionDisabledDialogDelegate
> {
95 ExtensionDisabledDialogDelegate(ExtensionService
* service
,
96 scoped_ptr
<ExtensionInstallPrompt
> install_ui
,
97 const Extension
* extension
);
100 friend class base::RefCountedThreadSafe
<ExtensionDisabledDialogDelegate
>;
102 ~ExtensionDisabledDialogDelegate() override
;
104 // ExtensionInstallPrompt::Delegate:
105 void InstallUIProceed() override
;
106 void InstallUIAbort(bool user_initiated
) override
;
108 // The UI for showing the install dialog when enabling.
109 scoped_ptr
<ExtensionInstallPrompt
> install_ui_
;
111 ExtensionService
* service_
;
112 const Extension
* extension_
;
115 ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
116 ExtensionService
* service
,
117 scoped_ptr
<ExtensionInstallPrompt
> install_ui
,
118 const Extension
* extension
)
119 : install_ui_(install_ui
.Pass()),
121 extension_(extension
) {
122 AddRef(); // Balanced in Proceed or Abort.
123 install_ui_
->ConfirmReEnable(this, extension_
);
126 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
129 void ExtensionDisabledDialogDelegate::InstallUIProceed() {
130 service_
->GrantPermissionsAndEnableExtension(extension_
);
134 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated
) {
135 std::string histogram_name
= user_initiated
? "ReEnableCancel"
137 ExtensionService::RecordPermissionMessagesHistogram(
138 extension_
, histogram_name
.c_str());
140 // Do nothing. The extension will remain disabled.
144 // ExtensionDisabledGlobalError -----------------------------------------------
146 class ExtensionDisabledGlobalError
147 : public GlobalErrorWithStandardBubble
,
148 public content::NotificationObserver
,
149 public extensions::ExtensionUninstallDialog::Delegate
{
151 ExtensionDisabledGlobalError(ExtensionService
* service
,
152 const Extension
* extension
,
153 bool is_remote_install
,
154 const gfx::Image
& icon
);
155 ~ExtensionDisabledGlobalError() override
;
157 // GlobalError implementation.
158 Severity
GetSeverity() override
;
159 bool HasMenuItem() override
;
160 int MenuItemCommandID() override
;
161 base::string16
MenuItemLabel() override
;
162 void ExecuteMenuItem(Browser
* browser
) override
;
163 gfx::Image
GetBubbleViewIcon() override
;
164 base::string16
GetBubbleViewTitle() override
;
165 std::vector
<base::string16
> GetBubbleViewMessages() override
;
166 base::string16
GetBubbleViewAcceptButtonLabel() override
;
167 base::string16
GetBubbleViewCancelButtonLabel() override
;
168 void OnBubbleViewDidClose(Browser
* browser
) override
;
169 void BubbleViewAcceptButtonPressed(Browser
* browser
) override
;
170 void BubbleViewCancelButtonPressed(Browser
* browser
) override
;
171 bool ShouldCloseOnDeactivate() const override
;
173 // ExtensionUninstallDialog::Delegate implementation.
174 void OnExtensionUninstallDialogClosed(bool did_start_uninstall
,
175 const base::string16
& error
) override
;
177 // content::NotificationObserver implementation.
178 void Observe(int type
,
179 const content::NotificationSource
& source
,
180 const content::NotificationDetails
& details
) override
;
183 ExtensionService
* service_
;
184 const Extension
* extension_
;
185 bool is_remote_install_
;
188 // How the user responded to the error; used for metrics.
193 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
195 UserResponse user_response_
;
197 scoped_ptr
<extensions::ExtensionUninstallDialog
> uninstall_dialog_
;
199 // Menu command ID assigned for this extension's error.
200 int menu_command_id_
;
202 content::NotificationRegistrar registrar_
;
205 // TODO(yoz): create error at startup for disabled extensions.
206 ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
207 ExtensionService
* service
,
208 const Extension
* extension
,
209 bool is_remote_install
,
210 const gfx::Image
& icon
)
212 extension_(extension
),
213 is_remote_install_(is_remote_install
),
215 user_response_(IGNORED
),
216 menu_command_id_(GetMenuCommandID()) {
217 if (icon_
.IsEmpty()) {
219 gfx::ImageSkiaOperations::CreateResizedImage(
220 extension_
->is_app() ?
221 extensions::util::GetDefaultAppIcon() :
222 extensions::util::GetDefaultExtensionIcon(),
223 skia::ImageOperations::RESIZE_BEST
,
224 gfx::Size(kIconSize
, kIconSize
)));
227 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
228 content::Source
<Profile
>(service
->profile()));
230 extensions::NOTIFICATION_EXTENSION_REMOVED
,
231 content::Source
<Profile
>(service
->profile()));
234 ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
235 ReleaseMenuCommandID(menu_command_id_
);
236 if (is_remote_install_
) {
237 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponseRemoteInstall",
239 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
);
241 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
243 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
);
247 GlobalError::Severity
ExtensionDisabledGlobalError::GetSeverity() {
251 bool ExtensionDisabledGlobalError::HasMenuItem() {
255 int ExtensionDisabledGlobalError::MenuItemCommandID() {
256 return menu_command_id_
;
259 base::string16
ExtensionDisabledGlobalError::MenuItemLabel() {
260 std::string extension_name
= extension_
->name();
261 // Ampersands need to be escaped to avoid being treated like
262 // mnemonics in the menu.
263 base::ReplaceChars(extension_name
, "&", "&&", &extension_name
);
265 if (is_remote_install_
) {
266 return l10n_util::GetStringFUTF16(
267 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE
,
268 base::UTF8ToUTF16(extension_name
));
270 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE
,
271 base::UTF8ToUTF16(extension_name
));
275 void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser
* browser
) {
276 ShowBubbleView(browser
);
279 gfx::Image
ExtensionDisabledGlobalError::GetBubbleViewIcon() {
283 base::string16
ExtensionDisabledGlobalError::GetBubbleViewTitle() {
284 if (is_remote_install_
) {
285 return l10n_util::GetStringFUTF16(
286 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE
,
287 base::UTF8ToUTF16(extension_
->name()));
289 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE
,
290 base::UTF8ToUTF16(extension_
->name()));
294 std::vector
<base::string16
>
295 ExtensionDisabledGlobalError::GetBubbleViewMessages() {
296 std::vector
<base::string16
> messages
;
297 CoalescedPermissionMessages permission_warnings
=
298 extension_
->permissions_data()->GetPermissionMessages();
299 if (is_remote_install_
) {
300 messages
.push_back(l10n_util::GetStringFUTF16(
302 ? IDS_APP_DISABLED_REMOTE_INSTALL_ERROR_LABEL
303 : IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_LABEL
,
304 base::UTF8ToUTF16(extension_
->name())));
305 if (!permission_warnings
.empty())
307 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO
));
309 // TODO(treib): If NeedCustodianApprovalForPermissionIncrease, add an extra
310 // message for supervised users. crbug.com/461261
311 messages
.push_back(l10n_util::GetStringFUTF16(
312 extension_
->is_app() ? IDS_APP_DISABLED_ERROR_LABEL
313 : IDS_EXTENSION_DISABLED_ERROR_LABEL
,
314 base::UTF8ToUTF16(extension_
->name())));
315 messages
.push_back(l10n_util::GetStringUTF16(
316 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO
));
318 for (const CoalescedPermissionMessage
& msg
: permission_warnings
) {
319 messages
.push_back(l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE
,
325 base::string16
ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
326 if (extensions::util::IsExtensionSupervised(extension_
,
327 service_
->profile()) &&
328 extensions::util::NeedCustodianApprovalForPermissionIncrease()) {
329 // TODO(treib): Probably use a new string here once we get UX design.
330 // For now, just use "OK". crbug.com/461261
331 return l10n_util::GetStringUTF16(IDS_OK
);
333 if (is_remote_install_
) {
334 return l10n_util::GetStringUTF16(
336 ? IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_APP
337 : IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_EXTENSION
);
339 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON
);
342 base::string16
ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
343 if (extensions::util::IsExtensionSupervised(extension_
,
344 service_
->profile())) {
345 if (extensions::util::NeedCustodianApprovalForPermissionIncrease()) {
346 // If the supervised user can't approve the update, then there is no
348 return base::string16();
350 // Supervised users can not remove extensions, so use "cancel" here
351 // instead of "uninstall".
352 return l10n_util::GetStringUTF16(IDS_CANCEL
);
355 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL
);
358 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser
* browser
) {
361 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
363 if (extensions::util::IsExtensionSupervised(extension_
,
364 service_
->profile()) &&
365 extensions::util::NeedCustodianApprovalForPermissionIncrease()) {
368 // Delay extension reenabling so this bubble closes properly.
369 base::ThreadTaskRunnerHandle::Get()->PostTask(
371 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension
,
372 service_
->AsWeakPtr(), extension_
));
375 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
377 if (extensions::util::IsExtensionSupervised(extension_
,
378 service_
->profile())) {
379 // For custodian-installed extensions, this button should only exist if the
380 // supervised user can approve the update. Otherwise there is only an "OK"
382 DCHECK(!extensions::util::NeedCustodianApprovalForPermissionIncrease());
383 // Supervised users may never remove custodian-installed extensions.
387 uninstall_dialog_
.reset(extensions::ExtensionUninstallDialog::Create(
388 service_
->profile(), browser
->window()->GetNativeWindow(), this));
389 // Delay showing the uninstall dialog, so that this function returns
390 // immediately, to close the bubble properly. See crbug.com/121544.
391 base::ThreadTaskRunnerHandle::Get()->PostTask(
393 base::Bind(&extensions::ExtensionUninstallDialog::ConfirmUninstall
,
394 uninstall_dialog_
->AsWeakPtr(), extension_
,
395 extensions::UNINSTALL_REASON_EXTENSION_DISABLED
,
396 extensions::UNINSTALL_SOURCE_PERMISSIONS_INCREASE
));
399 bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
400 // Since this indicates that an extension was disabled, we should definitely
401 // have the user acknowledge it, rather than having the bubble disappear when
402 // a new window pops up.
406 void ExtensionDisabledGlobalError::OnExtensionUninstallDialogClosed(
407 bool did_start_uninstall
,
408 const base::string16
& error
) {
409 // No need to do anything.
412 void ExtensionDisabledGlobalError::Observe(
414 const content::NotificationSource
& source
,
415 const content::NotificationDetails
& details
) {
416 // The error is invalidated if the extension has been loaded or removed.
417 DCHECK(type
== extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
||
418 type
== extensions::NOTIFICATION_EXTENSION_REMOVED
);
419 const Extension
* extension
= content::Details
<const Extension
>(details
).ptr();
420 if (extension
!= extension_
)
422 GlobalErrorServiceFactory::GetForProfile(service_
->profile())->
423 RemoveGlobalError(this);
425 // Make sure we don't call RemoveGlobalError again.
426 registrar_
.RemoveAll();
428 if (type
== extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
)
429 user_response_
= REENABLE
;
430 else if (type
== extensions::NOTIFICATION_EXTENSION_REMOVED
)
431 user_response_
= UNINSTALL
;
433 // Delete this object after any running tasks, so that the extension dialog
434 // still has it as a delegate to finish the current tasks.
435 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
438 // Globals --------------------------------------------------------------------
440 namespace extensions
{
442 void AddExtensionDisabledErrorWithIcon(base::WeakPtr
<ExtensionService
> service
,
443 const std::string
& extension_id
,
444 bool is_remote_install
,
445 const gfx::Image
& icon
) {
448 const Extension
* extension
= service
->GetInstalledExtension(extension_id
);
450 GlobalErrorServiceFactory::GetForProfile(service
->profile())
451 ->AddGlobalError(new ExtensionDisabledGlobalError(
452 service
.get(), extension
, is_remote_install
, icon
));
456 void AddExtensionDisabledError(ExtensionService
* service
,
457 const Extension
* extension
,
458 bool is_remote_install
) {
459 // Do not display notifications for ephemeral apps that have been disabled.
460 // Instead, a prompt will be shown the next time the app is launched.
461 if (util::IsEphemeralApp(extension
->id(), service
->profile()))
464 extensions::ExtensionResource image
= extensions::IconsInfo::GetIconResource(
465 extension
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
466 gfx::Size
size(kIconSize
, kIconSize
);
467 ImageLoader::Get(service
->profile())
468 ->LoadImageAsync(extension
,
471 base::Bind(&AddExtensionDisabledErrorWithIcon
,
472 service
->AsWeakPtr(),
477 void ShowExtensionDisabledDialog(ExtensionService
* service
,
478 content::WebContents
* web_contents
,
479 const Extension
* extension
) {
480 scoped_ptr
<ExtensionInstallPrompt
> install_ui(
481 new ExtensionInstallPrompt(web_contents
));
482 // This object manages its own lifetime.
483 new ExtensionDisabledDialogDelegate(service
, install_ui
.Pass(), extension
);
486 } // namespace extensions