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