component_loader.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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/pref_names.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 "extensions/common/manifest_constants.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#endif 53 54#if defined(ENABLE_APP_LIST) 55#include "grit/chromium_strings.h" 56#endif 57 58namespace extensions { 59 60namespace { 61 62static bool enable_background_extensions_during_testing = false; 63 64std::string LookupWebstoreName() { 65 const char kWebStoreNameFieldTrialName[] = "WebStoreName"; 66 const char kStoreControl[] = "StoreControl"; 67 const char kWebStore[] = "WebStore"; 68 const char kGetApps[] = "GetApps"; 69 const char kAddApps[] = "AddApps"; 70 const char kMoreApps[] = "MoreApps"; 71 72 typedef std::map<std::string, int> NameMap; 73 CR_DEFINE_STATIC_LOCAL(NameMap, names, ()); 74 if (names.empty()) { 75 names.insert(std::make_pair(kStoreControl, IDS_WEBSTORE_NAME_STORE)); 76 names.insert(std::make_pair(kWebStore, IDS_WEBSTORE_NAME_WEBSTORE)); 77 names.insert(std::make_pair(kGetApps, IDS_WEBSTORE_NAME_GET_APPS)); 78 names.insert(std::make_pair(kAddApps, IDS_WEBSTORE_NAME_ADD_APPS)); 79 names.insert(std::make_pair(kMoreApps, IDS_WEBSTORE_NAME_MORE_APPS)); 80 } 81 std::string field_trial_name = 82 base::FieldTrialList::FindFullName(kWebStoreNameFieldTrialName); 83 NameMap::iterator it = names.find(field_trial_name); 84 int string_id = it == names.end() ? names[kStoreControl] : it->second; 85 return l10n_util::GetStringUTF8(string_id); 86} 87 88std::string GenerateId(const base::DictionaryValue* manifest, 89 const base::FilePath& path) { 90 std::string raw_key; 91 std::string id_input; 92 CHECK(manifest->GetString(manifest_keys::kPublicKey, &raw_key)); 93 CHECK(Extension::ParsePEMKeyBytes(raw_key, &id_input)); 94 std::string id = id_util::GenerateId(id_input); 95 return id; 96} 97 98} // namespace 99 100ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( 101 const base::DictionaryValue* manifest, const base::FilePath& directory) 102 : manifest(manifest), 103 root_directory(directory) { 104 if (!root_directory.IsAbsolute()) { 105 CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory)); 106 root_directory = root_directory.Append(directory); 107 } 108 extension_id = GenerateId(manifest, root_directory); 109} 110 111ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service, 112 PrefService* profile_prefs, 113 PrefService* local_state) 114 : profile_prefs_(profile_prefs), 115 local_state_(local_state), 116 extension_service_(extension_service) {} 117 118ComponentLoader::~ComponentLoader() { 119 ClearAllRegistered(); 120} 121 122void ComponentLoader::LoadAll() { 123 for (RegisteredComponentExtensions::iterator it = 124 component_extensions_.begin(); 125 it != component_extensions_.end(); ++it) { 126 Load(*it); 127 } 128} 129 130base::DictionaryValue* ComponentLoader::ParseManifest( 131 const std::string& manifest_contents) const { 132 JSONStringValueSerializer serializer(manifest_contents); 133 scoped_ptr<base::Value> manifest(serializer.Deserialize(NULL, NULL)); 134 135 if (!manifest.get() || !manifest->IsType(base::Value::TYPE_DICTIONARY)) { 136 LOG(ERROR) << "Failed to parse extension manifest."; 137 return NULL; 138 } 139 // Transfer ownership to the caller. 140 return static_cast<base::DictionaryValue*>(manifest.release()); 141} 142 143void ComponentLoader::ClearAllRegistered() { 144 for (RegisteredComponentExtensions::iterator it = 145 component_extensions_.begin(); 146 it != component_extensions_.end(); ++it) { 147 delete it->manifest; 148 } 149 150 component_extensions_.clear(); 151} 152 153std::string ComponentLoader::GetExtensionID( 154 int manifest_resource_id, 155 const base::FilePath& root_directory) { 156 std::string manifest_contents = ResourceBundle::GetSharedInstance(). 157 GetRawDataResource(manifest_resource_id).as_string(); 158 base::DictionaryValue* manifest = ParseManifest(manifest_contents); 159 if (!manifest) 160 return std::string(); 161 162 ComponentExtensionInfo info(manifest, root_directory); 163 return info.extension_id; 164} 165 166std::string ComponentLoader::Add(int manifest_resource_id, 167 const base::FilePath& root_directory) { 168 std::string manifest_contents = 169 ResourceBundle::GetSharedInstance().GetRawDataResource( 170 manifest_resource_id).as_string(); 171 return Add(manifest_contents, root_directory); 172} 173 174std::string ComponentLoader::Add(const std::string& manifest_contents, 175 const base::FilePath& root_directory) { 176 // The Value is kept for the lifetime of the ComponentLoader. This is 177 // required in case LoadAll() is called again. 178 base::DictionaryValue* manifest = ParseManifest(manifest_contents); 179 if (manifest) 180 return Add(manifest, root_directory); 181 return std::string(); 182} 183 184std::string ComponentLoader::Add(const base::DictionaryValue* parsed_manifest, 185 const base::FilePath& root_directory) { 186 ComponentExtensionInfo info(parsed_manifest, root_directory); 187 component_extensions_.push_back(info); 188 if (extension_service_->is_ready()) 189 Load(info); 190 return info.extension_id; 191} 192 193std::string ComponentLoader::AddOrReplace(const base::FilePath& path) { 194 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); 195 std::string error; 196 scoped_ptr<base::DictionaryValue> manifest( 197 extension_file_util::LoadManifest(absolute_path, &error)); 198 if (!manifest) { 199 LOG(ERROR) << "Could not load extension from '" << 200 absolute_path.value() << "'. " << error; 201 return std::string(); 202 } 203 Remove(GenerateId(manifest.get(), absolute_path)); 204 205 return Add(manifest.release(), absolute_path); 206} 207 208void ComponentLoader::Reload(const std::string& extension_id) { 209 for (RegisteredComponentExtensions::iterator it = 210 component_extensions_.begin(); it != component_extensions_.end(); 211 ++it) { 212 if (it->extension_id == extension_id) { 213 Load(*it); 214 break; 215 } 216 } 217} 218 219void ComponentLoader::Load(const ComponentExtensionInfo& info) { 220 // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated 221 // our component extensions to the new manifest version. 222 int flags = Extension::REQUIRE_KEY; 223 224 std::string error; 225 226 scoped_refptr<const Extension> extension(Extension::Create( 227 info.root_directory, 228 Manifest::COMPONENT, 229 *info.manifest, 230 flags, 231 &error)); 232 if (!extension.get()) { 233 LOG(ERROR) << error; 234 return; 235 } 236 237 CHECK_EQ(info.extension_id, extension->id()) << extension->name(); 238 extension_service_->AddComponentExtension(extension.get()); 239} 240 241void ComponentLoader::Remove(const base::FilePath& root_directory) { 242 // Find the ComponentExtensionInfo for the extension. 243 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 244 for (; it != component_extensions_.end(); ++it) { 245 if (it->root_directory == root_directory) { 246 Remove(GenerateId(it->manifest, root_directory)); 247 break; 248 } 249 } 250} 251 252void ComponentLoader::Remove(const std::string& id) { 253 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 254 for (; it != component_extensions_.end(); ++it) { 255 if (it->extension_id == id) { 256 UnloadComponent(&(*it)); 257 it = component_extensions_.erase(it); 258 break; 259 } 260 } 261} 262 263bool ComponentLoader::Exists(const std::string& id) const { 264 RegisteredComponentExtensions::const_iterator it = 265 component_extensions_.begin(); 266 for (; it != component_extensions_.end(); ++it) 267 if (it->extension_id == id) 268 return true; 269 return false; 270} 271 272void ComponentLoader::AddFileManagerExtension() { 273#if defined(FILE_MANAGER_EXTENSION) 274#ifndef NDEBUG 275 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 276 if (command_line->HasSwitch(switches::kFileManagerExtensionPath)) { 277 base::FilePath filemgr_extension_path( 278 command_line->GetSwitchValuePath(switches::kFileManagerExtensionPath)); 279 Add(IDR_FILEMANAGER_MANIFEST, filemgr_extension_path); 280 return; 281 } 282#endif // NDEBUG 283 Add(IDR_FILEMANAGER_MANIFEST, 284 base::FilePath(FILE_PATH_LITERAL("file_manager"))); 285#endif // defined(FILE_MANAGER_EXTENSION) 286} 287 288void ComponentLoader::AddImageLoaderExtension() { 289#if defined(IMAGE_LOADER_EXTENSION) 290#ifndef NDEBUG 291 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 292 if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) { 293 base::FilePath image_loader_extension_path( 294 command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath)); 295 Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path); 296 return; 297 } 298#endif // NDEBUG 299 Add(IDR_IMAGE_LOADER_MANIFEST, 300 base::FilePath(FILE_PATH_LITERAL("image_loader"))); 301#endif // defined(IMAGE_LOADER_EXTENSION) 302} 303 304void ComponentLoader::AddBookmarksExtensions() { 305 Add(IDR_BOOKMARKS_MANIFEST, 306 base::FilePath(FILE_PATH_LITERAL("bookmark_manager"))); 307#if defined(ENABLE_ENHANCED_BOOKMARKS) 308 Add(IDR_ENHANCED_BOOKMARKS_MANIFEST, 309 base::FilePath(FILE_PATH_LITERAL("enhanced_bookmark_manager"))); 310#endif 311} 312 313void ComponentLoader::AddWithName(int manifest_resource_id, 314 const base::FilePath& root_directory, 315 const std::string& name) { 316 std::string manifest_contents = 317 ResourceBundle::GetSharedInstance().GetRawDataResource( 318 manifest_resource_id).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 base::DictionaryValue* manifest = ParseManifest(manifest_contents); 323 324 if (manifest) { 325 // Update manifest to use a proper name. 326 manifest->SetString(manifest_keys::kName, name); 327 Add(manifest, root_directory); 328 } 329} 330 331void ComponentLoader::AddChromeApp() { 332#if defined(ENABLE_APP_LIST) 333 AddWithName(IDR_CHROME_APP_MANIFEST, 334 base::FilePath(FILE_PATH_LITERAL("chrome_app")), 335 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME)); 336#endif 337} 338 339void ComponentLoader::AddKeyboardApp() { 340#if defined(USE_AURA) 341 if (keyboard::IsKeyboardEnabled()) 342 Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard"))); 343#endif 344} 345 346void ComponentLoader::AddWebStoreApp() { 347 AddWithName(IDR_WEBSTORE_MANIFEST, 348 base::FilePath(FILE_PATH_LITERAL("web_store")), 349 LookupWebstoreName()); 350} 351 352// static 353void ComponentLoader::EnableBackgroundExtensionsForTesting() { 354 enable_background_extensions_during_testing = true; 355} 356 357void ComponentLoader::AddDefaultComponentExtensions( 358 bool skip_session_components) { 359 // Do not add component extensions that have background pages here -- add them 360 // to AddDefaultComponentExtensionsWithBackgroundPages. 361#if defined(OS_CHROMEOS) 362 Add(IDR_MOBILE_MANIFEST, 363 base::FilePath(FILE_PATH_LITERAL("/usr/share/chromeos-assets/mobile"))); 364 365#if defined(GOOGLE_CHROME_BUILD) 366 { 367 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 368 if (!command_line->HasSwitch(chromeos::switches::kDisableGeniusApp)) { 369 AddWithName(IDR_GENIUS_APP_MANIFEST, 370 base::FilePath(FILE_PATH_LITERAL( 371 "/usr/share/chromeos-assets/genius_app")), 372 l10n_util::GetStringUTF8(IDS_GENIUS_APP_NAME)); 373 } 374 } 375 if (browser_defaults::enable_help_app) { 376 Add(IDR_HELP_MANIFEST, base::FilePath(FILE_PATH_LITERAL( 377 "/usr/share/chromeos-assets/helpapp"))); 378 } 379#endif 380 381 // Skip all other extensions that require user session presence. 382 if (!skip_session_components) { 383 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 384 if (!command_line->HasSwitch(chromeos::switches::kGuestSession)) 385 AddBookmarksExtensions(); 386 387 Add(IDR_CROSH_BUILTIN_MANIFEST, base::FilePath(FILE_PATH_LITERAL( 388 "/usr/share/chromeos-assets/crosh_builtin"))); 389 } 390#else // !defined(OS_CHROMEOS) 391 DCHECK(!skip_session_components); 392 AddBookmarksExtensions(); 393 // Cloud Print component app. Not required on Chrome OS. 394 Add(IDR_CLOUDPRINT_MANIFEST, 395 base::FilePath(FILE_PATH_LITERAL("cloud_print"))); 396#endif 397 398 if (!skip_session_components) { 399 AddWebStoreApp(); 400 AddChromeApp(); 401 } 402 403 AddKeyboardApp(); 404 405 AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components); 406} 407 408void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages( 409 bool skip_session_components) { 410 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 411 412 // Component extensions with background pages are not enabled during tests 413 // because they generate a lot of background behavior that can interfere. 414 if (!enable_background_extensions_during_testing && 415 (command_line->HasSwitch(switches::kTestType) || 416 command_line->HasSwitch( 417 switches::kDisableComponentExtensionsWithBackgroundPages))) { 418 return; 419 } 420 421 if (!skip_session_components) { 422 // Apps Debugger 423 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAppsDevtool) && 424 profile_prefs_->GetBoolean(prefs::kExtensionsUIDeveloperMode)) { 425 Add(IDR_APPS_DEBUGGER_MANIFEST, 426 base::FilePath(FILE_PATH_LITERAL("apps_debugger"))); 427 } 428 429 AddFileManagerExtension(); 430 AddImageLoaderExtension(); 431 432#if defined(ENABLE_SETTINGS_APP) 433 Add(IDR_SETTINGS_APP_MANIFEST, 434 base::FilePath(FILE_PATH_LITERAL("settings_app"))); 435#endif 436 } 437 438 // If (!enable_background_extensions_during_testing || this isn't a test) 439 // install_feedback = false; 440 bool install_feedback = enable_background_extensions_during_testing; 441#if defined(GOOGLE_CHROME_BUILD) 442 install_feedback = true; 443#endif // defined(GOOGLE_CHROME_BUILD) 444 if (install_feedback) 445 Add(IDR_FEEDBACK_MANIFEST, base::FilePath(FILE_PATH_LITERAL("feedback"))); 446 447#if defined(OS_CHROMEOS) 448 if (!skip_session_components && 449 !command_line->HasSwitch(chromeos::switches::kGuestSession)) { 450 Add(IDR_WALLPAPERMANAGER_MANIFEST, 451 base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager"))); 452 453#if defined(GOOGLE_CHROME_BUILD) 454 if (!command_line->HasSwitch( 455 chromeos::switches::kDisableQuickofficeComponentApp)) { 456 int manifest_id = IDR_QUICKOFFICE_EDITOR_MANIFEST; 457 if (command_line->HasSwitch(switches::kEnableQuickofficeViewing)) { 458 manifest_id = IDR_QUICKOFFICE_VIEWING_MANIFEST; 459 } 460 std::string id = Add(manifest_id, base::FilePath( 461 FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office"))); 462 if (command_line->HasSwitch(chromeos::switches::kGuestSession)) { 463 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for 464 // Quickoffice. It doesn't work without temporary file system access. 465 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord(); 466 ExtensionService* service = 467 extensions::ExtensionSystem::Get(profile)->extension_service(); 468 GURL site = service->GetSiteForExtensionId(id); 469 fileapi::FileSystemContext* context = 470 content::BrowserContext::GetStoragePartitionForSite(profile, site)-> 471 GetFileSystemContext(); 472 context->EnableTemporaryFileSystemInIncognito(); 473 } 474 } 475#endif // defined(GOOGLE_CHROME_BUILD) 476 477 base::FilePath echo_extension_path(FILE_PATH_LITERAL( 478 "/usr/share/chromeos-assets/echo")); 479 if (command_line->HasSwitch(chromeos::switches::kEchoExtensionPath)) { 480 echo_extension_path = command_line->GetSwitchValuePath( 481 chromeos::switches::kEchoExtensionPath); 482 } 483 Add(IDR_ECHO_MANIFEST, echo_extension_path); 484 485 Add(IDR_NETWORK_CONFIGURATION_MANIFEST, 486 base::FilePath(FILE_PATH_LITERAL("chromeos/network_configuration"))); 487 488 Add(IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST, 489 base::FilePath(extension_misc::kConnectivityDiagnosticsPath)); 490 Add(IDR_CONNECTIVITY_DIAGNOSTICS_LAUNCHER_MANIFEST, 491 base::FilePath(extension_misc::kConnectivityDiagnosticsLauncherPath)); 492 } 493 494 // Load ChromeVox extension now if spoken feedback is enabled. 495 if (chromeos::AccessibilityManager::Get() && 496 chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) { 497 base::FilePath path = 498 base::FilePath(extension_misc::kChromeVoxExtensionPath); 499 Add(IDR_CHROMEVOX_MANIFEST, path); 500 } 501#endif // defined(OS_CHROMEOS) 502 503#if defined(ENABLE_GOOGLE_NOW) 504 const char kEnablePrefix[] = "Enable"; 505 const char kFieldTrialName[] = "GoogleNow"; 506 std::string enable_prefix(kEnablePrefix); 507 std::string field_trial_result = 508 base::FieldTrialList::FindFullName(kFieldTrialName); 509 if (((field_trial_result.compare( 510 0, 511 enable_prefix.length(), 512 enable_prefix) == 0) && 513 !CommandLine::ForCurrentProcess()->HasSwitch( 514 switches::kDisableGoogleNowIntegration)) || 515 CommandLine::ForCurrentProcess()->HasSwitch( 516 switches::kEnableGoogleNowIntegration)) { 517 Add(IDR_GOOGLE_NOW_MANIFEST, 518 base::FilePath(FILE_PATH_LITERAL("google_now"))); 519 } 520#endif 521} 522 523void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { 524 delete component->manifest; 525 if (extension_service_->is_ready()) { 526 extension_service_-> 527 RemoveComponentExtension(component->extension_id); 528 } 529} 530 531} // namespace extensions 532