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