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