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