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 46using extensions::Extension; 47 48namespace { 49 50static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL; 51 52static 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. 58int 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. 74void ReleaseMenuCommandID(int id) { 75 menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST); 76} 77 78} // namespace 79 80// ExtensionDisabledDialogDelegate -------------------------------------------- 81 82class 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 106ExtensionDisabledDialogDelegate::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 117ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() { 118} 119 120void ExtensionDisabledDialogDelegate::InstallUIProceed() { 121 service_->GrantPermissionsAndEnableExtension(extension_); 122 Release(); 123} 124 125void 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 138class 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. 194ExtensionDisabledGlobalError::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 218ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() { 219 ReleaseMenuCommandID(menu_command_id_); 220 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse", 221 user_response_, 222 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY); 223} 224 225GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() { 226 return SEVERITY_LOW; 227} 228 229bool ExtensionDisabledGlobalError::HasMenuItem() { 230 return true; 231} 232 233int ExtensionDisabledGlobalError::MenuItemCommandID() { 234 return menu_command_id_; 235} 236 237base::string16 ExtensionDisabledGlobalError::MenuItemLabel() { 238 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE, 239 UTF8ToUTF16(extension_->name())); 240} 241 242void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) { 243 ShowBubbleView(browser); 244} 245 246gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() { 247 return icon_; 248} 249 250base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() { 251 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE, 252 UTF8ToUTF16(extension_->name())); 253} 254 255std::vector<base::string16> 256ExtensionDisabledGlobalError::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 274base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() { 275 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON); 276} 277 278base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() { 279 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL); 280} 281 282void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) { 283} 284 285void 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 293void 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 306void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() { 307 service_->UninstallExtension(extension_->id(), false, NULL); 308} 309 310void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() { 311 // Nothing happens, and the error is still there. 312} 313 314void 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 336namespace extensions { 337 338void 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 351void 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 362void 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