bundle_installer.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/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_list.h" 19#include "chrome/browser/ui/browser_finder.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 "content/public/browser/navigation_controller.h" 26#include "content/public/browser/web_contents.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 99string16 BundleInstaller::Item::GetNameForDisplay() { 100 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()))); 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::FLAG_NONE); 175 installer->Start(); 176 } 177} 178 179string16 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 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 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, dummy_extensions_[i]->required_permission_set()); 264 } 265 266 if (g_auto_approve_for_test == PROCEED) { 267 InstallUIProceed(); 268 } else if (g_auto_approve_for_test == ABORT) { 269 InstallUIAbort(true); 270 } else { 271 Browser* browser = browser_; 272 if (!browser) { 273 // The browser that we got initially could have gone away during our 274 // thread hopping. 275 browser = chrome::FindLastActiveWithProfile(profile_, host_desktop_type_); 276 } 277 content::WebContents* web_contents = NULL; 278 if (browser) 279 web_contents = browser->tab_strip_model()->GetActiveWebContents(); 280 install_ui_.reset(new ExtensionInstallPrompt(web_contents)); 281 install_ui_->ConfirmBundleInstall(this, permissions); 282 } 283} 284 285void BundleInstaller::ShowInstalledBubbleIfDone() { 286 // We're ready to show the installed bubble when no items are pending. 287 if (!GetItemsWithState(Item::STATE_PENDING).empty()) 288 return; 289 290 if (browser_) 291 ShowInstalledBubble(this, browser_); 292 293 ReportComplete(); 294} 295 296void BundleInstaller::OnWebstoreParseSuccess( 297 const std::string& id, 298 const SkBitmap& icon, 299 DictionaryValue* manifest) { 300 dummy_extensions_.push_back(CreateDummyExtension(items_[id], manifest)); 301 parsed_manifests_[id] = linked_ptr<DictionaryValue>(manifest); 302 303 ShowPromptIfDoneParsing(); 304} 305 306void BundleInstaller::OnWebstoreParseFailure( 307 const std::string& id, 308 WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code, 309 const std::string& error_message) { 310 items_[id].state = Item::STATE_FAILED; 311 312 ShowPromptIfDoneParsing(); 313} 314 315void BundleInstaller::InstallUIProceed() { 316 approved_ = true; 317 ReportApproved(); 318} 319 320void BundleInstaller::InstallUIAbort(bool user_initiated) { 321 for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) 322 i->second.state = Item::STATE_FAILED; 323 324 ReportCanceled(user_initiated); 325} 326 327void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) { 328 items_[id].state = Item::STATE_INSTALLED; 329 330 ShowInstalledBubbleIfDone(); 331} 332 333void BundleInstaller::OnExtensionInstallFailure( 334 const std::string& id, 335 const std::string& error, 336 WebstoreInstaller::FailureReason reason) { 337 items_[id].state = Item::STATE_FAILED; 338 339 ExtensionList::iterator i = std::find_if( 340 dummy_extensions_.begin(), dummy_extensions_.end(), MatchIdFunctor(id)); 341 CHECK(dummy_extensions_.end() != i); 342 dummy_extensions_.erase(i); 343 344 ShowInstalledBubbleIfDone(); 345} 346 347void BundleInstaller::OnBrowserAdded(Browser* browser) {} 348 349void BundleInstaller::OnBrowserRemoved(Browser* browser) { 350 if (browser_ == browser) 351 browser_ = NULL; 352} 353 354void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {} 355 356} // namespace extensions 357