// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/bundle_installer.h" #include #include #include #include "base/command_line.h" #include "base/i18n/rtl.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/permissions_updater.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/grit/generated_resources.h" #include "content/public/browser/web_contents.h" #include "extensions/common/extension.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" #include "ui/base/l10n/l10n_util.h" namespace extensions { namespace { enum AutoApproveForTest { DO_NOT_SKIP = 0, PROCEED, ABORT }; AutoApproveForTest g_auto_approve_for_test = DO_NOT_SKIP; // Creates a dummy extension and sets the manifest's name to the item's // localized name. scoped_refptr CreateDummyExtension( const BundleInstaller::Item& item, base::DictionaryValue* manifest, content::BrowserContext* browser_context) { // We require localized names so we can have nice error messages when we can't // parse an extension manifest. CHECK(!item.localized_name.empty()); std::string error; scoped_refptr extension = Extension::Create(base::FilePath(), Manifest::INTERNAL, *manifest, Extension::NO_FLAGS, item.id, &error); // Initialize permissions so that withheld permissions are displayed properly // in the install prompt. PermissionsUpdater(browser_context, PermissionsUpdater::INIT_FLAG_TRANSIENT) .InitializePermissions(extension.get()); return extension; } bool IsAppPredicate(scoped_refptr extension) { return extension->is_app(); } struct MatchIdFunctor { explicit MatchIdFunctor(const std::string& id) : id(id) {} bool operator()(scoped_refptr extension) { return extension->id() == id; } std::string id; }; // Holds the message IDs for BundleInstaller::GetHeadingTextFor. const int kHeadingIds[3][4] = { { 0, IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSIONS, IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_APPS, IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSION_APPS }, { 0, IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSIONS, IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_APPS, IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSION_APPS } }; } // namespace // static void BundleInstaller::SetAutoApproveForTesting(bool auto_approve) { CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); g_auto_approve_for_test = auto_approve ? PROCEED : ABORT; } BundleInstaller::Item::Item() : state(STATE_PENDING) {} base::string16 BundleInstaller::Item::GetNameForDisplay() { base::string16 name = base::UTF8ToUTF16(localized_name); base::i18n::AdjustStringForLocaleDirection(&name); return l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE, name); } BundleInstaller::BundleInstaller(Browser* browser, const BundleInstaller::ItemList& items) : approved_(false), browser_(browser), host_desktop_type_(browser->host_desktop_type()), profile_(browser->profile()), delegate_(NULL) { BrowserList::AddObserver(this); for (size_t i = 0; i < items.size(); ++i) { items_[items[i].id] = items[i]; items_[items[i].id].state = Item::STATE_PENDING; } } BundleInstaller::ItemList BundleInstaller::GetItemsWithState( Item::State state) const { ItemList list; for (ItemMap::const_iterator i = items_.begin(); i != items_.end(); ++i) { if (i->second.state == state) list.push_back(i->second); } return list; } void BundleInstaller::PromptForApproval(Delegate* delegate) { delegate_ = delegate; AddRef(); // Balanced in ReportApproved() and ReportCanceled(). ParseManifests(); } void BundleInstaller::CompleteInstall(content::WebContents* web_contents, Delegate* delegate) { CHECK(approved_); delegate_ = delegate; AddRef(); // Balanced in ReportComplete(); if (GetItemsWithState(Item::STATE_PENDING).empty()) { ReportComplete(); return; } // Start each WebstoreInstaller. for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) { if (i->second.state != Item::STATE_PENDING) continue; // Since we've already confirmed the permissions, create an approval that // lets CrxInstaller bypass the prompt. scoped_ptr approval( WebstoreInstaller::Approval::CreateWithNoInstallPrompt( profile_, i->first, scoped_ptr( parsed_manifests_[i->first]->DeepCopy()), true)); approval->use_app_installed_bubble = false; approval->skip_post_install_ui = true; scoped_refptr installer = new WebstoreInstaller( profile_, this, web_contents, i->first, approval.Pass(), WebstoreInstaller::INSTALL_SOURCE_OTHER); installer->Start(); } } base::string16 BundleInstaller::GetHeadingTextFor(Item::State state) const { // For STATE_FAILED, we can't tell if the items were apps or extensions // so we always show the same message. if (state == Item::STATE_FAILED) { if (GetItemsWithState(state).size()) return l10n_util::GetStringUTF16(IDS_EXTENSION_BUNDLE_ERROR_HEADING); return base::string16(); } size_t total = GetItemsWithState(state).size(); size_t apps = std::count_if( dummy_extensions_.begin(), dummy_extensions_.end(), &IsAppPredicate); bool has_apps = apps > 0; bool has_extensions = apps < total; size_t index = (has_extensions << 0) + (has_apps << 1); CHECK_LT(static_cast(state), arraysize(kHeadingIds)); CHECK_LT(index, arraysize(kHeadingIds[state])); int msg_id = kHeadingIds[state][index]; if (!msg_id) return base::string16(); return l10n_util::GetStringUTF16(msg_id); } BundleInstaller::~BundleInstaller() { BrowserList::RemoveObserver(this); } void BundleInstaller::ParseManifests() { if (items_.empty()) { ReportCanceled(false); return; } for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) { scoped_refptr helper = new WebstoreInstallHelper( this, i->first, i->second.manifest, std::string(), GURL(), NULL); helper->Start(); } } void BundleInstaller::ReportApproved() { if (delegate_) delegate_->OnBundleInstallApproved(); Release(); // Balanced in PromptForApproval(). } void BundleInstaller::ReportCanceled(bool user_initiated) { if (delegate_) delegate_->OnBundleInstallCanceled(user_initiated); Release(); // Balanced in PromptForApproval(). } void BundleInstaller::ReportComplete() { if (delegate_) delegate_->OnBundleInstallCompleted(); Release(); // Balanced in CompleteInstall(). } void BundleInstaller::ShowPromptIfDoneParsing() { // We don't prompt until all the manifests have been parsed. ItemList pending_items = GetItemsWithState(Item::STATE_PENDING); if (pending_items.size() != dummy_extensions_.size()) return; ShowPrompt(); } void BundleInstaller::ShowPrompt() { // Abort if we couldn't create any Extensions out of the manifests. if (dummy_extensions_.empty()) { ReportCanceled(false); return; } scoped_refptr permissions; for (size_t i = 0; i < dummy_extensions_.size(); ++i) { permissions = PermissionSet::CreateUnion( permissions.get(), dummy_extensions_[i]->permissions_data()->active_permissions().get()); } if (g_auto_approve_for_test == PROCEED) { InstallUIProceed(); } else if (g_auto_approve_for_test == ABORT) { InstallUIAbort(true); } else { Browser* browser = browser_; if (!browser) { // The browser that we got initially could have gone away during our // thread hopping. browser = chrome::FindLastActiveWithProfile(profile_, host_desktop_type_); } content::WebContents* web_contents = NULL; if (browser) web_contents = browser->tab_strip_model()->GetActiveWebContents(); install_ui_.reset(new ExtensionInstallPrompt(web_contents)); install_ui_->ConfirmBundleInstall(this, permissions.get()); } } void BundleInstaller::ShowInstalledBubbleIfDone() { // We're ready to show the installed bubble when no items are pending. if (!GetItemsWithState(Item::STATE_PENDING).empty()) return; if (browser_) ShowInstalledBubble(this, browser_); ReportComplete(); } void BundleInstaller::OnWebstoreParseSuccess( const std::string& id, const SkBitmap& icon, base::DictionaryValue* manifest) { dummy_extensions_.push_back( CreateDummyExtension(items_[id], manifest, profile_)); parsed_manifests_[id] = linked_ptr(manifest); ShowPromptIfDoneParsing(); } void BundleInstaller::OnWebstoreParseFailure( const std::string& id, WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code, const std::string& error_message) { items_[id].state = Item::STATE_FAILED; ShowPromptIfDoneParsing(); } void BundleInstaller::InstallUIProceed() { approved_ = true; ReportApproved(); } void BundleInstaller::InstallUIAbort(bool user_initiated) { for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) i->second.state = Item::STATE_FAILED; ReportCanceled(user_initiated); } void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) { items_[id].state = Item::STATE_INSTALLED; ShowInstalledBubbleIfDone(); } void BundleInstaller::OnExtensionInstallFailure( const std::string& id, const std::string& error, WebstoreInstaller::FailureReason reason) { items_[id].state = Item::STATE_FAILED; ExtensionList::iterator i = std::find_if( dummy_extensions_.begin(), dummy_extensions_.end(), MatchIdFunctor(id)); CHECK(dummy_extensions_.end() != i); dummy_extensions_.erase(i); ShowInstalledBubbleIfDone(); } void BundleInstaller::OnBrowserAdded(Browser* browser) {} void BundleInstaller::OnBrowserRemoved(Browser* browser) { if (browser_ == browser) browser_ = NULL; } void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {} } // namespace extensions