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