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 30using content::NavigationController; 31 32namespace extensions { 33 34namespace { 35 36enum AutoApproveForTest { 37 DO_NOT_SKIP = 0, 38 PROCEED, 39 ABORT 40}; 41 42AutoApproveForTest 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. 46scoped_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 61bool IsAppPredicate(scoped_refptr<const Extension> extension) { 62 return extension->is_app(); 63} 64 65struct 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. 74const 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 92void BundleInstaller::SetAutoApproveForTesting(bool auto_approve) { 93 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); 94 g_auto_approve_for_test = auto_approve ? PROCEED : ABORT; 95} 96 97BundleInstaller::Item::Item() : state(STATE_PENDING) {} 98 99base::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 105BundleInstaller::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 119BundleInstaller::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 131void BundleInstaller::PromptForApproval(Delegate* delegate) { 132 delegate_ = delegate; 133 134 AddRef(); // Balanced in ReportApproved() and ReportCanceled(). 135 136 ParseManifests(); 137} 138 139void 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 179base::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 206BundleInstaller::~BundleInstaller() { 207 BrowserList::RemoveObserver(this); 208} 209 210void 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 223void BundleInstaller::ReportApproved() { 224 if (delegate_) 225 delegate_->OnBundleInstallApproved(); 226 227 Release(); // Balanced in PromptForApproval(). 228} 229 230void BundleInstaller::ReportCanceled(bool user_initiated) { 231 if (delegate_) 232 delegate_->OnBundleInstallCanceled(user_initiated); 233 234 Release(); // Balanced in PromptForApproval(). 235} 236 237void BundleInstaller::ReportComplete() { 238 if (delegate_) 239 delegate_->OnBundleInstallCompleted(); 240 241 Release(); // Balanced in CompleteInstall(). 242} 243 244void 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 253void 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 286void 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 297void 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 307void 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 316void BundleInstaller::InstallUIProceed() { 317 approved_ = true; 318 ReportApproved(); 319} 320 321void 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 328void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) { 329 items_[id].state = Item::STATE_INSTALLED; 330 331 ShowInstalledBubbleIfDone(); 332} 333 334void 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 348void BundleInstaller::OnBrowserAdded(Browser* browser) {} 349 350void BundleInstaller::OnBrowserRemoved(Browser* browser) { 351 if (browser_ == browser) 352 browser_ = NULL; 353} 354 355void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {} 356 357} // namespace extensions 358