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