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