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_service.h"
21 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
22 #include "chrome/browser/extensions/extension_util.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_window.h"
26 #include "chrome/browser/ui/global_error/global_error.h"
27 #include "chrome/browser/ui/global_error/global_error_service.h"
28 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
29 #include "chrome/browser/ui/tabs/tab_strip_model.h"
30 #include "chrome/grit/chromium_strings.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "content/public/browser/notification_details.h"
33 #include "content/public/browser/notification_observer.h"
34 #include "content/public/browser/notification_registrar.h"
35 #include "content/public/browser/notification_source.h"
36 #include "extensions/browser/extension_util.h"
37 #include "extensions/browser/image_loader.h"
38 #include "extensions/browser/notification_types.h"
39 #include "extensions/browser/uninstall_reason.h"
40 #include "extensions/common/constants.h"
41 #include "extensions/common/extension.h"
42 #include "extensions/common/extension_icon_set.h"
43 #include "extensions/common/manifest_handlers/icons_handler.h"
44 #include "extensions/common/permissions/permission_message_provider.h"
45 #include "extensions/common/permissions/permission_set.h"
46 #include "extensions/common/permissions/permissions_data.h"
47 #include "ui/base/l10n/l10n_util.h"
48 #include "ui/gfx/image/image.h"
49 #include "ui/gfx/image/image_skia_operations.h"
50 #include "ui/gfx/size.h"
52 using extensions::Extension;
56 static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL;
58 static base::LazyInstance<
59 std::bitset<IDC_EXTENSION_DISABLED_LAST -
60 IDC_EXTENSION_DISABLED_FIRST + 1> >
61 menu_command_ids = LAZY_INSTANCE_INITIALIZER;
63 // Get an available menu ID.
64 int GetMenuCommandID() {
66 for (id = IDC_EXTENSION_DISABLED_FIRST;
67 id <= IDC_EXTENSION_DISABLED_LAST; ++id) {
68 if (!menu_command_ids.Get()[id - IDC_EXTENSION_DISABLED_FIRST]) {
69 menu_command_ids.Get().set(id - IDC_EXTENSION_DISABLED_FIRST);
73 // This should not happen.
74 DCHECK(id <= IDC_EXTENSION_DISABLED_LAST) <<
75 "No available menu command IDs for ExtensionDisabledGlobalError";
76 return IDC_EXTENSION_DISABLED_LAST;
79 // Make a menu ID available when it is no longer used.
80 void ReleaseMenuCommandID(int id) {
81 menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST);
86 // ExtensionDisabledDialogDelegate --------------------------------------------
88 class ExtensionDisabledDialogDelegate
89 : public ExtensionInstallPrompt::Delegate,
90 public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> {
92 ExtensionDisabledDialogDelegate(ExtensionService* service,
93 scoped_ptr<ExtensionInstallPrompt> install_ui,
94 const Extension* extension);
97 friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>;
99 ~ExtensionDisabledDialogDelegate() override;
101 // ExtensionInstallPrompt::Delegate:
102 void InstallUIProceed() override;
103 void InstallUIAbort(bool user_initiated) override;
105 // The UI for showing the install dialog when enabling.
106 scoped_ptr<ExtensionInstallPrompt> install_ui_;
108 ExtensionService* service_;
109 const Extension* extension_;
112 ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
113 ExtensionService* service,
114 scoped_ptr<ExtensionInstallPrompt> install_ui,
115 const Extension* extension)
116 : install_ui_(install_ui.Pass()),
118 extension_(extension) {
119 AddRef(); // Balanced in Proceed or Abort.
120 install_ui_->ConfirmReEnable(this, extension_);
123 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
126 void ExtensionDisabledDialogDelegate::InstallUIProceed() {
127 service_->GrantPermissionsAndEnableExtension(extension_);
131 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) {
132 std::string histogram_name = user_initiated
133 ? "Extensions.Permissions_ReEnableCancel2"
134 : "Extensions.Permissions_ReEnableAbort2";
135 ExtensionService::RecordPermissionMessagesHistogram(
136 extension_, histogram_name.c_str());
138 // Do nothing. The extension will remain disabled.
142 // ExtensionDisabledGlobalError -----------------------------------------------
144 class ExtensionDisabledGlobalError
145 : public GlobalErrorWithStandardBubble,
146 public content::NotificationObserver,
147 public extensions::ExtensionUninstallDialog::Delegate {
149 ExtensionDisabledGlobalError(ExtensionService* service,
150 const Extension* extension,
151 bool is_remote_install,
152 const gfx::Image& icon);
153 ~ExtensionDisabledGlobalError() override;
155 // GlobalError implementation.
156 Severity GetSeverity() override;
157 bool HasMenuItem() override;
158 int MenuItemCommandID() override;
159 base::string16 MenuItemLabel() override;
160 void ExecuteMenuItem(Browser* browser) override;
161 gfx::Image GetBubbleViewIcon() override;
162 base::string16 GetBubbleViewTitle() override;
163 std::vector<base::string16> GetBubbleViewMessages() override;
164 base::string16 GetBubbleViewAcceptButtonLabel() override;
165 base::string16 GetBubbleViewCancelButtonLabel() override;
166 void OnBubbleViewDidClose(Browser* browser) override;
167 void BubbleViewAcceptButtonPressed(Browser* browser) override;
168 void BubbleViewCancelButtonPressed(Browser* browser) override;
169 bool ShouldCloseOnDeactivate() const override;
171 // ExtensionUninstallDialog::Delegate implementation.
172 void ExtensionUninstallAccepted() override;
173 void ExtensionUninstallCanceled() override;
175 // content::NotificationObserver implementation.
176 void Observe(int type,
177 const content::NotificationSource& source,
178 const content::NotificationDetails& details) override;
181 ExtensionService* service_;
182 const Extension* extension_;
183 bool is_remote_install_;
186 // How the user responded to the error; used for metrics.
191 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
193 UserResponse user_response_;
195 scoped_ptr<extensions::ExtensionUninstallDialog> uninstall_dialog_;
197 // Menu command ID assigned for this extension's error.
198 int menu_command_id_;
200 content::NotificationRegistrar registrar_;
203 // TODO(yoz): create error at startup for disabled extensions.
204 ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
205 ExtensionService* service,
206 const Extension* extension,
207 bool is_remote_install,
208 const gfx::Image& icon)
210 extension_(extension),
211 is_remote_install_(is_remote_install),
213 user_response_(IGNORED),
214 menu_command_id_(GetMenuCommandID()) {
215 if (icon_.IsEmpty()) {
217 gfx::ImageSkiaOperations::CreateResizedImage(
218 extension_->is_app() ?
219 extensions::util::GetDefaultAppIcon() :
220 extensions::util::GetDefaultExtensionIcon(),
221 skia::ImageOperations::RESIZE_BEST,
222 gfx::Size(kIconSize, kIconSize)));
225 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
226 content::Source<Profile>(service->profile()));
228 extensions::NOTIFICATION_EXTENSION_REMOVED,
229 content::Source<Profile>(service->profile()));
232 ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
233 ReleaseMenuCommandID(menu_command_id_);
234 if (is_remote_install_) {
235 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponseRemoteInstall",
237 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
239 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
241 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
245 GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
249 bool ExtensionDisabledGlobalError::HasMenuItem() {
253 int ExtensionDisabledGlobalError::MenuItemCommandID() {
254 return menu_command_id_;
257 base::string16 ExtensionDisabledGlobalError::MenuItemLabel() {
258 std::string extension_name = extension_->name();
259 // Ampersands need to be escaped to avoid being treated like
260 // mnemonics in the menu.
261 base::ReplaceChars(extension_name, "&", "&&", &extension_name);
263 if (is_remote_install_) {
264 return l10n_util::GetStringFUTF16(
265 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
266 base::UTF8ToUTF16(extension_name));
268 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
269 base::UTF8ToUTF16(extension_name));
273 void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) {
274 ShowBubbleView(browser);
277 gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() {
281 base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() {
282 if (is_remote_install_) {
283 return l10n_util::GetStringFUTF16(
284 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
285 base::UTF8ToUTF16(extension_->name()));
287 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
288 base::UTF8ToUTF16(extension_->name()));
292 std::vector<base::string16>
293 ExtensionDisabledGlobalError::GetBubbleViewMessages() {
294 std::vector<base::string16> messages;
295 std::vector<base::string16> permission_warnings =
296 extensions::PermissionMessageProvider::Get()->GetWarningMessages(
297 extension_->permissions_data()->active_permissions().get(),
298 extension_->GetType());
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 messages.push_back(l10n_util::GetStringFUTF16(
310 extension_->is_app() ? IDS_APP_DISABLED_ERROR_LABEL
311 : IDS_EXTENSION_DISABLED_ERROR_LABEL,
312 base::UTF8ToUTF16(extension_->name())));
313 messages.push_back(l10n_util::GetStringUTF16(
314 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO));
316 for (size_t i = 0; i < permission_warnings.size(); ++i) {
317 messages.push_back(l10n_util::GetStringFUTF16(
318 IDS_EXTENSION_PERMISSION_LINE, permission_warnings[i]));
323 base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
324 if (is_remote_install_) {
325 return l10n_util::GetStringUTF16(
326 IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON);
328 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON);
332 base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
333 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL);
336 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
339 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
341 // Delay extension reenabling so this bubble closes properly.
342 base::MessageLoop::current()->PostTask(FROM_HERE,
343 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension,
344 service_->AsWeakPtr(), extension_));
347 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
349 uninstall_dialog_.reset(extensions::ExtensionUninstallDialog::Create(
350 service_->profile(), browser->window()->GetNativeWindow(), this));
351 // Delay showing the uninstall dialog, so that this function returns
352 // immediately, to close the bubble properly. See crbug.com/121544.
353 base::MessageLoop::current()->PostTask(
355 base::Bind(&extensions::ExtensionUninstallDialog::ConfirmUninstall,
356 uninstall_dialog_->AsWeakPtr(),
360 bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
361 // Since this indicates that an extension was disabled, we should definitely
362 // have the user acknowledge it, rather than having the bubble disappear when
363 // a new window pops up.
367 void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() {
368 service_->UninstallExtension(extension_->id(),
369 extensions::UNINSTALL_REASON_EXTENSION_DISABLED,
370 base::Bind(&base::DoNothing),
374 void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() {
375 // Nothing happens, and the error is still there.
378 void ExtensionDisabledGlobalError::Observe(
380 const content::NotificationSource& source,
381 const content::NotificationDetails& details) {
382 // The error is invalidated if the extension has been loaded or removed.
383 DCHECK(type == extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED ||
384 type == extensions::NOTIFICATION_EXTENSION_REMOVED);
385 const Extension* extension = content::Details<const Extension>(details).ptr();
386 if (extension != extension_)
388 GlobalErrorServiceFactory::GetForProfile(service_->profile())->
389 RemoveGlobalError(this);
391 if (type == extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED)
392 user_response_ = REENABLE;
393 else if (type == extensions::NOTIFICATION_EXTENSION_REMOVED)
394 user_response_ = UNINSTALL;
398 // Globals --------------------------------------------------------------------
400 namespace extensions {
402 void AddExtensionDisabledErrorWithIcon(base::WeakPtr<ExtensionService> service,
403 const std::string& extension_id,
404 bool is_remote_install,
405 const gfx::Image& icon) {
408 const Extension* extension = service->GetInstalledExtension(extension_id);
410 GlobalErrorServiceFactory::GetForProfile(service->profile())
411 ->AddGlobalError(new ExtensionDisabledGlobalError(
412 service.get(), extension, is_remote_install, icon));
416 void AddExtensionDisabledError(ExtensionService* service,
417 const Extension* extension,
418 bool is_remote_install) {
419 // Do not display notifications for ephemeral apps that have been disabled.
420 // Instead, a prompt will be shown the next time the app is launched.
421 if (util::IsEphemeralApp(extension->id(), service->profile()))
424 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
425 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
426 gfx::Size size(kIconSize, kIconSize);
427 ImageLoader::Get(service->profile())
428 ->LoadImageAsync(extension,
431 base::Bind(&AddExtensionDisabledErrorWithIcon,
432 service->AsWeakPtr(),
437 void ShowExtensionDisabledDialog(ExtensionService* service,
438 content::WebContents* web_contents,
439 const Extension* extension) {
440 scoped_ptr<ExtensionInstallPrompt> install_ui(
441 new ExtensionInstallPrompt(web_contents));
442 // This object manages its own lifetime.
443 new ExtensionDisabledDialogDelegate(service, install_ui.Pass(), extension);
446 } // namespace extensions