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_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 
     46 using extensions::Extension;
     47 
     48 namespace {
     49 
     50 static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL;
     51 
     52 static 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.
     58 int 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.
     74 void ReleaseMenuCommandID(int id) {
     75   menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST);
     76 }
     77 
     78 }  // namespace
     79 
     80 // ExtensionDisabledDialogDelegate --------------------------------------------
     81 
     82 class 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 
    106 ExtensionDisabledDialogDelegate::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 
    117 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
    118 }
    119 
    120 void ExtensionDisabledDialogDelegate::InstallUIProceed() {
    121   service_->GrantPermissionsAndEnableExtension(extension_);
    122   Release();
    123 }
    124 
    125 void 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 
    138 class 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.
    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 base::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 gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() {
    247   return icon_;
    248 }
    249 
    250 base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() {
    251   return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
    252                                     UTF8ToUTF16(extension_->name()));
    253 }
    254 
    255 std::vector<base::string16>
    256 ExtensionDisabledGlobalError::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 
    274 base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
    275   return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON);
    276 }
    277 
    278 base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
    279   return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL);
    280 }
    281 
    282 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
    283 }
    284 
    285 void 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 
    293 void 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 
    306 void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() {
    307   service_->UninstallExtension(extension_->id(), false, NULL);
    308 }
    309 
    310 void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() {
    311   // Nothing happens, and the error is still there.
    312 }
    313 
    314 void 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 
    336 namespace extensions {
    337 
    338 void 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 
    351 void 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 
    362 void 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