component_loader.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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 <map> 8#include <string> 9 10#include "base/command_line.h" 11#include "base/file_util.h" 12#include "base/json/json_string_value_serializer.h" 13#include "base/metrics/field_trial.h" 14#include "base/path_service.h" 15#include "base/prefs/pref_change_registrar.h" 16#include "chrome/browser/chrome_notification_types.h" 17#include "chrome/browser/extensions/extension_service.h" 18#include "chrome/browser/profiles/profile.h" 19#include "chrome/common/chrome_paths.h" 20#include "chrome/common/chrome_switches.h" 21#include "chrome/common/extensions/extension.h" 22#include "chrome/common/extensions/extension_file_util.h" 23#include "chrome/common/extensions/extension_manifest_constants.h" 24#include "chrome/common/pref_names.h" 25#include "content/public/browser/notification_details.h" 26#include "content/public/browser/notification_source.h" 27#include "extensions/common/id_util.h" 28#include "grit/browser_resources.h" 29#include "grit/generated_resources.h" 30#include "ui/base/l10n/l10n_util.h" 31#include "ui/base/resource/resource_bundle.h" 32 33#if defined(USE_AURA) 34#include "grit/keyboard_resources.h" 35#include "ui/keyboard/keyboard_util.h" 36#endif 37 38#if defined(GOOGLE_CHROME_BUILD) 39#include "chrome/browser/defaults.h" 40#endif 41 42#if defined(OS_CHROMEOS) 43#include "chrome/browser/chromeos/accessibility/accessibility_manager.h" 44#include "chrome/browser/chromeos/login/user_manager.h" 45#include "chrome/browser/extensions/extension_service.h" 46#include "chrome/browser/extensions/extension_system.h" 47#include "chrome/browser/profiles/profile.h" 48#include "chrome/browser/profiles/profile_manager.h" 49#include "chromeos/chromeos_switches.h" 50#include "content/public/browser/storage_partition.h" 51#include "webkit/browser/fileapi/file_system_context.h" 52#include "webkit/browser/fileapi/sandbox_file_system_backend.h" 53#endif 54 55#if defined(ENABLE_APP_LIST) 56#include "grit/chromium_strings.h" 57#endif 58 59namespace extensions { 60 61namespace { 62 63static bool enable_background_extensions_during_testing = false; 64 65std::string LookupWebstoreName() { 66 const char kWebStoreNameFieldTrialName[] = "WebStoreName"; 67 const char kStoreControl[] = "StoreControl"; 68 const char kWebStore[] = "WebStore"; 69 const char kGetApps[] = "GetApps"; 70 const char kAddApps[] = "AddApps"; 71 const char kMoreApps[] = "MoreApps"; 72 73 typedef std::map<std::string, int> NameMap; 74 CR_DEFINE_STATIC_LOCAL(NameMap, names, ()); 75 if (names.empty()) { 76 names.insert(std::make_pair(kStoreControl, IDS_WEBSTORE_NAME_STORE)); 77 names.insert(std::make_pair(kWebStore, IDS_WEBSTORE_NAME_WEBSTORE)); 78 names.insert(std::make_pair(kGetApps, IDS_WEBSTORE_NAME_GET_APPS)); 79 names.insert(std::make_pair(kAddApps, IDS_WEBSTORE_NAME_ADD_APPS)); 80 names.insert(std::make_pair(kMoreApps, IDS_WEBSTORE_NAME_MORE_APPS)); 81 } 82 std::string field_trial_name = 83 base::FieldTrialList::FindFullName(kWebStoreNameFieldTrialName); 84 NameMap::iterator it = names.find(field_trial_name); 85 int string_id = it == names.end() ? names[kStoreControl] : it->second; 86 return l10n_util::GetStringUTF8(string_id); 87} 88 89std::string GenerateId(const DictionaryValue* manifest, 90 const base::FilePath& path) { 91 std::string raw_key; 92 std::string id_input; 93 CHECK(manifest->GetString(extension_manifest_keys::kPublicKey, &raw_key)); 94 CHECK(Extension::ParsePEMKeyBytes(raw_key, &id_input)); 95 std::string id = id_util::GenerateId(id_input); 96 return id; 97} 98 99} // namespace 100 101ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( 102 const DictionaryValue* manifest, const base::FilePath& directory) 103 : manifest(manifest), 104 root_directory(directory) { 105 if (!root_directory.IsAbsolute()) { 106 CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory)); 107 root_directory = root_directory.Append(directory); 108 } 109 extension_id = GenerateId(manifest, root_directory); 110} 111 112ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service, 113 PrefService* profile_prefs, 114 PrefService* local_state) 115 : profile_prefs_(profile_prefs), 116 local_state_(local_state), 117 extension_service_(extension_service) {} 118 119ComponentLoader::~ComponentLoader() { 120 ClearAllRegistered(); 121} 122 123void ComponentLoader::LoadAll() { 124 for (RegisteredComponentExtensions::iterator it = 125 component_extensions_.begin(); 126 it != component_extensions_.end(); ++it) { 127 Load(*it); 128 } 129} 130 131DictionaryValue* ComponentLoader::ParseManifest( 132 const std::string& manifest_contents) const { 133 JSONStringValueSerializer serializer(manifest_contents); 134 scoped_ptr<Value> manifest(serializer.Deserialize(NULL, NULL)); 135 136 if (!manifest.get() || !manifest->IsType(Value::TYPE_DICTIONARY)) { 137 LOG(ERROR) << "Failed to parse extension manifest."; 138 return NULL; 139 } 140 // Transfer ownership to the caller. 141 return static_cast<DictionaryValue*>(manifest.release()); 142} 143 144void ComponentLoader::ClearAllRegistered() { 145 for (RegisteredComponentExtensions::iterator it = 146 component_extensions_.begin(); 147 it != component_extensions_.end(); ++it) { 148 delete it->manifest; 149 } 150 151 component_extensions_.clear(); 152} 153 154std::string ComponentLoader::Add(int manifest_resource_id, 155 const base::FilePath& root_directory) { 156 std::string manifest_contents = 157 ResourceBundle::GetSharedInstance().GetRawDataResource( 158 manifest_resource_id).as_string(); 159 return Add(manifest_contents, root_directory); 160} 161 162std::string ComponentLoader::Add(const std::string& manifest_contents, 163 const base::FilePath& root_directory) { 164 // The Value is kept for the lifetime of the ComponentLoader. This is 165 // required in case LoadAll() is called again. 166 DictionaryValue* manifest = ParseManifest(manifest_contents); 167 if (manifest) 168 return Add(manifest, root_directory); 169 return std::string(); 170} 171 172std::string ComponentLoader::Add(const DictionaryValue* parsed_manifest, 173 const base::FilePath& root_directory) { 174 ComponentExtensionInfo info(parsed_manifest, root_directory); 175 component_extensions_.push_back(info); 176 if (extension_service_->is_ready()) 177 Load(info); 178 return info.extension_id; 179} 180 181std::string ComponentLoader::AddOrReplace(const base::FilePath& path) { 182 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); 183 std::string error; 184 scoped_ptr<DictionaryValue> manifest( 185 extension_file_util::LoadManifest(absolute_path, &error)); 186 if (!manifest) { 187 LOG(ERROR) << "Could not load extension from '" << 188 absolute_path.value() << "'. " << error; 189 return std::string(); 190 } 191 Remove(GenerateId(manifest.get(), absolute_path)); 192 193 return Add(manifest.release(), absolute_path); 194} 195 196void ComponentLoader::Reload(const std::string& extension_id) { 197 for (RegisteredComponentExtensions::iterator it = 198 component_extensions_.begin(); it != component_extensions_.end(); 199 ++it) { 200 if (it->extension_id == extension_id) { 201 Load(*it); 202 break; 203 } 204 } 205} 206 207void ComponentLoader::Load(const ComponentExtensionInfo& info) { 208 // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated 209 // our component extensions to the new manifest version. 210 int flags = Extension::REQUIRE_KEY; 211 212 std::string error; 213 214 scoped_refptr<const Extension> extension(Extension::Create( 215 info.root_directory, 216 Manifest::COMPONENT, 217 *info.manifest, 218 flags, 219 &error)); 220 if (!extension.get()) { 221 LOG(ERROR) << error; 222 return; 223 } 224 225 CHECK_EQ(info.extension_id, extension->id()) << extension->name(); 226 extension_service_->AddComponentExtension(extension.get()); 227} 228 229void ComponentLoader::RemoveAll() { 230 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 231 for (; it != component_extensions_.end(); ++it) 232 UnloadComponent(&(*it)); 233 234 component_extensions_.clear(); 235} 236 237void ComponentLoader::Remove(const base::FilePath& root_directory) { 238 // Find the ComponentExtensionInfo for the extension. 239 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 240 for (; it != component_extensions_.end(); ++it) { 241 if (it->root_directory == root_directory) { 242 Remove(GenerateId(it->manifest, root_directory)); 243 break; 244 } 245 } 246} 247 248void ComponentLoader::Remove(const std::string& id) { 249 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 250 for (; it != component_extensions_.end(); ++it) { 251 if (it->extension_id == id) { 252 UnloadComponent(&(*it)); 253 it = component_extensions_.erase(it); 254 break; 255 } 256 } 257} 258 259bool ComponentLoader::Exists(const std::string& id) const { 260 RegisteredComponentExtensions::const_iterator it = 261 component_extensions_.begin(); 262 for (; it != component_extensions_.end(); ++it) 263 if (it->extension_id == id) 264 return true; 265 return false; 266} 267 268void ComponentLoader::AddFileManagerExtension() { 269#if defined(FILE_MANAGER_EXTENSION) 270#ifndef NDEBUG 271 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 272 if (command_line->HasSwitch(switches::kFileManagerExtensionPath)) { 273 base::FilePath filemgr_extension_path( 274 command_line->GetSwitchValuePath(switches::kFileManagerExtensionPath)); 275 Add(IDR_FILEMANAGER_MANIFEST, filemgr_extension_path); 276 return; 277 } 278#endif // NDEBUG 279 Add(IDR_FILEMANAGER_MANIFEST, 280 base::FilePath(FILE_PATH_LITERAL("file_manager"))); 281#endif // defined(FILE_MANAGER_EXTENSION) 282} 283 284void ComponentLoader::AddImageLoaderExtension() { 285#if defined(IMAGE_LOADER_EXTENSION) 286#ifndef NDEBUG 287 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 288 if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) { 289 base::FilePath image_loader_extension_path( 290 command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath)); 291 Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path); 292 return; 293 } 294#endif // NDEBUG 295 Add(IDR_IMAGE_LOADER_MANIFEST, 296 base::FilePath(FILE_PATH_LITERAL("image_loader"))); 297#endif // defined(IMAGE_LOADER_EXTENSION) 298} 299 300void ComponentLoader::AddWithName(int manifest_resource_id, 301 const base::FilePath& root_directory, 302 const std::string& name) { 303 std::string manifest_contents = 304 ResourceBundle::GetSharedInstance().GetRawDataResource( 305 manifest_resource_id).as_string(); 306 307 // The Value is kept for the lifetime of the ComponentLoader. This is 308 // required in case LoadAll() is called again. 309 DictionaryValue* manifest = ParseManifest(manifest_contents); 310 311 if (manifest) { 312 // Update manifest to use a proper name. 313 manifest->SetString(extension_manifest_keys::kName, name); 314 Add(manifest, root_directory); 315 } 316} 317 318void ComponentLoader::AddChromeApp() { 319#if defined(ENABLE_APP_LIST) 320 AddWithName(IDR_CHROME_APP_MANIFEST, 321 base::FilePath(FILE_PATH_LITERAL("chrome_app")), 322 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME)); 323#endif 324} 325 326void ComponentLoader::AddKeyboardApp() { 327#if defined(USE_AURA) 328 if (keyboard::IsKeyboardEnabled()) 329 Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard"))); 330#endif 331} 332 333void ComponentLoader::AddWebStoreApp() { 334 AddWithName(IDR_WEBSTORE_MANIFEST, 335 base::FilePath(FILE_PATH_LITERAL("web_store")), 336 LookupWebstoreName()); 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 AddWebStoreApp(); 380 AddChromeApp(); 381 } 382 383 AddKeyboardApp(); 384 385 AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components); 386} 387 388void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages( 389 bool skip_session_components) { 390 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 391 392 // Component extensions with background pages are not enabled during tests 393 // because they generate a lot of background behavior that can interfere. 394 if (!enable_background_extensions_during_testing && 395 (command_line->HasSwitch(switches::kTestType) || 396 command_line->HasSwitch(switches::kMetricsRecordingOnly))) { 397 return; 398 } 399 400 if (!skip_session_components) { 401 // Apps Debugger 402 if (CommandLine::ForCurrentProcess()->HasSwitch( 403 switches::kAppsDevtool)) { 404 Add(IDR_APPS_DEBUGGER_MANIFEST, 405 base::FilePath(FILE_PATH_LITERAL("apps_debugger"))); 406 } 407 408 AddFileManagerExtension(); 409 AddImageLoaderExtension(); 410 411#if defined(ENABLE_SETTINGS_APP) 412 Add(IDR_SETTINGS_APP_MANIFEST, 413 base::FilePath(FILE_PATH_LITERAL("settings_app"))); 414#endif 415 } 416 417#if defined(GOOGLE_CHROME_BUILD) 418 Add(IDR_FEEDBACK_MANIFEST, base::FilePath(FILE_PATH_LITERAL("feedback"))); 419#endif // defined(GOOGLE_CHROME_BUILD) 420 421#if defined(OS_CHROMEOS) 422 if (!skip_session_components) { 423 Add(IDR_WALLPAPERMANAGER_MANIFEST, 424 base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager"))); 425 426#if defined(GOOGLE_CHROME_BUILD) 427 if (!command_line->HasSwitch( 428 chromeos::switches::kDisableQuickofficeComponentApp)) { 429 int manifest_id = IDR_QUICK_OFFICE_MANIFEST; 430 if (command_line->HasSwitch(switches::kEnableQuickofficeEdit)) { 431 manifest_id = IDR_QUICKOFFICE_EDITOR_MANIFEST; 432 } 433 std::string id = Add(manifest_id, base::FilePath( 434 FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office"))); 435 if (command_line->HasSwitch(chromeos::switches::kGuestSession)) { 436 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for 437 // Quickoffice. It doesn't work without temporary file system access. 438 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord(); 439 ExtensionService* service = 440 extensions::ExtensionSystem::Get(profile)->extension_service(); 441 GURL site = service->GetSiteForExtensionId(id); 442 fileapi::FileSystemContext* context = 443 content::BrowserContext::GetStoragePartitionForSite(profile, site)-> 444 GetFileSystemContext(); 445 context->EnableTemporaryFileSystemInIncognito(); 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(chromeos::switches::kEchoExtensionPath)) { 453 echo_extension_path = command_line->GetSwitchValuePath( 454 chromeos::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 Add(IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST, 462 base::FilePath(extension_misc::kConnectivityDiagnosticsPath)); 463 Add(IDR_CONNECTIVITY_DIAGNOSTICS_LAUNCHER_MANIFEST, 464 base::FilePath(extension_misc::kConnectivityDiagnosticsLauncherPath)); 465 } 466 467 // Load ChromeVox extension now if spoken feedback is enabled. 468 if (chromeos::AccessibilityManager::Get() && 469 chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) { 470 base::FilePath path = 471 base::FilePath(extension_misc::kChromeVoxExtensionPath); 472 Add(IDR_CHROMEVOX_MANIFEST, path); 473 } 474#endif // defined(OS_CHROMEOS) 475 476#if defined(ENABLE_GOOGLE_NOW) 477 if (base::FieldTrialList::FindFullName("GoogleNow") == "Enable" || 478 CommandLine::ForCurrentProcess()->HasSwitch( 479 switches::kEnableGoogleNowIntegration)) { 480 Add(IDR_GOOGLE_NOW_MANIFEST, 481 base::FilePath(FILE_PATH_LITERAL("google_now"))); 482 } 483#endif 484} 485 486void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { 487 delete component->manifest; 488 if (extension_service_->is_ready()) { 489 extension_service_-> 490 UnloadExtension(component->extension_id, 491 extension_misc::UNLOAD_REASON_DISABLE); 492 } 493} 494 495} // namespace extensions 496