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