Clean up extension confirmation prompts and make them consistent between Views and...
[chromium-blink-merge.git] / chrome / browser / extensions / extension_disabled_ui.cc
blob79809fefe376fc62bce39bbe9a65493b23ac6d96
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"
7 #include <bitset>
8 #include <string>
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/permission_message_provider.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::Extension;
55 namespace {
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() {
66 int id;
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);
71 return id;
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);
85 } // namespace
87 // ExtensionDisabledDialogDelegate --------------------------------------------
89 class ExtensionDisabledDialogDelegate
90 : public ExtensionInstallPrompt::Delegate,
91 public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> {
92 public:
93 ExtensionDisabledDialogDelegate(ExtensionService* service,
94 scoped_ptr<ExtensionInstallPrompt> install_ui,
95 const Extension* extension);
97 private:
98 friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>;
100 ~ExtensionDisabledDialogDelegate() override;
102 // ExtensionInstallPrompt::Delegate:
103 void InstallUIProceed() override;
104 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()),
118 service_(service),
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_);
129 Release();
132 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) {
133 std::string histogram_name = user_initiated ? "ReEnableCancel"
134 : "ReEnableAbort";
135 ExtensionService::RecordPermissionMessagesHistogram(
136 extension_, histogram_name.c_str());
138 // Do nothing. The extension will remain disabled.
139 Release();
142 // ExtensionDisabledGlobalError -----------------------------------------------
144 class ExtensionDisabledGlobalError
145 : public GlobalErrorWithStandardBubble,
146 public content::NotificationObserver,
147 public extensions::ExtensionUninstallDialog::Delegate {
148 public:
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 OnExtensionUninstallDialogClosed(bool did_start_uninstall,
173 const base::string16& error) override;
175 // content::NotificationObserver implementation.
176 void Observe(int type,
177 const content::NotificationSource& source,
178 const content::NotificationDetails& details) override;
180 private:
181 ExtensionService* service_;
182 const Extension* extension_;
183 bool is_remote_install_;
184 gfx::Image icon_;
186 // How the user responded to the error; used for metrics.
187 enum UserResponse {
188 IGNORED,
189 REENABLE,
190 UNINSTALL,
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)
209 : service_(service),
210 extension_(extension),
211 is_remote_install_(is_remote_install),
212 icon_(icon),
213 user_response_(IGNORED),
214 menu_command_id_(GetMenuCommandID()) {
215 if (icon_.IsEmpty()) {
216 icon_ = gfx::Image(
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)));
224 registrar_.Add(this,
225 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
226 content::Source<Profile>(service->profile()));
227 registrar_.Add(this,
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",
236 user_response_,
237 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
238 } else {
239 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
240 user_response_,
241 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
245 GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
246 return SEVERITY_LOW;
249 bool ExtensionDisabledGlobalError::HasMenuItem() {
250 return true;
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));
267 } else {
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() {
278 return icon_;
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()));
286 } else {
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 extensions::PermissionMessageStrings permission_warnings =
296 extensions::PermissionMessageProvider::Get()->GetPermissionMessageStrings(
297 extension_->permissions_data()->active_permissions().get(),
298 extension_->GetType());
299 if (is_remote_install_) {
300 messages.push_back(l10n_util::GetStringFUTF16(
301 extension_->is_app()
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())
306 messages.push_back(
307 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO));
308 } else {
309 // TODO(treib): Add an extra message for supervised users. crbug.com/461261
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 (const extensions::PermissionMessageString& str : permission_warnings) {
318 messages.push_back(l10n_util::GetStringFUTF16(
319 IDS_EXTENSION_PERMISSION_LINE, str.message));
321 return messages;
324 base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
325 if (extensions::util::IsExtensionSupervised(extension_,
326 service_->profile())) {
327 // TODO(treib): Probably use a new string here once we get UX design.
328 // For now, just re-use an existing string that says "OK". crbug.com/461261
329 return l10n_util::GetStringUTF16(IDS_EXTENSION_ALERT_ITEM_OK);
331 if (is_remote_install_) {
332 return l10n_util::GetStringUTF16(
333 extension_->is_app()
334 ? IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_APP
335 : IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_EXTENSION);
337 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON);
340 base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
341 // For custodian-installed extensions, supervised users only get a single
342 // "acknowledge" button.
343 if (extensions::util::IsExtensionSupervised(extension_, service_->profile()))
344 return base::string16();
345 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL);
348 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
351 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
352 Browser* browser) {
353 // Supervised users can't re-enable custodian-installed extensions, just
354 // acknowledge that they've been disabled.
355 if (extensions::util::IsExtensionSupervised(extension_, service_->profile()))
356 return;
357 // Delay extension reenabling so this bubble closes properly.
358 base::ThreadTaskRunnerHandle::Get()->PostTask(
359 FROM_HERE,
360 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension,
361 service_->AsWeakPtr(), extension_));
364 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
365 Browser* browser) {
366 // This button shouldn't exist for custodian-installed extensions in a
367 // supervised profile.
368 DCHECK(!extensions::util::IsExtensionSupervised(extension_,
369 service_->profile()));
371 uninstall_dialog_.reset(extensions::ExtensionUninstallDialog::Create(
372 service_->profile(), browser->window()->GetNativeWindow(), this));
373 // Delay showing the uninstall dialog, so that this function returns
374 // immediately, to close the bubble properly. See crbug.com/121544.
375 base::ThreadTaskRunnerHandle::Get()->PostTask(
376 FROM_HERE,
377 base::Bind(&extensions::ExtensionUninstallDialog::ConfirmUninstall,
378 uninstall_dialog_->AsWeakPtr(), extension_,
379 extensions::UNINSTALL_REASON_EXTENSION_DISABLED));
382 bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
383 // Since this indicates that an extension was disabled, we should definitely
384 // have the user acknowledge it, rather than having the bubble disappear when
385 // a new window pops up.
386 return false;
389 void ExtensionDisabledGlobalError::OnExtensionUninstallDialogClosed(
390 bool did_start_uninstall,
391 const base::string16& error) {
392 // No need to do anything.
395 void ExtensionDisabledGlobalError::Observe(
396 int type,
397 const content::NotificationSource& source,
398 const content::NotificationDetails& details) {
399 // The error is invalidated if the extension has been loaded or removed.
400 DCHECK(type == extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED ||
401 type == extensions::NOTIFICATION_EXTENSION_REMOVED);
402 const Extension* extension = content::Details<const Extension>(details).ptr();
403 if (extension != extension_)
404 return;
405 GlobalErrorServiceFactory::GetForProfile(service_->profile())->
406 RemoveGlobalError(this);
408 // Make sure we don't call RemoveGlobalError again.
409 registrar_.RemoveAll();
411 if (type == extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED)
412 user_response_ = REENABLE;
413 else if (type == extensions::NOTIFICATION_EXTENSION_REMOVED)
414 user_response_ = UNINSTALL;
416 // Delete this object after any running tasks, so that the extension dialog
417 // still has it as a delegate to finish the current tasks.
418 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
421 // Globals --------------------------------------------------------------------
423 namespace extensions {
425 void AddExtensionDisabledErrorWithIcon(base::WeakPtr<ExtensionService> service,
426 const std::string& extension_id,
427 bool is_remote_install,
428 const gfx::Image& icon) {
429 if (!service.get())
430 return;
431 const Extension* extension = service->GetInstalledExtension(extension_id);
432 if (extension) {
433 GlobalErrorServiceFactory::GetForProfile(service->profile())
434 ->AddGlobalError(new ExtensionDisabledGlobalError(
435 service.get(), extension, is_remote_install, icon));
439 void AddExtensionDisabledError(ExtensionService* service,
440 const Extension* extension,
441 bool is_remote_install) {
442 // Do not display notifications for ephemeral apps that have been disabled.
443 // Instead, a prompt will be shown the next time the app is launched.
444 if (util::IsEphemeralApp(extension->id(), service->profile()))
445 return;
447 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
448 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
449 gfx::Size size(kIconSize, kIconSize);
450 ImageLoader::Get(service->profile())
451 ->LoadImageAsync(extension,
452 image,
453 size,
454 base::Bind(&AddExtensionDisabledErrorWithIcon,
455 service->AsWeakPtr(),
456 extension->id(),
457 is_remote_install));
460 void ShowExtensionDisabledDialog(ExtensionService* service,
461 content::WebContents* web_contents,
462 const Extension* extension) {
463 scoped_ptr<ExtensionInstallPrompt> install_ui(
464 new ExtensionInstallPrompt(web_contents));
465 // This object manages its own lifetime.
466 new ExtensionDisabledDialogDelegate(service, install_ui.Pass(), extension);
469 } // namespace extensions