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