component_loader.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/component_loader.h" 6 7#include "base/command_line.h" 8#include "base/file_util.h" 9#include "base/json/json_string_value_serializer.h" 10#include "base/path_service.h" 11#include "base/prefs/pref_change_registrar.h" 12#include "base/prefs/pref_notifier.h" 13#include "base/prefs/pref_service.h" 14#include "chrome/browser/extensions/extension_service.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/common/chrome_notification_types.h" 17#include "chrome/common/chrome_paths.h" 18#include "chrome/common/chrome_switches.h" 19#include "chrome/common/extensions/extension.h" 20#include "chrome/common/extensions/extension_file_util.h" 21#include "chrome/common/extensions/extension_manifest_constants.h" 22#include "chrome/common/pref_names.h" 23#include "components/user_prefs/pref_registry_syncable.h" 24#include "content/public/browser/notification_details.h" 25#include "content/public/browser/notification_source.h" 26#include "extensions/common/id_util.h" 27#include "grit/browser_resources.h" 28#include "ui/base/resource/resource_bundle.h" 29 30#if defined(USE_AURA) 31#include "grit/keyboard_resources.h" 32#endif 33 34#if defined(GOOGLE_CHROME_BUILD) 35#include "chrome/browser/defaults.h" 36#endif 37 38#if defined(OS_CHROMEOS) 39#include "chrome/browser/chromeos/login/user_manager.h" 40#include "chrome/browser/extensions/extension_service.h" 41#include "chrome/browser/extensions/extension_system.h" 42#include "chrome/browser/profiles/profile.h" 43#include "chrome/browser/profiles/profile_manager.h" 44#include "chromeos/chromeos_switches.h" 45#include "content/public/browser/storage_partition.h" 46#include "webkit/fileapi/file_system_context.h" 47#include "webkit/fileapi/sandbox_mount_point_provider.h" 48#endif 49 50#if defined(ENABLE_APP_LIST) 51#include "grit/chromium_strings.h" 52#include "ui/base/l10n/l10n_util.h" 53#endif 54 55namespace extensions { 56 57namespace { 58 59static bool enable_background_extensions_during_testing = false; 60 61std::string GenerateId(const DictionaryValue* manifest, 62 const base::FilePath& path) { 63 std::string raw_key; 64 std::string id_input; 65 CHECK(manifest->GetString(extension_manifest_keys::kPublicKey, &raw_key)); 66 CHECK(Extension::ParsePEMKeyBytes(raw_key, &id_input)); 67 std::string id = id_util::GenerateId(id_input); 68 return id; 69} 70 71} // namespace 72 73ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( 74 const DictionaryValue* manifest, const base::FilePath& directory) 75 : manifest(manifest), 76 root_directory(directory) { 77 if (!root_directory.IsAbsolute()) { 78 CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory)); 79 root_directory = root_directory.Append(directory); 80 } 81 extension_id = GenerateId(manifest, root_directory); 82} 83 84ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service, 85 PrefService* profile_prefs, 86 PrefService* local_state) 87 : profile_prefs_(profile_prefs), 88 local_state_(local_state), 89 extension_service_(extension_service) { 90 pref_change_registrar_.Init(profile_prefs); 91 92 // This pref is set by policy. We have to watch it for change because on 93 // ChromeOS, policy isn't loaded until after the browser process is started. 94 pref_change_registrar_.Add( 95 prefs::kEnterpriseWebStoreURL, 96 base::Bind(&ComponentLoader::AddOrReloadEnterpriseWebStore, 97 base::Unretained(this))); 98} 99 100ComponentLoader::~ComponentLoader() { 101 ClearAllRegistered(); 102} 103 104void ComponentLoader::LoadAll() { 105 for (RegisteredComponentExtensions::iterator it = 106 component_extensions_.begin(); 107 it != component_extensions_.end(); ++it) { 108 Load(*it); 109 } 110} 111 112DictionaryValue* ComponentLoader::ParseManifest( 113 const std::string& manifest_contents) const { 114 JSONStringValueSerializer serializer(manifest_contents); 115 scoped_ptr<Value> manifest(serializer.Deserialize(NULL, NULL)); 116 117 if (!manifest.get() || !manifest->IsType(Value::TYPE_DICTIONARY)) { 118 LOG(ERROR) << "Failed to parse extension manifest."; 119 return NULL; 120 } 121 // Transfer ownership to the caller. 122 return static_cast<DictionaryValue*>(manifest.release()); 123} 124 125void ComponentLoader::ClearAllRegistered() { 126 for (RegisteredComponentExtensions::iterator it = 127 component_extensions_.begin(); 128 it != component_extensions_.end(); ++it) { 129 delete it->manifest; 130 } 131 132 component_extensions_.clear(); 133} 134 135std::string ComponentLoader::Add(int manifest_resource_id, 136 const base::FilePath& root_directory) { 137 std::string manifest_contents = 138 ResourceBundle::GetSharedInstance().GetRawDataResource( 139 manifest_resource_id).as_string(); 140 return Add(manifest_contents, root_directory); 141} 142 143std::string ComponentLoader::Add(const std::string& manifest_contents, 144 const base::FilePath& root_directory) { 145 // The Value is kept for the lifetime of the ComponentLoader. This is 146 // required in case LoadAll() is called again. 147 DictionaryValue* manifest = ParseManifest(manifest_contents); 148 if (manifest) 149 return Add(manifest, root_directory); 150 return std::string(); 151} 152 153std::string ComponentLoader::Add(const DictionaryValue* parsed_manifest, 154 const base::FilePath& root_directory) { 155 ComponentExtensionInfo info(parsed_manifest, root_directory); 156 component_extensions_.push_back(info); 157 if (extension_service_->is_ready()) 158 Load(info); 159 return info.extension_id; 160} 161 162std::string ComponentLoader::AddOrReplace(const base::FilePath& path) { 163 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); 164 std::string error; 165 scoped_ptr<DictionaryValue> manifest( 166 extension_file_util::LoadManifest(absolute_path, &error)); 167 if (!manifest) { 168 LOG(ERROR) << "Could not load extension from '" << 169 absolute_path.value() << "'. " << error; 170 return NULL; 171 } 172 Remove(GenerateId(manifest.get(), absolute_path)); 173 174 return Add(manifest.release(), absolute_path); 175} 176 177void ComponentLoader::Reload(const std::string& extension_id) { 178 for (RegisteredComponentExtensions::iterator it = 179 component_extensions_.begin(); it != component_extensions_.end(); 180 ++it) { 181 if (it->extension_id == extension_id) { 182 Load(*it); 183 break; 184 } 185 } 186} 187 188void ComponentLoader::Load(const ComponentExtensionInfo& info) { 189 // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated 190 // our component extensions to the new manifest version. 191 int flags = Extension::REQUIRE_KEY; 192 193 std::string error; 194 195 scoped_refptr<const Extension> extension(Extension::Create( 196 info.root_directory, 197 Manifest::COMPONENT, 198 *info.manifest, 199 flags, 200 &error)); 201 if (!extension) { 202 LOG(ERROR) << error; 203 return; 204 } 205 206 CHECK_EQ(info.extension_id, extension->id()) << extension->name(); 207 extension_service_->AddComponentExtension(extension); 208} 209 210void ComponentLoader::RemoveAll() { 211 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 212 for (; it != component_extensions_.end(); ++it) 213 UnloadComponent(&(*it)); 214 215 component_extensions_.clear(); 216} 217 218void ComponentLoader::Remove(const base::FilePath& root_directory) { 219 // Find the ComponentExtensionInfo for the extension. 220 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 221 for (; it != component_extensions_.end(); ++it) { 222 if (it->root_directory == root_directory) { 223 Remove(GenerateId(it->manifest, root_directory)); 224 break; 225 } 226 } 227} 228 229void ComponentLoader::Remove(const std::string& id) { 230 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 231 for (; it != component_extensions_.end(); ++it) { 232 if (it->extension_id == id) { 233 UnloadComponent(&(*it)); 234 it = component_extensions_.erase(it); 235 break; 236 } 237 } 238} 239 240bool ComponentLoader::Exists(const std::string& id) const { 241 RegisteredComponentExtensions::const_iterator it = 242 component_extensions_.begin(); 243 for (; it != component_extensions_.end(); ++it) 244 if (it->extension_id == id) 245 return true; 246 return false; 247} 248 249void ComponentLoader::AddFileManagerExtension() { 250#if defined(FILE_MANAGER_EXTENSION) 251 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 252 int manifest_id; 253 if (command_line->HasSwitch(switches::kFileManagerLegacy)) 254 manifest_id = IDR_FILEMANAGER_MANIFEST_V1; 255 else if (command_line->HasSwitch(switches::kFileManagerLegacyUI)) 256 manifest_id = IDR_FILEMANAGER_MANIFEST; 257 else 258 manifest_id = IDR_FILEMANAGER_MANIFEST_NEW_UI; 259#ifndef NDEBUG 260 if (command_line->HasSwitch(switches::kFileManagerExtensionPath)) { 261 base::FilePath filemgr_extension_path( 262 command_line->GetSwitchValuePath(switches::kFileManagerExtensionPath)); 263 Add(manifest_id, filemgr_extension_path); 264 return; 265 } 266#endif // NDEBUG 267 Add(manifest_id, base::FilePath(FILE_PATH_LITERAL("file_manager"))); 268#endif // defined(FILE_MANAGER_EXTENSION) 269} 270 271void ComponentLoader::AddImageLoaderExtension() { 272#if defined(IMAGE_LOADER_EXTENSION) 273#ifndef NDEBUG 274 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 275 if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) { 276 base::FilePath image_loader_extension_path( 277 command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath)); 278 Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path); 279 return; 280 } 281#endif // NDEBUG 282 Add(IDR_IMAGE_LOADER_MANIFEST, 283 base::FilePath(FILE_PATH_LITERAL("image_loader"))); 284#endif // defined(IMAGE_LOADER_EXTENSION) 285} 286 287void ComponentLoader::AddOrReloadEnterpriseWebStore() { 288 base::FilePath path(FILE_PATH_LITERAL("enterprise_web_store")); 289 290 // Remove the extension if it was already loaded. 291 Remove(path); 292 293 std::string enterprise_webstore_url = 294 profile_prefs_->GetString(prefs::kEnterpriseWebStoreURL); 295 296 // Load the extension only if the URL preference is set. 297 if (!enterprise_webstore_url.empty()) { 298 std::string manifest_contents = 299 ResourceBundle::GetSharedInstance().GetRawDataResource( 300 IDR_ENTERPRISE_WEBSTORE_MANIFEST).as_string(); 301 302 // The manifest is missing some values that are provided by policy. 303 DictionaryValue* manifest = ParseManifest(manifest_contents); 304 if (manifest) { 305 std::string name = 306 profile_prefs_->GetString(prefs::kEnterpriseWebStoreName); 307 manifest->SetString("app.launch.web_url", enterprise_webstore_url); 308 manifest->SetString("name", name); 309 Add(manifest, path); 310 } 311 } 312} 313 314void ComponentLoader::AddChromeApp() { 315#if defined(ENABLE_APP_LIST) 316 std::string manifest_contents = 317 ResourceBundle::GetSharedInstance().GetRawDataResource( 318 IDR_CHROME_APP_MANIFEST).as_string(); 319 320 // The Value is kept for the lifetime of the ComponentLoader. This is 321 // required in case LoadAll() is called again. 322 DictionaryValue* manifest = ParseManifest(manifest_contents); 323 324 if (manifest) { 325 // Update manifest to use a proper name. 326 manifest->SetString(extension_manifest_keys::kName, 327 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME)); 328 Add(manifest, base::FilePath(FILE_PATH_LITERAL("chrome_app"))); 329 } 330#endif 331} 332 333void ComponentLoader::AddKeyboardApp() { 334#if defined(USE_AURA) 335 Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard"))); 336#endif 337} 338 339// static 340void ComponentLoader::EnableBackgroundExtensionsForTesting() { 341 enable_background_extensions_during_testing = true; 342} 343 344void ComponentLoader::AddDefaultComponentExtensions( 345 bool skip_session_components) { 346 // Do not add component extensions that have background pages here -- add them 347 // to AddDefaultComponentExtensionsWithBackgroundPages. 348#if defined(OS_CHROMEOS) 349 Add(IDR_MOBILE_MANIFEST, 350 base::FilePath(FILE_PATH_LITERAL("/usr/share/chromeos-assets/mobile"))); 351 352#if defined(GOOGLE_CHROME_BUILD) 353 if (browser_defaults::enable_help_app) { 354 Add(IDR_HELP_MANIFEST, base::FilePath(FILE_PATH_LITERAL( 355 "/usr/share/chromeos-assets/helpapp"))); 356 } 357#endif 358 359 // Skip all other extensions that require user session presence. 360 if (!skip_session_components) { 361 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 362 if (!command_line->HasSwitch(chromeos::switches::kGuestSession)) 363 Add(IDR_BOOKMARKS_MANIFEST, 364 base::FilePath(FILE_PATH_LITERAL("bookmark_manager"))); 365 366 Add(IDR_CROSH_BUILTIN_MANIFEST, base::FilePath(FILE_PATH_LITERAL( 367 "/usr/share/chromeos-assets/crosh_builtin"))); 368 } 369#else // !defined(OS_CHROMEOS) 370 DCHECK(!skip_session_components); 371 Add(IDR_BOOKMARKS_MANIFEST, 372 base::FilePath(FILE_PATH_LITERAL("bookmark_manager"))); 373 // Cloud Print component app. Not required on Chrome OS. 374 Add(IDR_CLOUDPRINT_MANIFEST, 375 base::FilePath(FILE_PATH_LITERAL("cloud_print"))); 376#endif 377 378 if (!skip_session_components) { 379 Add(IDR_WEBSTORE_MANIFEST, base::FilePath(FILE_PATH_LITERAL("web_store"))); 380 381 // If a URL for the enterprise webstore has been specified, load the 382 // component extension. This extension might also be loaded later, because 383 // it is specified by policy, and on ChromeOS policies are loaded after 384 // the browser process has started. 385 AddOrReloadEnterpriseWebStore(); 386 387 AddChromeApp(); 388 } 389 390 AddKeyboardApp(); 391 392 AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components); 393} 394 395void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages( 396 bool skip_session_components) { 397 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 398 399 // Component extensions with background pages are not enabled during tests 400 // because they generate a lot of background behavior that can interfere. 401 if (!enable_background_extensions_during_testing && 402 command_line->HasSwitch(switches::kTestType)) { 403 return; 404 } 405 406 if (!skip_session_components) { 407 // Apps Debugger 408 if (CommandLine::ForCurrentProcess()->HasSwitch( 409 switches::kAppsDevtool)) { 410 Add(IDR_APPS_DEBUGGER_MANIFEST, 411 base::FilePath(FILE_PATH_LITERAL("apps_debugger"))); 412 } 413 414 AddFileManagerExtension(); 415 AddImageLoaderExtension(); 416 417#if defined(ENABLE_SETTINGS_APP) 418 Add(IDR_SETTINGS_APP_MANIFEST, 419 base::FilePath(FILE_PATH_LITERAL("settings_app"))); 420#endif 421 } 422 423#if defined(OS_CHROMEOS) 424 if (!skip_session_components) { 425 Add(IDR_WALLPAPERMANAGER_MANIFEST, 426 base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager"))); 427 428#if defined(GOOGLE_CHROME_BUILD) 429 if (!command_line->HasSwitch( 430 chromeos::switches::kDisableQuickofficeComponentApp)) { 431 std::string id = Add(IDR_QUICK_OFFICE_MANIFEST, base::FilePath( 432 FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office"))); 433 if (command_line->HasSwitch(chromeos::switches::kGuestSession)) { 434 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for 435 // Quickoffice. It doesn't work without temporary file system access. 436 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord(); 437 ExtensionService* service = 438 extensions::ExtensionSystem::Get(profile)->extension_service(); 439 GURL site = service->GetSiteForExtensionId(id); 440 fileapi::FileSystemContext* context = 441 content::BrowserContext::GetStoragePartitionForSite(profile, site)-> 442 GetFileSystemContext(); 443 fileapi::SandboxMountPointProvider* provider = 444 context->sandbox_provider(); 445 provider->set_enable_temporary_file_system_in_incognito(true); 446 } 447 } 448#endif // defined(GOOGLE_CHROME_BUILD) 449 450 base::FilePath echo_extension_path(FILE_PATH_LITERAL( 451 "/usr/share/chromeos-assets/echo")); 452 if (command_line->HasSwitch(switches::kEchoExtensionPath)) { 453 echo_extension_path = 454 command_line->GetSwitchValuePath(switches::kEchoExtensionPath); 455 } 456 Add(IDR_ECHO_MANIFEST, echo_extension_path); 457 458 Add(IDR_NETWORK_CONFIGURATION_MANIFEST, 459 base::FilePath(FILE_PATH_LITERAL("chromeos/network_configuration"))); 460 } 461 462 // Load ChromeVox extension now if spoken feedback is enabled. 463 if (local_state_->GetBoolean(prefs::kSpokenFeedbackEnabled)) { 464 base::FilePath path = 465 base::FilePath(extension_misc::kChromeVoxExtensionPath); 466 Add(IDR_CHROMEVOX_MANIFEST, path); 467 } 468#endif // defined(OS_CHROMEOS) 469 470#if defined(ENABLE_GOOGLE_NOW) 471 if (CommandLine::ForCurrentProcess()->HasSwitch( 472 switches::kEnableGoogleNowIntegration)) { 473 Add(IDR_GOOGLE_NOW_MANIFEST, 474 base::FilePath(FILE_PATH_LITERAL("google_now"))); 475 } 476#endif 477} 478 479void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { 480 delete component->manifest; 481 if (extension_service_->is_ready()) { 482 extension_service_-> 483 UnloadExtension(component->extension_id, 484 extension_misc::UNLOAD_REASON_DISABLE); 485 } 486} 487 488// static 489void ComponentLoader::RegisterUserPrefs( 490 user_prefs::PrefRegistrySyncable* registry) { 491 registry->RegisterStringPref( 492 prefs::kEnterpriseWebStoreURL, 493 std::string() /* default_value */, 494 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 495 registry->RegisterStringPref( 496 prefs::kEnterpriseWebStoreName, 497 std::string() /* default_value */, 498 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 499} 500 501} // namespace extensions 502