extension_disabled_ui.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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. 4 5#include "chrome/browser/extensions/extension_disabled_ui.h" 6 7#include <string> 8 9#include "base/bind.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/extension_util.h" 23#include "chrome/browser/extensions/image_loader.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/ui/browser.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 "content/public/browser/notification_details.h" 31#include "content/public/browser/notification_observer.h" 32#include "content/public/browser/notification_registrar.h" 33#include "content/public/browser/notification_source.h" 34#include "extensions/common/extension.h" 35#include "extensions/common/extension_icon_set.h" 36#include "extensions/common/manifest_handlers/icons_handler.h" 37#include "extensions/common/permissions/permission_message_provider.h" 38#include "extensions/common/permissions/permission_set.h" 39#include "grit/chromium_strings.h" 40#include "grit/generated_resources.h" 41#include "grit/theme_resources.h" 42#include "ui/base/l10n/l10n_util.h" 43#include "ui/gfx/image/image.h" 44#include "ui/gfx/image/image_skia_operations.h" 45#include "ui/gfx/size.h" 46 47using extensions::Extension; 48 49namespace { 50 51static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL; 52 53static base::LazyInstance< 54 std::bitset<IDC_EXTENSION_DISABLED_LAST - 55 IDC_EXTENSION_DISABLED_FIRST + 1> > 56 menu_command_ids = LAZY_INSTANCE_INITIALIZER; 57 58// Get an available menu ID. 59int GetMenuCommandID() { 60 int id; 61 for (id = IDC_EXTENSION_DISABLED_FIRST; 62 id <= IDC_EXTENSION_DISABLED_LAST; ++id) { 63 if (!menu_command_ids.Get()[id - IDC_EXTENSION_DISABLED_FIRST]) { 64 menu_command_ids.Get().set(id - IDC_EXTENSION_DISABLED_FIRST); 65 return id; 66 } 67 } 68 // This should not happen. 69 DCHECK(id <= IDC_EXTENSION_DISABLED_LAST) << 70 "No available menu command IDs for ExtensionDisabledGlobalError"; 71 return IDC_EXTENSION_DISABLED_LAST; 72} 73 74// Make a menu ID available when it is no longer used. 75void ReleaseMenuCommandID(int id) { 76 menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST); 77} 78 79} // namespace 80 81// ExtensionDisabledDialogDelegate -------------------------------------------- 82 83class ExtensionDisabledDialogDelegate 84 : public ExtensionInstallPrompt::Delegate, 85 public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> { 86 public: 87 ExtensionDisabledDialogDelegate(ExtensionService* service, 88 scoped_ptr<ExtensionInstallPrompt> install_ui, 89 const Extension* extension); 90 91 private: 92 friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>; 93 94 virtual ~ExtensionDisabledDialogDelegate(); 95 96 // ExtensionInstallPrompt::Delegate: 97 virtual void InstallUIProceed() OVERRIDE; 98 virtual void InstallUIAbort(bool user_initiated) OVERRIDE; 99 100 // The UI for showing the install dialog when enabling. 101 scoped_ptr<ExtensionInstallPrompt> install_ui_; 102 103 ExtensionService* service_; 104 const Extension* extension_; 105}; 106 107ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate( 108 ExtensionService* service, 109 scoped_ptr<ExtensionInstallPrompt> install_ui, 110 const Extension* extension) 111 : install_ui_(install_ui.Pass()), 112 service_(service), 113 extension_(extension) { 114 AddRef(); // Balanced in Proceed or Abort. 115 install_ui_->ConfirmReEnable(this, extension_); 116} 117 118ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() { 119} 120 121void ExtensionDisabledDialogDelegate::InstallUIProceed() { 122 service_->GrantPermissionsAndEnableExtension(extension_); 123 Release(); 124} 125 126void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) { 127 std::string histogram_name = user_initiated ? 128 "Extensions.Permissions_ReEnableCancel" : 129 "Extensions.Permissions_ReEnableAbort"; 130 ExtensionService::RecordPermissionMessagesHistogram( 131 extension_, histogram_name.c_str()); 132 133 // Do nothing. The extension will remain disabled. 134 Release(); 135} 136 137// ExtensionDisabledGlobalError ----------------------------------------------- 138 139class ExtensionDisabledGlobalError : public GlobalErrorWithStandardBubble, 140 public content::NotificationObserver, 141 public ExtensionUninstallDialog::Delegate { 142 public: 143 ExtensionDisabledGlobalError(ExtensionService* service, 144 const Extension* extension, 145 const gfx::Image& icon); 146 virtual ~ExtensionDisabledGlobalError(); 147 148 // GlobalError implementation. 149 virtual Severity GetSeverity() OVERRIDE; 150 virtual bool HasMenuItem() OVERRIDE; 151 virtual int MenuItemCommandID() OVERRIDE; 152 virtual base::string16 MenuItemLabel() OVERRIDE; 153 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; 154 virtual gfx::Image GetBubbleViewIcon() OVERRIDE; 155 virtual base::string16 GetBubbleViewTitle() OVERRIDE; 156 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE; 157 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE; 158 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE; 159 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE; 160 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE; 161 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE; 162 163 // ExtensionUninstallDialog::Delegate implementation. 164 virtual void ExtensionUninstallAccepted() OVERRIDE; 165 virtual void ExtensionUninstallCanceled() OVERRIDE; 166 167 // content::NotificationObserver implementation. 168 virtual void Observe(int type, 169 const content::NotificationSource& source, 170 const content::NotificationDetails& details) OVERRIDE; 171 172 private: 173 ExtensionService* service_; 174 const Extension* extension_; 175 gfx::Image icon_; 176 177 // How the user responded to the error; used for metrics. 178 enum UserResponse { 179 IGNORED, 180 REENABLE, 181 UNINSTALL, 182 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY 183 }; 184 UserResponse user_response_; 185 186 scoped_ptr<ExtensionUninstallDialog> uninstall_dialog_; 187 188 // Menu command ID assigned for this extension's error. 189 int menu_command_id_; 190 191 content::NotificationRegistrar registrar_; 192}; 193 194// TODO(yoz): create error at startup for disabled extensions. 195ExtensionDisabledGlobalError::ExtensionDisabledGlobalError( 196 ExtensionService* service, 197 const Extension* extension, 198 const gfx::Image& icon) 199 : service_(service), 200 extension_(extension), 201 icon_(icon), 202 user_response_(IGNORED), 203 menu_command_id_(GetMenuCommandID()) { 204 if (icon_.IsEmpty()) { 205 icon_ = gfx::Image( 206 gfx::ImageSkiaOperations::CreateResizedImage( 207 extension_->is_app() ? 208 extensions::util::GetDefaultAppIcon() : 209 extensions::util::GetDefaultExtensionIcon(), 210 skia::ImageOperations::RESIZE_BEST, 211 gfx::Size(kIconSize, kIconSize))); 212 } 213 registrar_.Add(this, 214 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, 215 content::Source<Profile>(service->profile())); 216 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED, 217 content::Source<Profile>(service->profile())); 218} 219 220ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() { 221 ReleaseMenuCommandID(menu_command_id_); 222 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse", 223 user_response_, 224 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY); 225} 226 227GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() { 228 return SEVERITY_LOW; 229} 230 231bool ExtensionDisabledGlobalError::HasMenuItem() { 232 return true; 233} 234 235int ExtensionDisabledGlobalError::MenuItemCommandID() { 236 return menu_command_id_; 237} 238 239base::string16 ExtensionDisabledGlobalError::MenuItemLabel() { 240 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE, 241 base::UTF8ToUTF16(extension_->name())); 242} 243 244void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) { 245 ShowBubbleView(browser); 246} 247 248gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() { 249 return icon_; 250} 251 252base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() { 253 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE, 254 base::UTF8ToUTF16(extension_->name())); 255} 256 257std::vector<base::string16> 258ExtensionDisabledGlobalError::GetBubbleViewMessages() { 259 std::vector<base::string16> messages; 260 messages.push_back(l10n_util::GetStringFUTF16( 261 extension_->is_app() ? 262 IDS_APP_DISABLED_ERROR_LABEL : IDS_EXTENSION_DISABLED_ERROR_LABEL, 263 base::UTF8ToUTF16(extension_->name()))); 264 messages.push_back(l10n_util::GetStringUTF16( 265 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO)); 266 std::vector<base::string16> permission_warnings = 267 extensions::PermissionMessageProvider::Get()->GetWarningMessages( 268 extension_->GetActivePermissions(), extension_->GetType()); 269 for (size_t i = 0; i < permission_warnings.size(); ++i) { 270 messages.push_back(l10n_util::GetStringFUTF16( 271 IDS_EXTENSION_PERMISSION_LINE, permission_warnings[i])); 272 } 273 return messages; 274} 275 276base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() { 277 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON); 278} 279 280base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() { 281 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL); 282} 283 284void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) { 285} 286 287void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed( 288 Browser* browser) { 289 // Delay extension reenabling so this bubble closes properly. 290 base::MessageLoop::current()->PostTask(FROM_HERE, 291 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension, 292 service_->AsWeakPtr(), extension_)); 293} 294 295void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed( 296 Browser* browser) { 297#if !defined(OS_ANDROID) 298 uninstall_dialog_.reset( 299 ExtensionUninstallDialog::Create(service_->profile(), browser, this)); 300 // Delay showing the uninstall dialog, so that this function returns 301 // immediately, to close the bubble properly. See crbug.com/121544. 302 base::MessageLoop::current()->PostTask(FROM_HERE, 303 base::Bind(&ExtensionUninstallDialog::ConfirmUninstall, 304 uninstall_dialog_->AsWeakPtr(), extension_)); 305#endif // !defined(OS_ANDROID) 306} 307 308void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() { 309 service_->UninstallExtension(extension_->id(), false, NULL); 310} 311 312void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() { 313 // Nothing happens, and the error is still there. 314} 315 316void ExtensionDisabledGlobalError::Observe( 317 int type, 318 const content::NotificationSource& source, 319 const content::NotificationDetails& details) { 320 // The error is invalidated if the extension has been loaded or removed. 321 DCHECK(type == chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED || 322 type == chrome::NOTIFICATION_EXTENSION_REMOVED); 323 const Extension* extension = content::Details<const Extension>(details).ptr(); 324 if (extension != extension_) 325 return; 326 GlobalErrorServiceFactory::GetForProfile(service_->profile())-> 327 RemoveGlobalError(this); 328 329 if (type == chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED) 330 user_response_ = REENABLE; 331 else if (type == chrome::NOTIFICATION_EXTENSION_REMOVED) 332 user_response_ = UNINSTALL; 333 delete this; 334} 335 336// Globals -------------------------------------------------------------------- 337 338namespace extensions { 339 340void AddExtensionDisabledErrorWithIcon(base::WeakPtr<ExtensionService> service, 341 const std::string& extension_id, 342 const gfx::Image& icon) { 343 if (!service.get()) 344 return; 345 const Extension* extension = service->GetInstalledExtension(extension_id); 346 if (extension) { 347 GlobalErrorServiceFactory::GetForProfile(service->profile()) 348 ->AddGlobalError( 349 new ExtensionDisabledGlobalError(service.get(), extension, icon)); 350 } 351} 352 353void AddExtensionDisabledError(ExtensionService* service, 354 const Extension* extension) { 355 // Do not display notifications for ephemeral apps that have been disabled. 356 // Instead, a prompt will be shown the next time the app is launched. 357 if (extension->is_ephemeral()) 358 return; 359 360 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource( 361 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER); 362 gfx::Size size(kIconSize, kIconSize); 363 ImageLoader::Get(service->profile())->LoadImageAsync( 364 extension, image, size, 365 base::Bind(&AddExtensionDisabledErrorWithIcon, 366 service->AsWeakPtr(), extension->id())); 367} 368 369void ShowExtensionDisabledDialog(ExtensionService* service, 370 content::WebContents* web_contents, 371 const Extension* extension) { 372 scoped_ptr<ExtensionInstallPrompt> install_ui( 373 new ExtensionInstallPrompt(web_contents)); 374 // This object manages its own lifetime. 375 new ExtensionDisabledDialogDelegate(service, install_ui.Pass(), extension); 376} 377 378} // namespace extensions 379