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.
5 #include "chrome/browser/extensions/component_loader.h"
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/chrome_version_info.h"
22 #include "chrome/common/extensions/extension.h"
23 #include "chrome/common/extensions/extension_file_util.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 "extensions/common/manifest_constants.h"
29 #include "grit/browser_resources.h"
30 #include "grit/generated_resources.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/resource/resource_bundle.h"
35 #include "grit/keyboard_resources.h"
36 #include "ui/keyboard/keyboard_util.h"
39 #if defined(GOOGLE_CHROME_BUILD)
40 #include "chrome/browser/defaults.h"
43 #if defined(OS_CHROMEOS)
44 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
45 #include "chrome/browser/chromeos/login/user_manager.h"
46 #include "chrome/browser/extensions/extension_service.h"
47 #include "chrome/browser/extensions/extension_system.h"
48 #include "chrome/browser/profiles/profile.h"
49 #include "chrome/browser/profiles/profile_manager.h"
50 #include "chromeos/chromeos_switches.h"
51 #include "content/public/browser/storage_partition.h"
52 #include "webkit/browser/fileapi/file_system_context.h"
55 #if defined(ENABLE_APP_LIST)
56 #include "grit/chromium_strings.h"
59 namespace extensions {
63 static bool enable_background_extensions_during_testing = false;
65 std::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";
73 typedef std::map<std::string, int> NameMap;
74 CR_DEFINE_STATIC_LOCAL(NameMap, names, ());
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));
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);
89 std::string GenerateId(const base::DictionaryValue* manifest,
90 const base::FilePath& path) {
93 CHECK(manifest->GetString(manifest_keys::kPublicKey, &raw_key));
94 CHECK(Extension::ParsePEMKeyBytes(raw_key, &id_input));
95 std::string id = id_util::GenerateId(id_input);
101 ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo(
102 const base::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);
109 extension_id = GenerateId(manifest, root_directory);
112 ComponentLoader::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) {}
119 ComponentLoader::~ComponentLoader() {
120 ClearAllRegistered();
123 void ComponentLoader::LoadAll() {
124 for (RegisteredComponentExtensions::iterator it =
125 component_extensions_.begin();
126 it != component_extensions_.end(); ++it) {
131 base::DictionaryValue* ComponentLoader::ParseManifest(
132 const std::string& manifest_contents) const {
133 JSONStringValueSerializer serializer(manifest_contents);
134 scoped_ptr<base::Value> manifest(serializer.Deserialize(NULL, NULL));
136 if (!manifest.get() || !manifest->IsType(base::Value::TYPE_DICTIONARY)) {
137 LOG(ERROR) << "Failed to parse extension manifest.";
140 // Transfer ownership to the caller.
141 return static_cast<base::DictionaryValue*>(manifest.release());
144 void ComponentLoader::ClearAllRegistered() {
145 for (RegisteredComponentExtensions::iterator it =
146 component_extensions_.begin();
147 it != component_extensions_.end(); ++it) {
151 component_extensions_.clear();
154 std::string ComponentLoader::GetExtensionID(
155 int manifest_resource_id,
156 const base::FilePath& root_directory) {
157 std::string manifest_contents = ResourceBundle::GetSharedInstance().
158 GetRawDataResource(manifest_resource_id).as_string();
159 base::DictionaryValue* manifest = ParseManifest(manifest_contents);
161 return std::string();
163 ComponentExtensionInfo info(manifest, root_directory);
164 return info.extension_id;
167 std::string ComponentLoader::Add(int manifest_resource_id,
168 const base::FilePath& root_directory) {
169 std::string manifest_contents =
170 ResourceBundle::GetSharedInstance().GetRawDataResource(
171 manifest_resource_id).as_string();
172 return Add(manifest_contents, root_directory);
175 std::string ComponentLoader::Add(const std::string& manifest_contents,
176 const base::FilePath& root_directory) {
177 // The Value is kept for the lifetime of the ComponentLoader. This is
178 // required in case LoadAll() is called again.
179 base::DictionaryValue* manifest = ParseManifest(manifest_contents);
181 return Add(manifest, root_directory);
182 return std::string();
185 std::string ComponentLoader::Add(const base::DictionaryValue* parsed_manifest,
186 const base::FilePath& root_directory) {
187 ComponentExtensionInfo info(parsed_manifest, root_directory);
188 component_extensions_.push_back(info);
189 if (extension_service_->is_ready())
191 return info.extension_id;
194 std::string ComponentLoader::AddOrReplace(const base::FilePath& path) {
195 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path);
197 scoped_ptr<base::DictionaryValue> manifest(
198 extension_file_util::LoadManifest(absolute_path, &error));
200 LOG(ERROR) << "Could not load extension from '" <<
201 absolute_path.value() << "'. " << error;
202 return std::string();
204 Remove(GenerateId(manifest.get(), absolute_path));
206 return Add(manifest.release(), absolute_path);
209 void ComponentLoader::Reload(const std::string& extension_id) {
210 for (RegisteredComponentExtensions::iterator it =
211 component_extensions_.begin(); it != component_extensions_.end();
213 if (it->extension_id == extension_id) {
220 void ComponentLoader::Load(const ComponentExtensionInfo& info) {
221 // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated
222 // our component extensions to the new manifest version.
223 int flags = Extension::REQUIRE_KEY;
227 scoped_refptr<const Extension> extension(Extension::Create(
233 if (!extension.get()) {
238 CHECK_EQ(info.extension_id, extension->id()) << extension->name();
239 extension_service_->AddComponentExtension(extension.get());
242 void ComponentLoader::Remove(const base::FilePath& root_directory) {
243 // Find the ComponentExtensionInfo for the extension.
244 RegisteredComponentExtensions::iterator it = component_extensions_.begin();
245 for (; it != component_extensions_.end(); ++it) {
246 if (it->root_directory == root_directory) {
247 Remove(GenerateId(it->manifest, root_directory));
253 void ComponentLoader::Remove(const std::string& id) {
254 RegisteredComponentExtensions::iterator it = component_extensions_.begin();
255 for (; it != component_extensions_.end(); ++it) {
256 if (it->extension_id == id) {
257 UnloadComponent(&(*it));
258 it = component_extensions_.erase(it);
264 bool ComponentLoader::Exists(const std::string& id) const {
265 RegisteredComponentExtensions::const_iterator it =
266 component_extensions_.begin();
267 for (; it != component_extensions_.end(); ++it)
268 if (it->extension_id == id)
273 void ComponentLoader::AddFileManagerExtension() {
274 #if defined(FILE_MANAGER_EXTENSION)
276 const CommandLine* command_line = CommandLine::ForCurrentProcess();
277 if (command_line->HasSwitch(switches::kFileManagerExtensionPath)) {
278 base::FilePath filemgr_extension_path(
279 command_line->GetSwitchValuePath(switches::kFileManagerExtensionPath));
280 Add(IDR_FILEMANAGER_MANIFEST, filemgr_extension_path);
284 Add(IDR_FILEMANAGER_MANIFEST,
285 base::FilePath(FILE_PATH_LITERAL("file_manager")));
286 #endif // defined(FILE_MANAGER_EXTENSION)
289 void ComponentLoader::AddHangoutServicesExtension() {
290 Add(IDR_HANGOUT_SERVICES_MANIFEST,
291 base::FilePath(FILE_PATH_LITERAL("hangout_services")));
294 void ComponentLoader::AddImageLoaderExtension() {
295 #if defined(IMAGE_LOADER_EXTENSION)
297 const CommandLine* command_line = CommandLine::ForCurrentProcess();
298 if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) {
299 base::FilePath image_loader_extension_path(
300 command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath));
301 Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path);
305 Add(IDR_IMAGE_LOADER_MANIFEST,
306 base::FilePath(FILE_PATH_LITERAL("image_loader")));
307 #endif // defined(IMAGE_LOADER_EXTENSION)
310 void ComponentLoader::AddBookmarksExtensions() {
311 Add(IDR_BOOKMARKS_MANIFEST,
312 base::FilePath(FILE_PATH_LITERAL("bookmark_manager")));
313 #if defined(ENABLE_ENHANCED_BOOKMARKS)
314 Add(IDR_ENHANCED_BOOKMARKS_MANIFEST,
315 base::FilePath(FILE_PATH_LITERAL("enhanced_bookmark_manager")));
319 void ComponentLoader::AddNetworkSpeechSynthesisExtension() {
320 Add(IDR_NETWORK_SPEECH_SYNTHESIS_MANIFEST,
321 base::FilePath(FILE_PATH_LITERAL("network_speech_synthesis")));
324 void ComponentLoader::AddWithName(int manifest_resource_id,
325 const base::FilePath& root_directory,
326 const std::string& name) {
327 std::string manifest_contents =
328 ResourceBundle::GetSharedInstance().GetRawDataResource(
329 manifest_resource_id).as_string();
331 // The Value is kept for the lifetime of the ComponentLoader. This is
332 // required in case LoadAll() is called again.
333 base::DictionaryValue* manifest = ParseManifest(manifest_contents);
336 // Update manifest to use a proper name.
337 manifest->SetString(manifest_keys::kName, name);
338 Add(manifest, root_directory);
342 void ComponentLoader::AddChromeApp() {
343 #if defined(ENABLE_APP_LIST)
344 AddWithName(IDR_CHROME_APP_MANIFEST,
345 base::FilePath(FILE_PATH_LITERAL("chrome_app")),
346 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME));
350 void ComponentLoader::AddKeyboardApp() {
351 #if defined(USE_AURA)
352 if (keyboard::IsKeyboardEnabled())
353 Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard")));
357 void ComponentLoader::AddWebStoreApp() {
358 AddWithName(IDR_WEBSTORE_MANIFEST,
359 base::FilePath(FILE_PATH_LITERAL("web_store")),
360 LookupWebstoreName());
364 void ComponentLoader::EnableBackgroundExtensionsForTesting() {
365 enable_background_extensions_during_testing = true;
368 void ComponentLoader::AddDefaultComponentExtensions(
369 bool skip_session_components) {
370 // Do not add component extensions that have background pages here -- add them
371 // to AddDefaultComponentExtensionsWithBackgroundPages.
372 #if defined(OS_CHROMEOS)
373 Add(IDR_MOBILE_MANIFEST,
374 base::FilePath(FILE_PATH_LITERAL("/usr/share/chromeos-assets/mobile")));
376 #if defined(GOOGLE_CHROME_BUILD)
378 const CommandLine* command_line = CommandLine::ForCurrentProcess();
379 if (!command_line->HasSwitch(chromeos::switches::kDisableGeniusApp)) {
380 AddWithName(IDR_GENIUS_APP_MANIFEST,
381 base::FilePath(FILE_PATH_LITERAL(
382 "/usr/share/chromeos-assets/genius_app")),
383 l10n_util::GetStringUTF8(IDS_GENIUS_APP_NAME));
386 if (browser_defaults::enable_help_app) {
387 Add(IDR_HELP_MANIFEST, base::FilePath(FILE_PATH_LITERAL(
388 "/usr/share/chromeos-assets/helpapp")));
392 // Skip all other extensions that require user session presence.
393 if (!skip_session_components) {
394 const CommandLine* command_line = CommandLine::ForCurrentProcess();
395 if (!command_line->HasSwitch(chromeos::switches::kGuestSession))
396 AddBookmarksExtensions();
398 Add(IDR_CROSH_BUILTIN_MANIFEST, base::FilePath(FILE_PATH_LITERAL(
399 "/usr/share/chromeos-assets/crosh_builtin")));
401 #else // !defined(OS_CHROMEOS)
402 DCHECK(!skip_session_components);
403 AddBookmarksExtensions();
404 // Cloud Print component app. Not required on Chrome OS.
405 Add(IDR_CLOUDPRINT_MANIFEST,
406 base::FilePath(FILE_PATH_LITERAL("cloud_print")));
409 if (!skip_session_components) {
416 AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components);
419 void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages(
420 bool skip_session_components) {
421 const CommandLine* command_line = CommandLine::ForCurrentProcess();
423 // Component extensions with background pages are not enabled during tests
424 // because they generate a lot of background behavior that can interfere.
425 if (!enable_background_extensions_during_testing &&
426 (command_line->HasSwitch(switches::kTestType) ||
427 command_line->HasSwitch(
428 switches::kDisableComponentExtensionsWithBackgroundPages))) {
432 if (!skip_session_components) {
434 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAppsDevtool) &&
435 profile_prefs_->GetBoolean(prefs::kExtensionsUIDeveloperMode)) {
436 Add(IDR_APPS_DEBUGGER_MANIFEST,
437 base::FilePath(FILE_PATH_LITERAL("apps_debugger")));
441 AddFileManagerExtension();
442 AddHangoutServicesExtension();
443 AddImageLoaderExtension();
445 #if defined(ENABLE_SETTINGS_APP)
446 Add(IDR_SETTINGS_APP_MANIFEST,
447 base::FilePath(FILE_PATH_LITERAL("settings_app")));
451 // If (!enable_background_extensions_during_testing || this isn't a test)
452 // install_feedback = false;
453 bool install_feedback = enable_background_extensions_during_testing;
454 #if defined(GOOGLE_CHROME_BUILD)
455 install_feedback = true;
456 #endif // defined(GOOGLE_CHROME_BUILD)
457 if (install_feedback)
458 Add(IDR_FEEDBACK_MANIFEST, base::FilePath(FILE_PATH_LITERAL("feedback")));
460 #if defined(OS_CHROMEOS)
461 if (!skip_session_components) {
462 #if defined(GOOGLE_CHROME_BUILD)
463 if (!command_line->HasSwitch(
464 chromeos::switches::kDisableQuickofficeComponentApp)) {
465 int manifest_id = IDR_QUICKOFFICE_EDITOR_MANIFEST;
466 if (command_line->HasSwitch(switches::kEnableQuickofficeViewing)) {
467 manifest_id = IDR_QUICKOFFICE_VIEWING_MANIFEST;
469 std::string id = Add(manifest_id, base::FilePath(
470 FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office")));
471 if (command_line->HasSwitch(chromeos::switches::kGuestSession)) {
472 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for
473 // Quickoffice. It doesn't work without temporary file system access.
474 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
475 ExtensionService* service =
476 extensions::ExtensionSystem::Get(profile)->extension_service();
477 GURL site = service->GetSiteForExtensionId(id);
478 fileapi::FileSystemContext* context =
479 content::BrowserContext::GetStoragePartitionForSite(profile, site)->
480 GetFileSystemContext();
481 context->EnableTemporaryFileSystemInIncognito();
484 #endif // defined(GOOGLE_CHROME_BUILD)
486 base::FilePath echo_extension_path(FILE_PATH_LITERAL(
487 "/usr/share/chromeos-assets/echo"));
488 if (command_line->HasSwitch(chromeos::switches::kEchoExtensionPath)) {
489 echo_extension_path = command_line->GetSwitchValuePath(
490 chromeos::switches::kEchoExtensionPath);
492 Add(IDR_ECHO_MANIFEST, echo_extension_path);
494 if (!command_line->HasSwitch(chromeos::switches::kGuestSession)) {
495 Add(IDR_WALLPAPERMANAGER_MANIFEST,
496 base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager")));
499 Add(IDR_NETWORK_CONFIGURATION_MANIFEST,
500 base::FilePath(FILE_PATH_LITERAL("chromeos/network_configuration")));
502 Add(IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST,
503 base::FilePath(extension_misc::kConnectivityDiagnosticsPath));
504 Add(IDR_CONNECTIVITY_DIAGNOSTICS_LAUNCHER_MANIFEST,
505 base::FilePath(extension_misc::kConnectivityDiagnosticsLauncherPath));
508 // Load ChromeVox extension now if spoken feedback is enabled.
509 if (chromeos::AccessibilityManager::Get() &&
510 chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) {
511 base::FilePath path =
512 base::FilePath(extension_misc::kChromeVoxExtensionPath);
513 Add(IDR_CHROMEVOX_MANIFEST, path);
515 #endif // defined(OS_CHROMEOS)
517 #if defined(ENABLE_GOOGLE_NOW)
518 const char kEnablePrefix[] = "Enable";
519 const char kFieldTrialName[] = "GoogleNow";
520 std::string enable_prefix(kEnablePrefix);
521 std::string field_trial_result =
522 base::FieldTrialList::FindFullName(kFieldTrialName);
524 bool enabled_via_field_trial = field_trial_result.compare(
526 enable_prefix.length(),
529 // Enable the feature on trybots.
530 bool enabled_via_trunk_build = chrome::VersionInfo::GetChannel() ==
531 chrome::VersionInfo::CHANNEL_UNKNOWN;
533 bool enabled_via_flag =
534 chrome::VersionInfo::GetChannel() !=
535 chrome::VersionInfo::CHANNEL_STABLE &&
536 CommandLine::ForCurrentProcess()->HasSwitch(
537 switches::kEnableGoogleNowIntegration);
540 enabled_via_field_trial || enabled_via_trunk_build || enabled_via_flag;
542 bool disabled_via_flag =
543 CommandLine::ForCurrentProcess()->HasSwitch(
544 switches::kDisableGoogleNowIntegration);
546 if (enabled && !disabled_via_flag) {
547 Add(IDR_GOOGLE_NOW_MANIFEST,
548 base::FilePath(FILE_PATH_LITERAL("google_now")));
552 #if defined(GOOGLE_CHROME_BUILD)
553 AddNetworkSpeechSynthesisExtension();
554 #endif // defined(GOOGLE_CHROME_BUILD)
557 void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) {
558 delete component->manifest;
559 if (extension_service_->is_ready()) {
561 RemoveComponentExtension(component->extension_id);
565 } // namespace extensions