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