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/bundle_installer.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/command_line.h"
     12 #include "base/i18n/rtl.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/extensions/crx_installer.h"
     16 #include "chrome/browser/extensions/permissions_updater.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/ui/browser.h"
     19 #include "chrome/browser/ui/browser_finder.h"
     20 #include "chrome/browser/ui/browser_list.h"
     21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     22 #include "chrome/common/chrome_switches.h"
     23 #include "chrome/grit/generated_resources.h"
     24 #include "content/public/browser/web_contents.h"
     25 #include "extensions/common/extension.h"
     26 #include "extensions/common/permissions/permission_set.h"
     27 #include "extensions/common/permissions/permissions_data.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 
     30 namespace extensions {
     31 
     32 namespace {
     33 
     34 enum AutoApproveForTest {
     35   DO_NOT_SKIP = 0,
     36   PROCEED,
     37   ABORT
     38 };
     39 
     40 AutoApproveForTest g_auto_approve_for_test = DO_NOT_SKIP;
     41 
     42 // Creates a dummy extension and sets the manifest's name to the item's
     43 // localized name.
     44 scoped_refptr<Extension> CreateDummyExtension(
     45     const BundleInstaller::Item& item,
     46     base::DictionaryValue* manifest,
     47     content::BrowserContext* browser_context) {
     48   // We require localized names so we can have nice error messages when we can't
     49   // parse an extension manifest.
     50   CHECK(!item.localized_name.empty());
     51 
     52   std::string error;
     53   scoped_refptr<Extension> extension = Extension::Create(base::FilePath(),
     54                                                          Manifest::INTERNAL,
     55                                                          *manifest,
     56                                                          Extension::NO_FLAGS,
     57                                                          item.id,
     58                                                          &error);
     59   // Initialize permissions so that withheld permissions are displayed properly
     60   // in the install prompt.
     61   PermissionsUpdater(browser_context, PermissionsUpdater::INIT_FLAG_TRANSIENT)
     62       .InitializePermissions(extension.get());
     63   return extension;
     64 }
     65 
     66 bool IsAppPredicate(scoped_refptr<const Extension> extension) {
     67   return extension->is_app();
     68 }
     69 
     70 struct MatchIdFunctor {
     71   explicit MatchIdFunctor(const std::string& id) : id(id) {}
     72   bool operator()(scoped_refptr<const Extension> extension) {
     73     return extension->id() == id;
     74   }
     75   std::string id;
     76 };
     77 
     78 // Holds the message IDs for BundleInstaller::GetHeadingTextFor.
     79 const int kHeadingIds[3][4] = {
     80   {
     81     0,
     82     IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSIONS,
     83     IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_APPS,
     84     IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSION_APPS
     85   },
     86   {
     87     0,
     88     IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSIONS,
     89     IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_APPS,
     90     IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSION_APPS
     91   }
     92 };
     93 
     94 }  // namespace
     95 
     96 // static
     97 void BundleInstaller::SetAutoApproveForTesting(bool auto_approve) {
     98   CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType));
     99   g_auto_approve_for_test = auto_approve ? PROCEED : ABORT;
    100 }
    101 
    102 BundleInstaller::Item::Item() : state(STATE_PENDING) {}
    103 
    104 base::string16 BundleInstaller::Item::GetNameForDisplay() {
    105   base::string16 name = base::UTF8ToUTF16(localized_name);
    106   base::i18n::AdjustStringForLocaleDirection(&name);
    107   return l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE, name);
    108 }
    109 
    110 BundleInstaller::BundleInstaller(Browser* browser,
    111                                  const BundleInstaller::ItemList& items)
    112     : approved_(false),
    113       browser_(browser),
    114       host_desktop_type_(browser->host_desktop_type()),
    115       profile_(browser->profile()),
    116       delegate_(NULL) {
    117   BrowserList::AddObserver(this);
    118   for (size_t i = 0; i < items.size(); ++i) {
    119     items_[items[i].id] = items[i];
    120     items_[items[i].id].state = Item::STATE_PENDING;
    121   }
    122 }
    123 
    124 BundleInstaller::ItemList BundleInstaller::GetItemsWithState(
    125     Item::State state) const {
    126   ItemList list;
    127 
    128   for (ItemMap::const_iterator i = items_.begin(); i != items_.end(); ++i) {
    129     if (i->second.state == state)
    130       list.push_back(i->second);
    131   }
    132 
    133   return list;
    134 }
    135 
    136 void BundleInstaller::PromptForApproval(Delegate* delegate) {
    137   delegate_ = delegate;
    138 
    139   AddRef();  // Balanced in ReportApproved() and ReportCanceled().
    140 
    141   ParseManifests();
    142 }
    143 
    144 void BundleInstaller::CompleteInstall(content::WebContents* web_contents,
    145                                       Delegate* delegate) {
    146   CHECK(approved_);
    147 
    148   delegate_ = delegate;
    149 
    150   AddRef();  // Balanced in ReportComplete();
    151 
    152   if (GetItemsWithState(Item::STATE_PENDING).empty()) {
    153     ReportComplete();
    154     return;
    155   }
    156 
    157   // Start each WebstoreInstaller.
    158   for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) {
    159     if (i->second.state != Item::STATE_PENDING)
    160       continue;
    161 
    162     // Since we've already confirmed the permissions, create an approval that
    163     // lets CrxInstaller bypass the prompt.
    164     scoped_ptr<WebstoreInstaller::Approval> approval(
    165         WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
    166             profile_,
    167             i->first,
    168             scoped_ptr<base::DictionaryValue>(
    169                 parsed_manifests_[i->first]->DeepCopy()), true));
    170     approval->use_app_installed_bubble = false;
    171     approval->skip_post_install_ui = true;
    172 
    173     scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
    174         profile_,
    175         this,
    176         web_contents,
    177         i->first,
    178         approval.Pass(),
    179         WebstoreInstaller::INSTALL_SOURCE_OTHER);
    180     installer->Start();
    181   }
    182 }
    183 
    184 base::string16 BundleInstaller::GetHeadingTextFor(Item::State state) const {
    185   // For STATE_FAILED, we can't tell if the items were apps or extensions
    186   // so we always show the same message.
    187   if (state == Item::STATE_FAILED) {
    188     if (GetItemsWithState(state).size())
    189       return l10n_util::GetStringUTF16(IDS_EXTENSION_BUNDLE_ERROR_HEADING);
    190     return base::string16();
    191   }
    192 
    193   size_t total = GetItemsWithState(state).size();
    194   size_t apps = std::count_if(
    195       dummy_extensions_.begin(), dummy_extensions_.end(), &IsAppPredicate);
    196 
    197   bool has_apps = apps > 0;
    198   bool has_extensions = apps < total;
    199   size_t index = (has_extensions << 0) + (has_apps << 1);
    200 
    201   CHECK_LT(static_cast<size_t>(state), arraysize(kHeadingIds));
    202   CHECK_LT(index, arraysize(kHeadingIds[state]));
    203 
    204   int msg_id = kHeadingIds[state][index];
    205   if (!msg_id)
    206     return base::string16();
    207 
    208   return l10n_util::GetStringUTF16(msg_id);
    209 }
    210 
    211 BundleInstaller::~BundleInstaller() {
    212   BrowserList::RemoveObserver(this);
    213 }
    214 
    215 void BundleInstaller::ParseManifests() {
    216   if (items_.empty()) {
    217     ReportCanceled(false);
    218     return;
    219   }
    220 
    221   for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) {
    222     scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
    223         this, i->first, i->second.manifest, std::string(), GURL(), NULL);
    224     helper->Start();
    225   }
    226 }
    227 
    228 void BundleInstaller::ReportApproved() {
    229   if (delegate_)
    230     delegate_->OnBundleInstallApproved();
    231 
    232   Release();  // Balanced in PromptForApproval().
    233 }
    234 
    235 void BundleInstaller::ReportCanceled(bool user_initiated) {
    236   if (delegate_)
    237     delegate_->OnBundleInstallCanceled(user_initiated);
    238 
    239   Release();  // Balanced in PromptForApproval().
    240 }
    241 
    242 void BundleInstaller::ReportComplete() {
    243   if (delegate_)
    244     delegate_->OnBundleInstallCompleted();
    245 
    246   Release();  // Balanced in CompleteInstall().
    247 }
    248 
    249 void BundleInstaller::ShowPromptIfDoneParsing() {
    250   // We don't prompt until all the manifests have been parsed.
    251   ItemList pending_items = GetItemsWithState(Item::STATE_PENDING);
    252   if (pending_items.size() != dummy_extensions_.size())
    253     return;
    254 
    255   ShowPrompt();
    256 }
    257 
    258 void BundleInstaller::ShowPrompt() {
    259   // Abort if we couldn't create any Extensions out of the manifests.
    260   if (dummy_extensions_.empty()) {
    261     ReportCanceled(false);
    262     return;
    263   }
    264 
    265   scoped_refptr<PermissionSet> permissions;
    266   for (size_t i = 0; i < dummy_extensions_.size(); ++i) {
    267     permissions = PermissionSet::CreateUnion(
    268         permissions.get(),
    269         dummy_extensions_[i]->permissions_data()->active_permissions().get());
    270   }
    271 
    272   if (g_auto_approve_for_test == PROCEED) {
    273     InstallUIProceed();
    274   } else if (g_auto_approve_for_test == ABORT) {
    275     InstallUIAbort(true);
    276   } else {
    277     Browser* browser = browser_;
    278     if (!browser) {
    279       // The browser that we got initially could have gone away during our
    280       // thread hopping.
    281       browser = chrome::FindLastActiveWithProfile(profile_, host_desktop_type_);
    282     }
    283     content::WebContents* web_contents = NULL;
    284     if (browser)
    285       web_contents = browser->tab_strip_model()->GetActiveWebContents();
    286     install_ui_.reset(new ExtensionInstallPrompt(web_contents));
    287     install_ui_->ConfirmBundleInstall(this, permissions.get());
    288   }
    289 }
    290 
    291 void BundleInstaller::ShowInstalledBubbleIfDone() {
    292   // We're ready to show the installed bubble when no items are pending.
    293   if (!GetItemsWithState(Item::STATE_PENDING).empty())
    294     return;
    295 
    296   if (browser_)
    297     ShowInstalledBubble(this, browser_);
    298 
    299   ReportComplete();
    300 }
    301 
    302 void BundleInstaller::OnWebstoreParseSuccess(
    303     const std::string& id,
    304     const SkBitmap& icon,
    305     base::DictionaryValue* manifest) {
    306   dummy_extensions_.push_back(
    307       CreateDummyExtension(items_[id], manifest, profile_));
    308   parsed_manifests_[id] = linked_ptr<base::DictionaryValue>(manifest);
    309 
    310   ShowPromptIfDoneParsing();
    311 }
    312 
    313 void BundleInstaller::OnWebstoreParseFailure(
    314     const std::string& id,
    315     WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,
    316     const std::string& error_message) {
    317   items_[id].state = Item::STATE_FAILED;
    318 
    319   ShowPromptIfDoneParsing();
    320 }
    321 
    322 void BundleInstaller::InstallUIProceed() {
    323   approved_ = true;
    324   ReportApproved();
    325 }
    326 
    327 void BundleInstaller::InstallUIAbort(bool user_initiated) {
    328   for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i)
    329     i->second.state = Item::STATE_FAILED;
    330 
    331   ReportCanceled(user_initiated);
    332 }
    333 
    334 void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) {
    335   items_[id].state = Item::STATE_INSTALLED;
    336 
    337   ShowInstalledBubbleIfDone();
    338 }
    339 
    340 void BundleInstaller::OnExtensionInstallFailure(
    341     const std::string& id,
    342     const std::string& error,
    343     WebstoreInstaller::FailureReason reason) {
    344   items_[id].state = Item::STATE_FAILED;
    345 
    346   ExtensionList::iterator i = std::find_if(
    347       dummy_extensions_.begin(), dummy_extensions_.end(), MatchIdFunctor(id));
    348   CHECK(dummy_extensions_.end() != i);
    349   dummy_extensions_.erase(i);
    350 
    351   ShowInstalledBubbleIfDone();
    352 }
    353 
    354 void BundleInstaller::OnBrowserAdded(Browser* browser) {}
    355 
    356 void BundleInstaller::OnBrowserRemoved(Browser* browser) {
    357   if (browser_ == browser)
    358     browser_ = NULL;
    359 }
    360 
    361 void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {}
    362 
    363 }  // namespace extensions
    364