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.h" 30 #include "chrome/common/extensions/extension_icon_set.h" 31 #include "chrome/common/extensions/manifest_handlers/icons_handler.h" 32 #include "chrome/common/extensions/permissions/permission_set.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 "grit/chromium_strings.h" 38 #include "grit/generated_resources.h" 39 #include "grit/theme_resources.h" 40 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/gfx/image/image.h" 42 #include "ui/gfx/image/image_skia_operations.h" 43 #include "ui/gfx/size.h" 44 45 using extensions::Extension; 46 47 namespace { 48 49 static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL; 50 51 static base::LazyInstance< 52 std::bitset<IDC_EXTENSION_DISABLED_LAST - 53 IDC_EXTENSION_DISABLED_FIRST + 1> > 54 menu_command_ids = LAZY_INSTANCE_INITIALIZER; 55 56 // Get an available menu ID. 57 int GetMenuCommandID() { 58 int id; 59 for (id = IDC_EXTENSION_DISABLED_FIRST; 60 id <= IDC_EXTENSION_DISABLED_LAST; ++id) { 61 if (!menu_command_ids.Get()[id - IDC_EXTENSION_DISABLED_FIRST]) { 62 menu_command_ids.Get().set(id - IDC_EXTENSION_DISABLED_FIRST); 63 return id; 64 } 65 } 66 // This should not happen. 67 DCHECK(id <= IDC_EXTENSION_DISABLED_LAST) << 68 "No available menu command IDs for ExtensionDisabledGlobalError"; 69 return IDC_EXTENSION_DISABLED_LAST; 70 } 71 72 // Make a menu ID available when it is no longer used. 73 void ReleaseMenuCommandID(int id) { 74 menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST); 75 } 76 77 } // namespace 78 79 // ExtensionDisabledDialogDelegate -------------------------------------------- 80 81 class ExtensionDisabledDialogDelegate 82 : public ExtensionInstallPrompt::Delegate, 83 public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> { 84 public: 85 ExtensionDisabledDialogDelegate(ExtensionService* service, 86 scoped_ptr<ExtensionInstallPrompt> install_ui, 87 const Extension* extension); 88 89 private: 90 friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>; 91 92 virtual ~ExtensionDisabledDialogDelegate(); 93 94 // ExtensionInstallPrompt::Delegate: 95 virtual void InstallUIProceed() OVERRIDE; 96 virtual void InstallUIAbort(bool user_initiated) OVERRIDE; 97 98 // The UI for showing the install dialog when enabling. 99 scoped_ptr<ExtensionInstallPrompt> install_ui_; 100 101 ExtensionService* service_; 102 const Extension* extension_; 103 }; 104 105 ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate( 106 ExtensionService* service, 107 scoped_ptr<ExtensionInstallPrompt> install_ui, 108 const Extension* extension) 109 : install_ui_(install_ui.Pass()), 110 service_(service), 111 extension_(extension) { 112 AddRef(); // Balanced in Proceed or Abort. 113 install_ui_->ConfirmReEnable(this, extension_); 114 } 115 116 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() { 117 } 118 119 void ExtensionDisabledDialogDelegate::InstallUIProceed() { 120 service_->GrantPermissionsAndEnableExtension(extension_); 121 Release(); 122 } 123 124 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) { 125 std::string histogram_name = user_initiated ? 126 "Extensions.Permissions_ReEnableCancel" : 127 "Extensions.Permissions_ReEnableAbort"; 128 ExtensionService::RecordPermissionMessagesHistogram( 129 extension_, histogram_name.c_str()); 130 131 // Do nothing. The extension will remain disabled. 132 Release(); 133 } 134 135 // ExtensionDisabledGlobalError ----------------------------------------------- 136 137 class ExtensionDisabledGlobalError : public GlobalError, 138 public content::NotificationObserver, 139 public ExtensionUninstallDialog::Delegate { 140 public: 141 ExtensionDisabledGlobalError(ExtensionService* service, 142 const Extension* extension, 143 const gfx::Image& icon); 144 virtual ~ExtensionDisabledGlobalError(); 145 146 // GlobalError implementation. 147 virtual Severity GetSeverity() OVERRIDE; 148 virtual bool HasMenuItem() OVERRIDE; 149 virtual int MenuItemCommandID() OVERRIDE; 150 virtual string16 MenuItemLabel() OVERRIDE; 151 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; 152 virtual bool HasBubbleView() OVERRIDE; 153 virtual gfx::Image GetBubbleViewIcon() OVERRIDE; 154 virtual string16 GetBubbleViewTitle() OVERRIDE; 155 virtual std::vector<string16> GetBubbleViewMessages() OVERRIDE; 156 virtual string16 GetBubbleViewAcceptButtonLabel() OVERRIDE; 157 virtual 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 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 bool ExtensionDisabledGlobalError::HasBubbleView() { 247 return true; 248 } 249 250 gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() { 251 return icon_; 252 } 253 254 string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() { 255 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE, 256 UTF8ToUTF16(extension_->name())); 257 } 258 259 std::vector<string16> ExtensionDisabledGlobalError::GetBubbleViewMessages() { 260 std::vector<string16> messages; 261 messages.push_back(l10n_util::GetStringFUTF16( 262 extension_->is_app() ? 263 IDS_APP_DISABLED_ERROR_LABEL : IDS_EXTENSION_DISABLED_ERROR_LABEL, 264 UTF8ToUTF16(extension_->name()))); 265 messages.push_back(l10n_util::GetStringUTF16( 266 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO)); 267 std::vector<string16> permission_warnings = 268 extension_->GetActivePermissions()->GetWarningMessages( 269 extension_->GetType()); 270 for (size_t i = 0; i < permission_warnings.size(); ++i) { 271 messages.push_back(l10n_util::GetStringFUTF16( 272 IDS_EXTENSION_PERMISSION_LINE, permission_warnings[i])); 273 } 274 return messages; 275 } 276 277 string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() { 278 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON); 279 } 280 281 string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() { 282 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL); 283 } 284 285 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) { 286 } 287 288 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed( 289 Browser* browser) { 290 // Delay extension reenabling so this bubble closes properly. 291 base::MessageLoop::current()->PostTask(FROM_HERE, 292 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension, 293 service_->AsWeakPtr(), extension_)); 294 } 295 296 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed( 297 Browser* browser) { 298 #if !defined(OS_ANDROID) 299 uninstall_dialog_.reset( 300 ExtensionUninstallDialog::Create(service_->profile(), browser, this)); 301 // Delay showing the uninstall dialog, so that this function returns 302 // immediately, to close the bubble properly. See crbug.com/121544. 303 base::MessageLoop::current()->PostTask(FROM_HERE, 304 base::Bind(&ExtensionUninstallDialog::ConfirmUninstall, 305 uninstall_dialog_->AsWeakPtr(), extension_)); 306 #endif // !defined(OS_ANDROID) 307 } 308 309 void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() { 310 service_->UninstallExtension(extension_->id(), false, NULL); 311 } 312 313 void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() { 314 // Nothing happens, and the error is still there. 315 } 316 317 void ExtensionDisabledGlobalError::Observe( 318 int type, 319 const content::NotificationSource& source, 320 const content::NotificationDetails& details) { 321 // The error is invalidated if the extension has been loaded or removed. 322 DCHECK(type == chrome::NOTIFICATION_EXTENSION_LOADED || 323 type == chrome::NOTIFICATION_EXTENSION_REMOVED); 324 const Extension* extension = content::Details<const Extension>(details).ptr(); 325 if (extension != extension_) 326 return; 327 GlobalErrorServiceFactory::GetForProfile(service_->profile())-> 328 RemoveGlobalError(this); 329 330 if (type == chrome::NOTIFICATION_EXTENSION_LOADED) 331 user_response_ = REENABLE; 332 else if (type == chrome::NOTIFICATION_EXTENSION_REMOVED) 333 user_response_ = UNINSTALL; 334 delete this; 335 } 336 337 // Globals -------------------------------------------------------------------- 338 339 namespace extensions { 340 341 void AddExtensionDisabledErrorWithIcon(base::WeakPtr<ExtensionService> service, 342 const std::string& extension_id, 343 const gfx::Image& icon) { 344 if (!service.get()) 345 return; 346 const Extension* extension = service->GetInstalledExtension(extension_id); 347 if (extension) { 348 GlobalErrorServiceFactory::GetForProfile(service->profile()) 349 ->AddGlobalError( 350 new ExtensionDisabledGlobalError(service.get(), extension, icon)); 351 } 352 } 353 354 void AddExtensionDisabledError(ExtensionService* service, 355 const Extension* extension) { 356 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource( 357 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER); 358 gfx::Size size(kIconSize, kIconSize); 359 ImageLoader::Get(service->profile())->LoadImageAsync( 360 extension, image, size, 361 base::Bind(&AddExtensionDisabledErrorWithIcon, 362 service->AsWeakPtr(), extension->id())); 363 } 364 365 void ShowExtensionDisabledDialog(ExtensionService* service, 366 content::WebContents* web_contents, 367 const Extension* extension) { 368 scoped_ptr<ExtensionInstallPrompt> install_ui( 369 new ExtensionInstallPrompt(web_contents)); 370 // This object manages its own lifetime. 371 new ExtensionDisabledDialogDelegate(service, install_ui.Pass(), extension); 372 } 373 374 } // namespace extensions 375