Home | History | Annotate | Download | only in extensions
      1 // Copyright 2014 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_error.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/app/chrome_command_ids.h"
     10 #include "chrome/browser/extensions/extension_service.h"
     11 #include "chrome/browser/extensions/external_install_manager.h"
     12 #include "chrome/browser/extensions/webstore_data_fetcher.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/ui/browser.h"
     15 #include "chrome/browser/ui/browser_finder.h"
     16 #include "chrome/browser/ui/global_error/global_error.h"
     17 #include "chrome/browser/ui/global_error/global_error_service.h"
     18 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
     19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     20 #include "chrome/grit/generated_resources.h"
     21 #include "extensions/browser/extension_registry.h"
     22 #include "extensions/browser/extension_system.h"
     23 #include "extensions/browser/uninstall_reason.h"
     24 #include "extensions/common/constants.h"
     25 #include "extensions/common/extension.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 #include "ui/gfx/image/image.h"
     28 #include "ui/gfx/image/image_skia_operations.h"
     29 
     30 namespace extensions {
     31 
     32 namespace {
     33 
     34 // Return the menu label for a global error.
     35 base::string16 GetMenuItemLabel(const Extension* extension) {
     36   if (!extension)
     37     return base::string16();
     38 
     39   int id = -1;
     40   if (extension->is_app())
     41     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
     42   else if (extension->is_theme())
     43     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
     44   else
     45     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
     46 
     47   return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension->name()));
     48 }
     49 
     50 // A global error that spawns a dialog when the menu item is clicked.
     51 class ExternalInstallMenuAlert : public GlobalError {
     52  public:
     53   explicit ExternalInstallMenuAlert(ExternalInstallError* error);
     54   virtual ~ExternalInstallMenuAlert();
     55 
     56  private:
     57   // GlobalError implementation.
     58   virtual Severity GetSeverity() OVERRIDE;
     59   virtual bool HasMenuItem() OVERRIDE;
     60   virtual int MenuItemCommandID() OVERRIDE;
     61   virtual base::string16 MenuItemLabel() OVERRIDE;
     62   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
     63   virtual bool HasBubbleView() OVERRIDE;
     64   virtual bool HasShownBubbleView() OVERRIDE;
     65   virtual void ShowBubbleView(Browser* browser) OVERRIDE;
     66   virtual GlobalErrorBubbleViewBase* GetBubbleView() OVERRIDE;
     67 
     68   // The owning ExternalInstallError.
     69   ExternalInstallError* error_;
     70 
     71   DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
     72 };
     73 
     74 // A global error that spawns a bubble when the menu item is clicked.
     75 class ExternalInstallBubbleAlert : public GlobalErrorWithStandardBubble {
     76  public:
     77   explicit ExternalInstallBubbleAlert(ExternalInstallError* error,
     78                                       ExtensionInstallPrompt::Prompt* prompt);
     79   virtual ~ExternalInstallBubbleAlert();
     80 
     81  private:
     82   // GlobalError implementation.
     83   virtual Severity GetSeverity() OVERRIDE;
     84   virtual bool HasMenuItem() OVERRIDE;
     85   virtual int MenuItemCommandID() OVERRIDE;
     86   virtual base::string16 MenuItemLabel() OVERRIDE;
     87   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
     88 
     89   // GlobalErrorWithStandardBubble implementation.
     90   virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
     91   virtual base::string16 GetBubbleViewTitle() OVERRIDE;
     92   virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
     93   virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
     94   virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
     95   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
     96   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
     97   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
     98 
     99   // The owning ExternalInstallError.
    100   ExternalInstallError* error_;
    101 
    102   // The Prompt with all information, which we then use to populate the bubble.
    103   ExtensionInstallPrompt::Prompt* prompt_;
    104 
    105   DISALLOW_COPY_AND_ASSIGN(ExternalInstallBubbleAlert);
    106 };
    107 
    108 ////////////////////////////////////////////////////////////////////////////////
    109 // ExternalInstallMenuAlert
    110 
    111 ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExternalInstallError* error)
    112     : error_(error) {
    113 }
    114 
    115 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
    116 }
    117 
    118 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
    119   return SEVERITY_LOW;
    120 }
    121 
    122 bool ExternalInstallMenuAlert::HasMenuItem() {
    123   return true;
    124 }
    125 
    126 int ExternalInstallMenuAlert::MenuItemCommandID() {
    127   return IDC_EXTERNAL_EXTENSION_ALERT;
    128 }
    129 
    130 base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
    131   return GetMenuItemLabel(error_->GetExtension());
    132 }
    133 
    134 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
    135   error_->ShowDialog(browser);
    136 }
    137 
    138 bool ExternalInstallMenuAlert::HasBubbleView() {
    139   return false;
    140 }
    141 
    142 bool ExternalInstallMenuAlert::HasShownBubbleView() {
    143   NOTREACHED();
    144   return true;
    145 }
    146 
    147 void ExternalInstallMenuAlert::ShowBubbleView(Browser* browser) {
    148   NOTREACHED();
    149 }
    150 
    151 GlobalErrorBubbleViewBase* ExternalInstallMenuAlert::GetBubbleView() {
    152   return NULL;
    153 }
    154 
    155 ////////////////////////////////////////////////////////////////////////////////
    156 // ExternalInstallBubbleAlert
    157 
    158 ExternalInstallBubbleAlert::ExternalInstallBubbleAlert(
    159     ExternalInstallError* error,
    160     ExtensionInstallPrompt::Prompt* prompt)
    161     : error_(error), prompt_(prompt) {
    162   DCHECK(error_);
    163   DCHECK(prompt_);
    164 }
    165 
    166 ExternalInstallBubbleAlert::~ExternalInstallBubbleAlert() {
    167 }
    168 
    169 GlobalError::Severity ExternalInstallBubbleAlert::GetSeverity() {
    170   return SEVERITY_LOW;
    171 }
    172 
    173 bool ExternalInstallBubbleAlert::HasMenuItem() {
    174   return true;
    175 }
    176 
    177 int ExternalInstallBubbleAlert::MenuItemCommandID() {
    178   return IDC_EXTERNAL_EXTENSION_ALERT;
    179 }
    180 
    181 base::string16 ExternalInstallBubbleAlert::MenuItemLabel() {
    182   return GetMenuItemLabel(error_->GetExtension());
    183 }
    184 
    185 void ExternalInstallBubbleAlert::ExecuteMenuItem(Browser* browser) {
    186   ShowBubbleView(browser);
    187 }
    188 
    189 gfx::Image ExternalInstallBubbleAlert::GetBubbleViewIcon() {
    190   if (prompt_->icon().IsEmpty())
    191     return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
    192   // Scale icon to a reasonable size.
    193   return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
    194       *prompt_->icon().ToImageSkia(),
    195       skia::ImageOperations::RESIZE_BEST,
    196       gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
    197                 extension_misc::EXTENSION_ICON_SMALL)));
    198 }
    199 
    200 base::string16 ExternalInstallBubbleAlert::GetBubbleViewTitle() {
    201   return prompt_->GetDialogTitle();
    202 }
    203 
    204 std::vector<base::string16>
    205 ExternalInstallBubbleAlert::GetBubbleViewMessages() {
    206   ExtensionInstallPrompt::PermissionsType regular_permissions =
    207       ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS;
    208   ExtensionInstallPrompt::PermissionsType withheld_permissions =
    209       ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS;
    210 
    211   std::vector<base::string16> messages;
    212   messages.push_back(prompt_->GetHeading());
    213   if (prompt_->GetPermissionCount(regular_permissions)) {
    214     messages.push_back(prompt_->GetPermissionsHeading(regular_permissions));
    215     for (size_t i = 0; i < prompt_->GetPermissionCount(regular_permissions);
    216          ++i) {
    217       messages.push_back(l10n_util::GetStringFUTF16(
    218           IDS_EXTENSION_PERMISSION_LINE,
    219           prompt_->GetPermission(i, regular_permissions)));
    220     }
    221   }
    222   if (prompt_->GetPermissionCount(withheld_permissions)) {
    223     messages.push_back(prompt_->GetPermissionsHeading(withheld_permissions));
    224     for (size_t i = 0; i < prompt_->GetPermissionCount(withheld_permissions);
    225          ++i) {
    226       messages.push_back(l10n_util::GetStringFUTF16(
    227           IDS_EXTENSION_PERMISSION_LINE,
    228           prompt_->GetPermission(i, withheld_permissions)));
    229     }
    230   }
    231   // TODO(yoz): OAuth issue advice?
    232   return messages;
    233 }
    234 
    235 base::string16 ExternalInstallBubbleAlert::GetBubbleViewAcceptButtonLabel() {
    236   return prompt_->GetAcceptButtonLabel();
    237 }
    238 
    239 base::string16 ExternalInstallBubbleAlert::GetBubbleViewCancelButtonLabel() {
    240   return prompt_->GetAbortButtonLabel();
    241 }
    242 
    243 void ExternalInstallBubbleAlert::OnBubbleViewDidClose(Browser* browser) {
    244 }
    245 
    246 void ExternalInstallBubbleAlert::BubbleViewAcceptButtonPressed(
    247     Browser* browser) {
    248   error_->InstallUIProceed();
    249 }
    250 
    251 void ExternalInstallBubbleAlert::BubbleViewCancelButtonPressed(
    252     Browser* browser) {
    253   error_->InstallUIAbort(true);
    254 }
    255 
    256 }  // namespace
    257 
    258 ////////////////////////////////////////////////////////////////////////////////
    259 // ExternalInstallError
    260 
    261 ExternalInstallError::ExternalInstallError(
    262     content::BrowserContext* browser_context,
    263     const std::string& extension_id,
    264     AlertType alert_type,
    265     ExternalInstallManager* manager)
    266     : browser_context_(browser_context),
    267       extension_id_(extension_id),
    268       alert_type_(alert_type),
    269       manager_(manager),
    270       error_service_(GlobalErrorServiceFactory::GetForProfile(
    271           Profile::FromBrowserContext(browser_context_))),
    272       weak_factory_(this) {
    273   prompt_ = new ExtensionInstallPrompt::Prompt(
    274       ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT);
    275 
    276   webstore_data_fetcher_.reset(new WebstoreDataFetcher(
    277       this, browser_context_->GetRequestContext(), GURL(), extension_id_));
    278   webstore_data_fetcher_->Start();
    279 }
    280 
    281 ExternalInstallError::~ExternalInstallError() {
    282   if (global_error_.get())
    283     error_service_->RemoveGlobalError(global_error_.get());
    284 }
    285 
    286 void ExternalInstallError::InstallUIProceed() {
    287   const Extension* extension = GetExtension();
    288   if (extension) {
    289     ExtensionSystem::Get(browser_context_)
    290         ->extension_service()
    291         ->GrantPermissionsAndEnableExtension(extension);
    292     // Since the manager listens for the extension to be loaded, this will
    293     // remove the error...
    294   } else {
    295     // ... Otherwise we have to do it explicitly.
    296     manager_->RemoveExternalInstallError();
    297   }
    298 }
    299 
    300 void ExternalInstallError::InstallUIAbort(bool user_initiated) {
    301   if (user_initiated && GetExtension()) {
    302     ExtensionSystem::Get(browser_context_)
    303         ->extension_service()
    304         ->UninstallExtension(extension_id_,
    305                              extensions::UNINSTALL_REASON_INSTALL_CANCELED,
    306                              base::Bind(&base::DoNothing),
    307                              NULL);  // Ignore error.
    308     // Since the manager listens for the extension to be removed, this will
    309     // remove the error...
    310   } else {
    311     // ... Otherwise we have to do it explicitly.
    312     manager_->RemoveExternalInstallError();
    313   }
    314 }
    315 
    316 void ExternalInstallError::ShowDialog(Browser* browser) {
    317   DCHECK(install_ui_.get());
    318   DCHECK(prompt_.get());
    319   DCHECK(browser);
    320   content::WebContents* web_contents = NULL;
    321   web_contents = browser->tab_strip_model()->GetActiveWebContents();
    322   ExtensionInstallPrompt::ShowParams params(web_contents);
    323   ExtensionInstallPrompt::GetDefaultShowDialogCallback().Run(
    324       params, this, prompt_);
    325 }
    326 
    327 const Extension* ExternalInstallError::GetExtension() const {
    328   return ExtensionRegistry::Get(browser_context_)
    329       ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
    330 }
    331 
    332 void ExternalInstallError::OnWebstoreRequestFailure() {
    333   OnFetchComplete();
    334 }
    335 
    336 void ExternalInstallError::OnWebstoreResponseParseSuccess(
    337     scoped_ptr<base::DictionaryValue> webstore_data) {
    338   std::string localized_user_count;
    339   double average_rating = 0;
    340   int rating_count = 0;
    341   if (!webstore_data->GetString(kUsersKey, &localized_user_count) ||
    342       !webstore_data->GetDouble(kAverageRatingKey, &average_rating) ||
    343       !webstore_data->GetInteger(kRatingCountKey, &rating_count)) {
    344     // If we don't get a valid webstore response, short circuit, and continue
    345     // to show a prompt without webstore data.
    346     OnFetchComplete();
    347     return;
    348   }
    349 
    350   bool show_user_count = true;
    351   webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
    352 
    353   prompt_->SetWebstoreData(
    354       localized_user_count, show_user_count, average_rating, rating_count);
    355   OnFetchComplete();
    356 }
    357 
    358 void ExternalInstallError::OnWebstoreResponseParseFailure(
    359     const std::string& error) {
    360   OnFetchComplete();
    361 }
    362 
    363 void ExternalInstallError::OnFetchComplete() {
    364   // Create a new ExtensionInstallPrompt. We pass in NULL for the UI
    365   // components because we display at a later point, and don't want
    366   // to pass ones which may be invalidated.
    367   install_ui_.reset(
    368       new ExtensionInstallPrompt(Profile::FromBrowserContext(browser_context_),
    369                                  NULL,    // NULL native window.
    370                                  NULL));  // NULL navigator.
    371 
    372   install_ui_->ConfirmExternalInstall(
    373       this,
    374       GetExtension(),
    375       base::Bind(&ExternalInstallError::OnDialogReady,
    376                  weak_factory_.GetWeakPtr()),
    377       prompt_);
    378 }
    379 
    380 void ExternalInstallError::OnDialogReady(
    381     const ExtensionInstallPrompt::ShowParams& show_params,
    382     ExtensionInstallPrompt::Delegate* prompt_delegate,
    383     scoped_refptr<ExtensionInstallPrompt::Prompt> prompt) {
    384   DCHECK_EQ(this, prompt_delegate);
    385   prompt_ = prompt;
    386 
    387   if (alert_type_ == BUBBLE_ALERT) {
    388     global_error_.reset(new ExternalInstallBubbleAlert(this, prompt_.get()));
    389     error_service_->AddGlobalError(global_error_.get());
    390 
    391     Browser* browser =
    392         chrome::FindTabbedBrowser(Profile::FromBrowserContext(browser_context_),
    393                                   true,
    394                                   chrome::GetActiveDesktop());
    395     if (browser)
    396       global_error_->ShowBubbleView(browser);
    397   } else {
    398     DCHECK(alert_type_ == MENU_ALERT);
    399     global_error_.reset(new ExternalInstallMenuAlert(this));
    400     error_service_->AddGlobalError(global_error_.get());
    401   }
    402 }
    403 
    404 }  // namespace extensions
    405