Home | History | Annotate | Download | only in extensions
      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