installed_loader.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/installed_loader.h" 6 7#include "base/files/file_path.h" 8#include "base/metrics/histogram.h" 9#include "base/strings/stringprintf.h" 10#include "base/strings/utf_string_conversions.h" 11#include "base/threading/thread_restrictions.h" 12#include "base/values.h" 13#include "chrome/browser/browser_process.h" 14#include "chrome/browser/extensions/api/runtime/runtime_api.h" 15#include "chrome/browser/extensions/extension_action_manager.h" 16#include "chrome/browser/extensions/extension_service.h" 17#include "chrome/browser/profiles/profile_manager.h" 18#include "chrome/common/chrome_switches.h" 19#include "chrome/common/extensions/api/managed_mode_private/managed_mode_handler.h" 20#include "chrome/common/extensions/extension_file_util.h" 21#include "chrome/common/extensions/extension_l10n_util.h" 22#include "chrome/common/extensions/manifest_url_handler.h" 23#include "chrome/common/pref_names.h" 24#include "content/public/browser/notification_service.h" 25#include "content/public/browser/user_metrics.h" 26#include "extensions/browser/extension_prefs.h" 27#include "extensions/browser/extension_registry.h" 28#include "extensions/browser/extension_system.h" 29#include "extensions/browser/management_policy.h" 30#include "extensions/common/extension.h" 31#include "extensions/common/manifest.h" 32#include "extensions/common/manifest_constants.h" 33#include "extensions/common/manifest_handlers/background_info.h" 34 35using base::UserMetricsAction; 36using content::BrowserThread; 37 38namespace extensions { 39 40namespace errors = manifest_errors; 41 42namespace { 43 44// The following enumeration is used in histograms matching 45// Extensions.ManifestReload* . Values may be added, as long as existing 46// values are not changed. 47enum ManifestReloadReason { 48 NOT_NEEDED = 0, // Reload not needed. 49 UNPACKED_DIR, // Unpacked directory. 50 NEEDS_RELOCALIZATION, // The locale has changed since we read this extension. 51 CORRUPT_PREFERENCES, // The manifest in the preferences is corrupt. 52 NUM_MANIFEST_RELOAD_REASONS 53}; 54 55// Used in histogram Extension.BackgroundPageType. Values may be added, as 56// long as existing values are not changed. 57enum BackgroundPageType { 58 NO_BACKGROUND_PAGE = 0, 59 BACKGROUND_PAGE_PERSISTENT = 1, 60 EVENT_PAGE = 2, 61}; 62 63// Used in histogram Extensions.ExternalItemState. Values may be added, as 64// long as existing values are not changed. 65enum ExternalItemState { 66 DEPRECATED_EXTERNAL_ITEM_DISABLED = 0, 67 DEPRECATED_EXTERNAL_ITEM_ENABLED = 1, 68 EXTERNAL_ITEM_WEBSTORE_DISABLED = 2, 69 EXTERNAL_ITEM_WEBSTORE_ENABLED = 3, 70 EXTERNAL_ITEM_NONWEBSTORE_DISABLED = 4, 71 EXTERNAL_ITEM_NONWEBSTORE_ENABLED = 5, 72 EXTERNAL_ITEM_WEBSTORE_UNINSTALLED = 6, 73 EXTERNAL_ITEM_NONWEBSTORE_UNINSTALLED = 7, 74 EXTERNAL_ITEM_MAX_ITEMS = 8 75}; 76 77bool IsManifestCorrupt(const base::DictionaryValue* manifest) { 78 if (!manifest) 79 return false; 80 81 // Because of bug #272524 sometimes manifests got mangled in the preferences 82 // file, one particularly bad case resulting in having both a background page 83 // and background scripts values. In those situations we want to reload the 84 // manifest from the extension to fix this. 85 const base::Value* background_page; 86 const base::Value* background_scripts; 87 return manifest->Get(manifest_keys::kBackgroundPage, &background_page) && 88 manifest->Get(manifest_keys::kBackgroundScripts, &background_scripts); 89} 90 91ManifestReloadReason ShouldReloadExtensionManifest(const ExtensionInfo& info) { 92 // Always reload manifests of unpacked extensions, because they can change 93 // on disk independent of the manifest in our prefs. 94 if (Manifest::IsUnpackedLocation(info.extension_location)) 95 return UNPACKED_DIR; 96 97 // Reload the manifest if it needs to be relocalized. 98 if (extension_l10n_util::ShouldRelocalizeManifest( 99 info.extension_manifest.get())) 100 return NEEDS_RELOCALIZATION; 101 102 // Reload if the copy of the manifest in the preferences is corrupt. 103 if (IsManifestCorrupt(info.extension_manifest.get())) 104 return CORRUPT_PREFERENCES; 105 106 return NOT_NEEDED; 107} 108 109BackgroundPageType GetBackgroundPageType(const Extension* extension) { 110 if (!BackgroundInfo::HasBackgroundPage(extension)) 111 return NO_BACKGROUND_PAGE; 112 if (BackgroundInfo::HasPersistentBackgroundPage(extension)) 113 return BACKGROUND_PAGE_PERSISTENT; 114 return EVENT_PAGE; 115} 116 117} // namespace 118 119InstalledLoader::InstalledLoader(ExtensionService* extension_service) 120 : extension_service_(extension_service), 121 extension_registry_(ExtensionRegistry::Get(extension_service->profile())), 122 extension_prefs_(extension_service->extension_prefs()) {} 123 124InstalledLoader::~InstalledLoader() { 125} 126 127void InstalledLoader::Load(const ExtensionInfo& info, bool write_to_prefs) { 128 std::string error; 129 scoped_refptr<const Extension> extension(NULL); 130 if (info.extension_manifest) { 131 extension = Extension::Create( 132 info.extension_path, 133 info.extension_location, 134 *info.extension_manifest, 135 GetCreationFlags(&info), 136 &error); 137 } else { 138 error = errors::kManifestUnreadable; 139 } 140 141 // Once installed, non-unpacked extensions cannot change their IDs (e.g., by 142 // updating the 'key' field in their manifest). 143 // TODO(jstritar): migrate preferences when unpacked extensions change IDs. 144 if (extension.get() && !Manifest::IsUnpackedLocation(extension->location()) && 145 info.extension_id != extension->id()) { 146 error = errors::kCannotChangeExtensionID; 147 extension = NULL; 148 content::RecordAction(UserMetricsAction("Extensions.IDChangedError")); 149 } 150 151 // Check policy on every load in case an extension was blacklisted while 152 // Chrome was not running. 153 const ManagementPolicy* policy = extensions::ExtensionSystem::Get( 154 extension_service_->profile())->management_policy(); 155 if (extension.get()) { 156 Extension::DisableReason disable_reason = Extension::DISABLE_NONE; 157 bool force_disabled = false; 158 if (!policy->UserMayLoad(extension.get(), NULL)) { 159 // The error message from UserMayInstall() often contains the extension ID 160 // and is therefore not well suited to this UI. 161 error = errors::kDisabledByPolicy; 162 extension = NULL; 163 } else if (!extension_prefs_->IsExtensionDisabled(extension->id()) && 164 policy->MustRemainDisabled(extension, &disable_reason, NULL)) { 165 extension_prefs_->SetExtensionState(extension->id(), Extension::DISABLED); 166 extension_prefs_->AddDisableReason(extension->id(), disable_reason); 167 force_disabled = true; 168 } 169 UMA_HISTOGRAM_BOOLEAN("ExtensionInstalledLoader.ForceDisabled", 170 force_disabled); 171 } 172 173 if (!extension.get()) { 174 extension_service_->ReportExtensionLoadError( 175 info.extension_path, error, false); 176 return; 177 } 178 179 if (write_to_prefs) 180 extension_prefs_->UpdateManifest(extension.get()); 181 182 extension_service_->AddExtension(extension.get()); 183} 184 185void InstalledLoader::LoadAllExtensions() { 186 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 187 188 base::TimeTicks start_time = base::TimeTicks::Now(); 189 190 scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info( 191 extension_prefs_->GetInstalledExtensionsInfo()); 192 193 std::vector<int> reload_reason_counts(NUM_MANIFEST_RELOAD_REASONS, 0); 194 bool should_write_prefs = false; 195 196 for (size_t i = 0; i < extensions_info->size(); ++i) { 197 ExtensionInfo* info = extensions_info->at(i).get(); 198 199 // Skip extensions that were loaded from the command-line because we don't 200 // want those to persist across browser restart. 201 if (info->extension_location == Manifest::COMMAND_LINE) 202 continue; 203 204 ManifestReloadReason reload_reason = ShouldReloadExtensionManifest(*info); 205 ++reload_reason_counts[reload_reason]; 206 UMA_HISTOGRAM_ENUMERATION("Extensions.ManifestReloadEnumValue", 207 reload_reason, 100); 208 209 if (reload_reason != NOT_NEEDED) { 210 // Reloading an extension reads files from disk. We do this on the 211 // UI thread because reloads should be very rare, and the complexity 212 // added by delaying the time when the extensions service knows about 213 // all extensions is significant. See crbug.com/37548 for details. 214 // |allow_io| disables tests that file operations run on the file 215 // thread. 216 base::ThreadRestrictions::ScopedAllowIO allow_io; 217 218 std::string error; 219 scoped_refptr<const Extension> extension( 220 extension_file_util::LoadExtension( 221 info->extension_path, 222 info->extension_location, 223 GetCreationFlags(info), 224 &error)); 225 226 if (!extension.get()) { 227 extension_service_->ReportExtensionLoadError( 228 info->extension_path, error, false); 229 continue; 230 } 231 232 extensions_info->at(i)->extension_manifest.reset( 233 static_cast<base::DictionaryValue*>( 234 extension->manifest()->value()->DeepCopy())); 235 should_write_prefs = true; 236 } 237 } 238 239 for (size_t i = 0; i < extensions_info->size(); ++i) { 240 if (extensions_info->at(i)->extension_location == Manifest::COMMAND_LINE) 241 continue; 242 Load(*extensions_info->at(i), should_write_prefs); 243 } 244 245 extension_service_->OnLoadedInstalledExtensions(); 246 247 // The histograms Extensions.ManifestReload* allow us to validate 248 // the assumption that reloading manifest is a rare event. 249 UMA_HISTOGRAM_COUNTS_100("Extensions.ManifestReloadNotNeeded", 250 reload_reason_counts[NOT_NEEDED]); 251 UMA_HISTOGRAM_COUNTS_100("Extensions.ManifestReloadUnpackedDir", 252 reload_reason_counts[UNPACKED_DIR]); 253 UMA_HISTOGRAM_COUNTS_100("Extensions.ManifestReloadNeedsRelocalization", 254 reload_reason_counts[NEEDS_RELOCALIZATION]); 255 256 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadAll", 257 extension_registry_->enabled_extensions().size()); 258 UMA_HISTOGRAM_COUNTS_100("Extensions.Disabled", 259 extension_registry_->disabled_extensions().size()); 260 261 UMA_HISTOGRAM_TIMES("Extensions.LoadAllTime", 262 base::TimeTicks::Now() - start_time); 263 264 int app_user_count = 0; 265 int app_external_count = 0; 266 int hosted_app_count = 0; 267 int legacy_packaged_app_count = 0; 268 int platform_app_count = 0; 269 int user_script_count = 0; 270 int content_pack_count = 0; 271 int extension_user_count = 0; 272 int extension_external_count = 0; 273 int theme_count = 0; 274 int page_action_count = 0; 275 int browser_action_count = 0; 276 int disabled_for_permissions_count = 0; 277 int item_user_count = 0; 278 int non_webstore_ntp_override_count = 0; 279 const ExtensionSet& extensions = extension_registry_->enabled_extensions(); 280 ExtensionSet::const_iterator ex; 281 for (ex = extensions.begin(); ex != extensions.end(); ++ex) { 282 Manifest::Location location = (*ex)->location(); 283 Manifest::Type type = (*ex)->GetType(); 284 if ((*ex)->is_app()) { 285 UMA_HISTOGRAM_ENUMERATION("Extensions.AppLocation", 286 location, 100); 287 } else if (type == Manifest::TYPE_EXTENSION) { 288 UMA_HISTOGRAM_ENUMERATION("Extensions.ExtensionLocation", 289 location, 100); 290 } 291 if (!ManifestURL::UpdatesFromGallery(*ex)) { 292 UMA_HISTOGRAM_ENUMERATION("Extensions.NonWebstoreLocation", 293 location, 100); 294 } 295 if (Manifest::IsExternalLocation(location)) { 296 // See loop below for DISABLED. 297 if (ManifestURL::UpdatesFromGallery(*ex)) { 298 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalItemState", 299 EXTERNAL_ITEM_WEBSTORE_ENABLED, 300 EXTERNAL_ITEM_MAX_ITEMS); 301 } else { 302 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalItemState", 303 EXTERNAL_ITEM_NONWEBSTORE_ENABLED, 304 EXTERNAL_ITEM_MAX_ITEMS); 305 } 306 } 307 if ((*ex)->from_webstore()) { 308 // Check for inconsistencies if the extension was supposedly installed 309 // from the webstore. 310 enum { 311 BAD_UPDATE_URL = 0, 312 // This value was a mistake. Turns out sideloaded extensions can 313 // have the from_webstore bit if they update from the webstore. 314 DEPRECATED_IS_EXTERNAL = 1, 315 }; 316 if (!ManifestURL::UpdatesFromGallery(*ex)) { 317 UMA_HISTOGRAM_ENUMERATION("Extensions.FromWebstoreInconsistency", 318 BAD_UPDATE_URL, 2); 319 } 320 } 321 322 // Don't count component extensions, since they are only extensions as an 323 // implementation detail. 324 if (location == Manifest::COMPONENT) 325 continue; 326 // Histogram for non-webstore extensions overriding new tab page should 327 // include unpacked extensions. 328 if (!(*ex)->from_webstore()) { 329 const extensions::URLOverrides::URLOverrideMap& override_map = 330 extensions::URLOverrides::GetChromeURLOverrides(ex->get()); 331 if (override_map.find("newtab") != override_map.end()) { 332 ++non_webstore_ntp_override_count; 333 } 334 } 335 336 // Don't count unpacked extensions, since they're a developer-specific 337 // feature. 338 if (Manifest::IsUnpackedLocation(location)) 339 continue; 340 341 UMA_HISTOGRAM_ENUMERATION("Extensions.ManifestVersion", 342 (*ex)->manifest_version(), 10); 343 344 if (type == Manifest::TYPE_EXTENSION) { 345 BackgroundPageType background_page_type = 346 GetBackgroundPageType(ex->get()); 347 UMA_HISTOGRAM_ENUMERATION( 348 "Extensions.BackgroundPageType", background_page_type, 10); 349 } 350 351 // Using an enumeration shows us the total installed ratio across all users. 352 // Using the totals per user at each startup tells us the distribution of 353 // usage for each user (e.g. 40% of users have at least one app installed). 354 UMA_HISTOGRAM_ENUMERATION("Extensions.LoadType", type, 100); 355 switch (type) { 356 case Manifest::TYPE_THEME: 357 ++theme_count; 358 break; 359 case Manifest::TYPE_USER_SCRIPT: 360 ++user_script_count; 361 break; 362 case Manifest::TYPE_HOSTED_APP: 363 ++hosted_app_count; 364 if (Manifest::IsExternalLocation(location)) { 365 ++app_external_count; 366 } else { 367 ++app_user_count; 368 } 369 break; 370 case Manifest::TYPE_LEGACY_PACKAGED_APP: 371 ++legacy_packaged_app_count; 372 if (Manifest::IsExternalLocation(location)) { 373 ++app_external_count; 374 } else { 375 ++app_user_count; 376 } 377 break; 378 case Manifest::TYPE_PLATFORM_APP: 379 ++platform_app_count; 380 if (Manifest::IsExternalLocation(location)) { 381 ++app_external_count; 382 } else { 383 ++app_user_count; 384 } 385 break; 386 case Manifest::TYPE_EXTENSION: 387 default: 388 if (Manifest::IsExternalLocation(location)) { 389 ++extension_external_count; 390 } else { 391 ++extension_user_count; 392 } 393 break; 394 } 395 if (!Manifest::IsExternalLocation((*ex)->location())) 396 ++item_user_count; 397 ExtensionActionManager* extension_action_manager = 398 ExtensionActionManager::Get(extension_service_->profile()); 399 if (extension_action_manager->GetPageAction(*ex->get())) 400 ++page_action_count; 401 if (extension_action_manager->GetBrowserAction(*ex->get())) 402 ++browser_action_count; 403 404 if (extensions::ManagedModeInfo::IsContentPack(ex->get())) 405 ++content_pack_count; 406 407 extension_service_->RecordPermissionMessagesHistogram( 408 ex->get(), "Extensions.Permissions_Load"); 409 } 410 411 const ExtensionSet& disabled_extensions = 412 extension_registry_->disabled_extensions(); 413 for (ex = disabled_extensions.begin(); ex != disabled_extensions.end(); 414 ++ex) { 415 if (extension_service_->extension_prefs()-> 416 DidExtensionEscalatePermissions((*ex)->id())) { 417 ++disabled_for_permissions_count; 418 } 419 if (Manifest::IsExternalLocation((*ex)->location())) { 420 // See loop above for ENABLED. 421 if (ManifestURL::UpdatesFromGallery(*ex)) { 422 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalItemState", 423 EXTERNAL_ITEM_WEBSTORE_DISABLED, 424 EXTERNAL_ITEM_MAX_ITEMS); 425 } else { 426 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalItemState", 427 EXTERNAL_ITEM_NONWEBSTORE_DISABLED, 428 EXTERNAL_ITEM_MAX_ITEMS); 429 } 430 } 431 } 432 433 scoped_ptr<ExtensionPrefs::ExtensionsInfo> uninstalled_extensions_info( 434 extension_prefs_->GetUninstalledExtensionsInfo()); 435 for (size_t i = 0; i < uninstalled_extensions_info->size(); ++i) { 436 ExtensionInfo* info = uninstalled_extensions_info->at(i).get(); 437 if (Manifest::IsExternalLocation(info->extension_location)) { 438 std::string update_url; 439 if (info->extension_manifest->GetString("update_url", &update_url) && 440 extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) { 441 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalItemState", 442 EXTERNAL_ITEM_WEBSTORE_UNINSTALLED, 443 EXTERNAL_ITEM_MAX_ITEMS); 444 } else { 445 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalItemState", 446 EXTERNAL_ITEM_NONWEBSTORE_UNINSTALLED, 447 EXTERNAL_ITEM_MAX_ITEMS); 448 } 449 } 450 } 451 452 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadAllUser", item_user_count); 453 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadApp", 454 app_user_count + app_external_count); 455 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadAppUser", app_user_count); 456 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadAppExternal", app_external_count); 457 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadHostedApp", hosted_app_count); 458 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadPackagedApp", 459 legacy_packaged_app_count); 460 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadPlatformApp", platform_app_count); 461 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadExtension", 462 extension_user_count + extension_external_count); 463 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadExtensionUser", 464 extension_user_count); 465 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadExtensionExternal", 466 extension_external_count); 467 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadUserScript", user_script_count); 468 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadTheme", theme_count); 469 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadPageAction", page_action_count); 470 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadBrowserAction", 471 browser_action_count); 472 UMA_HISTOGRAM_COUNTS_100("Extensions.LoadContentPack", content_pack_count); 473 UMA_HISTOGRAM_COUNTS_100("Extensions.DisabledForPermissions", 474 disabled_for_permissions_count); 475 UMA_HISTOGRAM_COUNTS_100("Extensions.NonWebStoreNewTabPageOverrides", 476 non_webstore_ntp_override_count); 477} 478 479int InstalledLoader::GetCreationFlags(const ExtensionInfo* info) { 480 int flags = extension_prefs_->GetCreationFlags(info->extension_id); 481 if (!Manifest::IsUnpackedLocation(info->extension_location)) 482 flags |= Extension::REQUIRE_KEY; 483 if (extension_prefs_->AllowFileAccess(info->extension_id)) 484 flags |= Extension::ALLOW_FILE_ACCESS; 485 return flags; 486} 487 488} // namespace extensions 489