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/external_install_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/profiles/profile.h"
     23 #include "chrome/browser/ui/browser.h"
     24 #include "chrome/browser/ui/browser_finder.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/host_desktop.h"
     29 #include "chrome/common/extensions/extension_constants.h"
     30 #include "chrome/common/extensions/manifest_url_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 "grit/chromium_strings.h"
     37 #include "grit/generated_resources.h"
     38 #include "grit/theme_resources.h"
     39 #include "ui/base/l10n/l10n_util.h"
     40 #include "ui/gfx/image/image.h"
     41 #include "ui/gfx/image/image_skia_operations.h"
     42 #include "ui/gfx/size.h"
     43 
     44 namespace extensions {
     45 
     46 namespace {
     47 
     48 // Whether the external extension can use the streamlined bubble install flow.
     49 bool UseBubbleInstall(const Extension* extension, bool is_new_profile) {
     50   return ManifestURL::UpdatesFromGallery(extension) && !is_new_profile;
     51 }
     52 
     53 }  // namespace
     54 
     55 static const int kMenuCommandId = IDC_EXTERNAL_EXTENSION_ALERT;
     56 
     57 class ExternalInstallGlobalError;
     58 
     59 // TODO(mpcomplete): Get rid of the refcounting on this class, or document
     60 // why it's necessary. Will do after refactoring to merge back with
     61 // ExtensionDisabledDialogDelegate.
     62 class ExternalInstallDialogDelegate
     63     : public ExtensionInstallPrompt::Delegate,
     64       public base::RefCountedThreadSafe<ExternalInstallDialogDelegate> {
     65  public:
     66   ExternalInstallDialogDelegate(Browser* browser,
     67                                 ExtensionService* service,
     68                                 const Extension* extension,
     69                                 bool use_global_error);
     70 
     71   Browser* browser() { return browser_; }
     72 
     73  private:
     74   friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>;
     75   friend class ExternalInstallGlobalError;
     76 
     77   virtual ~ExternalInstallDialogDelegate();
     78 
     79   // ExtensionInstallPrompt::Delegate:
     80   virtual void InstallUIProceed() OVERRIDE;
     81   virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
     82 
     83   // The UI for showing the install dialog when enabling.
     84   scoped_ptr<ExtensionInstallPrompt> install_ui_;
     85 
     86   Browser* browser_;
     87   base::WeakPtr<ExtensionService> service_weak_;
     88   const std::string extension_id_;
     89 };
     90 
     91 // Only shows a menu item, no bubble. Clicking the menu item shows
     92 // an external install dialog.
     93 class ExternalInstallMenuAlert : public GlobalErrorWithStandardBubble,
     94                                  public content::NotificationObserver {
     95  public:
     96   ExternalInstallMenuAlert(ExtensionService* service,
     97                            const Extension* extension);
     98   virtual ~ExternalInstallMenuAlert();
     99 
    100   const Extension* extension() const { return extension_; }
    101 
    102   // GlobalError implementation.
    103   virtual Severity GetSeverity() OVERRIDE;
    104   virtual bool HasMenuItem() OVERRIDE;
    105   virtual int MenuItemCommandID() OVERRIDE;
    106   virtual base::string16 MenuItemLabel() OVERRIDE;
    107   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
    108   virtual bool HasBubbleView() OVERRIDE;
    109   virtual base::string16 GetBubbleViewTitle() OVERRIDE;
    110   virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
    111   virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
    112   virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
    113   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
    114   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
    115   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
    116 
    117   // content::NotificationObserver implementation.
    118   virtual void Observe(int type,
    119                        const content::NotificationSource& source,
    120                        const content::NotificationDetails& details) OVERRIDE;
    121 
    122  protected:
    123   ExtensionService* service_;
    124   const Extension* extension_;
    125   content::NotificationRegistrar registrar_;
    126 };
    127 
    128 // Shows a menu item and a global error bubble, replacing the install dialog.
    129 class ExternalInstallGlobalError : public ExternalInstallMenuAlert {
    130  public:
    131   ExternalInstallGlobalError(ExtensionService* service,
    132                              const Extension* extension,
    133                              ExternalInstallDialogDelegate* delegate,
    134                              const ExtensionInstallPrompt::Prompt& prompt);
    135   virtual ~ExternalInstallGlobalError();
    136 
    137   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
    138   virtual bool HasBubbleView() OVERRIDE;
    139   virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
    140   virtual base::string16 GetBubbleViewTitle() OVERRIDE;
    141   virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
    142   virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
    143   virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
    144   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
    145   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
    146   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
    147 
    148  protected:
    149   // Ref-counted, but needs to be disposed of if we are dismissed without
    150   // having been clicked (perhaps because the user enabled the extension
    151   // manually).
    152   ExternalInstallDialogDelegate* delegate_;
    153   const ExtensionInstallPrompt::Prompt* prompt_;
    154 };
    155 
    156 static void CreateExternalInstallGlobalError(
    157     base::WeakPtr<ExtensionService> service,
    158     const std::string& extension_id,
    159     const ExtensionInstallPrompt::ShowParams& show_params,
    160     ExtensionInstallPrompt::Delegate* prompt_delegate,
    161     const ExtensionInstallPrompt::Prompt& prompt) {
    162   if (!service.get())
    163     return;
    164   const Extension* extension = service->GetInstalledExtension(extension_id);
    165   if (!extension)
    166     return;
    167   GlobalErrorService* error_service =
    168       GlobalErrorServiceFactory::GetForProfile(service->profile());
    169   if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
    170     return;
    171 
    172   ExternalInstallDialogDelegate* delegate =
    173       static_cast<ExternalInstallDialogDelegate*>(prompt_delegate);
    174   ExternalInstallGlobalError* error_bubble = new ExternalInstallGlobalError(
    175       service.get(), extension, delegate, prompt);
    176   error_service->AddGlobalError(error_bubble);
    177   // Show bubble immediately if possible.
    178   if (delegate->browser())
    179     error_bubble->ShowBubbleView(delegate->browser());
    180 }
    181 
    182 static void ShowExternalInstallDialog(
    183     ExtensionService* service,
    184     Browser* browser,
    185     const Extension* extension) {
    186   // This object manages its own lifetime.
    187   new ExternalInstallDialogDelegate(browser, service, extension, false);
    188 }
    189 
    190 // ExternalInstallDialogDelegate --------------------------------------------
    191 
    192 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
    193     Browser* browser,
    194     ExtensionService* service,
    195     const Extension* extension,
    196     bool use_global_error)
    197     : browser_(browser),
    198       service_weak_(service->AsWeakPtr()),
    199       extension_id_(extension->id()) {
    200   AddRef();  // Balanced in Proceed or Abort.
    201 
    202   install_ui_.reset(
    203       ExtensionInstallUI::CreateInstallPromptWithBrowser(browser));
    204 
    205   const ExtensionInstallPrompt::ShowDialogCallback callback =
    206       use_global_error ?
    207       base::Bind(&CreateExternalInstallGlobalError,
    208                  service_weak_, extension_id_) :
    209       ExtensionInstallPrompt::GetDefaultShowDialogCallback();
    210   install_ui_->ConfirmExternalInstall(this, extension, callback);
    211 }
    212 
    213 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
    214 }
    215 
    216 void ExternalInstallDialogDelegate::InstallUIProceed() {
    217   if (!service_weak_.get())
    218     return;
    219   const Extension* extension =
    220       service_weak_->GetInstalledExtension(extension_id_);
    221   if (!extension)
    222     return;
    223   service_weak_->GrantPermissionsAndEnableExtension(extension);
    224   Release();
    225 }
    226 
    227 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) {
    228   if (!service_weak_.get())
    229     return;
    230   const Extension* extension =
    231       service_weak_->GetInstalledExtension(extension_id_);
    232   if (!extension)
    233     return;
    234   service_weak_->UninstallExtension(extension_id_, false, NULL);
    235   Release();
    236 }
    237 
    238 // ExternalInstallMenuAlert -------------------------------------------------
    239 
    240 ExternalInstallMenuAlert::ExternalInstallMenuAlert(
    241     ExtensionService* service,
    242     const Extension* extension)
    243     : service_(service),
    244       extension_(extension) {
    245   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
    246                  content::Source<Profile>(service->profile()));
    247   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED,
    248                  content::Source<Profile>(service->profile()));
    249 }
    250 
    251 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
    252 }
    253 
    254 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
    255   return SEVERITY_LOW;
    256 }
    257 
    258 bool ExternalInstallMenuAlert::HasMenuItem() {
    259   return true;
    260 }
    261 
    262 int ExternalInstallMenuAlert::MenuItemCommandID() {
    263   return kMenuCommandId;
    264 }
    265 
    266 base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
    267   int id = -1;
    268   if (extension_->is_app())
    269     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
    270   else if (extension_->is_theme())
    271     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
    272   else
    273     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
    274   return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name()));
    275 }
    276 
    277 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
    278   ShowExternalInstallDialog(service_, browser, extension_);
    279 }
    280 
    281 bool ExternalInstallMenuAlert::HasBubbleView() {
    282   return false;
    283 }
    284 base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() {
    285   return base::string16();
    286 }
    287 
    288 std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() {
    289   return std::vector<base::string16>();
    290 }
    291 
    292 base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
    293   return base::string16();
    294 }
    295 
    296 base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
    297   return base::string16();
    298 }
    299 
    300 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) {
    301   NOTREACHED();
    302 }
    303 
    304 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
    305     Browser* browser) {
    306   NOTREACHED();
    307 }
    308 
    309 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
    310     Browser* browser) {
    311   NOTREACHED();
    312 }
    313 
    314 void ExternalInstallMenuAlert::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   GlobalErrorService* error_service =
    325       GlobalErrorServiceFactory::GetForProfile(service_->profile());
    326   error_service->RemoveGlobalError(this);
    327   service_->AcknowledgeExternalExtension(extension_->id());
    328   delete this;
    329 }
    330 
    331 // ExternalInstallGlobalError -----------------------------------------------
    332 
    333 ExternalInstallGlobalError::ExternalInstallGlobalError(
    334     ExtensionService* service,
    335     const Extension* extension,
    336     ExternalInstallDialogDelegate* delegate,
    337     const ExtensionInstallPrompt::Prompt& prompt)
    338     : ExternalInstallMenuAlert(service, extension),
    339       delegate_(delegate),
    340       prompt_(&prompt) {
    341 }
    342 
    343 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
    344   if (delegate_)
    345     delegate_->Release();
    346 }
    347 
    348 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
    349   ShowBubbleView(browser);
    350 }
    351 
    352 bool ExternalInstallGlobalError::HasBubbleView() {
    353   return true;
    354 }
    355 
    356 gfx::Image ExternalInstallGlobalError::GetBubbleViewIcon() {
    357   if (prompt_->icon().IsEmpty())
    358     return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
    359   // Scale icon to a reasonable size.
    360   return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
    361       *prompt_->icon().ToImageSkia(),
    362       skia::ImageOperations::RESIZE_BEST,
    363       gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
    364                 extension_misc::EXTENSION_ICON_SMALL)));
    365 }
    366 
    367 base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() {
    368   return prompt_->GetDialogTitle();
    369 }
    370 
    371 std::vector<base::string16>
    372 ExternalInstallGlobalError::GetBubbleViewMessages() {
    373   std::vector<base::string16> messages;
    374   messages.push_back(prompt_->GetHeading());
    375   if (prompt_->GetPermissionCount()) {
    376     messages.push_back(prompt_->GetPermissionsHeading());
    377     for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) {
    378       messages.push_back(l10n_util::GetStringFUTF16(
    379           IDS_EXTENSION_PERMISSION_LINE,
    380           prompt_->GetPermission(i)));
    381     }
    382   }
    383   // TODO(yoz): OAuth issue advice?
    384   return messages;
    385 }
    386 
    387 base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
    388   return prompt_->GetAcceptButtonLabel();
    389 }
    390 
    391 base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
    392   return prompt_->GetAbortButtonLabel();
    393 }
    394 
    395 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) {
    396 }
    397 
    398 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
    399     Browser* browser) {
    400   ExternalInstallDialogDelegate* delegate = delegate_;
    401   delegate_ = NULL;
    402   delegate->InstallUIProceed();
    403 }
    404 
    405 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
    406     Browser* browser) {
    407   ExternalInstallDialogDelegate* delegate = delegate_;
    408   delegate_ = NULL;
    409   delegate->InstallUIAbort(true);
    410 }
    411 
    412 // Public interface ---------------------------------------------------------
    413 
    414 void AddExternalInstallError(ExtensionService* service,
    415                              const Extension* extension,
    416                              bool is_new_profile) {
    417   GlobalErrorService* error_service =
    418       GlobalErrorServiceFactory::GetForProfile(service->profile());
    419   if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
    420     return;
    421 
    422   if (UseBubbleInstall(extension, is_new_profile)) {
    423     Browser* browser = NULL;
    424 #if !defined(OS_ANDROID)
    425     browser = chrome::FindTabbedBrowser(service->profile(),
    426                                         true,
    427                                         chrome::GetActiveDesktop());
    428 #endif
    429     new ExternalInstallDialogDelegate(browser, service, extension, true);
    430   } else {
    431     error_service->AddGlobalError(
    432         new ExternalInstallMenuAlert(service, extension));
    433   }
    434 }
    435 
    436 void RemoveExternalInstallError(ExtensionService* service) {
    437   GlobalErrorService* error_service =
    438       GlobalErrorServiceFactory::GetForProfile(service->profile());
    439   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
    440       kMenuCommandId);
    441   if (error) {
    442     error_service->RemoveGlobalError(error);
    443     delete error;
    444   }
    445 }
    446 
    447 bool HasExternalInstallError(ExtensionService* service) {
    448   GlobalErrorService* error_service =
    449       GlobalErrorServiceFactory::GetForProfile(service->profile());
    450   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
    451       kMenuCommandId);
    452   return !!error;
    453 }
    454 
    455 bool HasExternalInstallBubble(ExtensionService* service) {
    456   GlobalErrorService* error_service =
    457       GlobalErrorServiceFactory::GetForProfile(service->profile());
    458   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
    459       kMenuCommandId);
    460   return error && error->HasBubbleView();
    461 }
    462 
    463 }  // namespace extensions
    464