- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / component_loader.cc
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/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"
33
34 #if defined(USE_AURA)
35 #include "grit/keyboard_resources.h"
36 #include "ui/keyboard/keyboard_util.h"
37 #endif
38
39 #if defined(GOOGLE_CHROME_BUILD)
40 #include "chrome/browser/defaults.h"
41 #endif
42
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"
53 #endif
54
55 #if defined(ENABLE_APP_LIST)
56 #include "grit/chromium_strings.h"
57 #endif
58
59 namespace extensions {
60
61 namespace {
62
63 static bool enable_background_extensions_during_testing = false;
64
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";
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
89 std::string GenerateId(const base::DictionaryValue* manifest,
90                        const base::FilePath& path) {
91   std::string raw_key;
92   std::string id_input;
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);
96   return id;
97 }
98
99 }  // namespace
100
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);
108   }
109   extension_id = GenerateId(manifest, root_directory);
110 }
111
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) {}
118
119 ComponentLoader::~ComponentLoader() {
120   ClearAllRegistered();
121 }
122
123 void ComponentLoader::LoadAll() {
124   for (RegisteredComponentExtensions::iterator it =
125           component_extensions_.begin();
126       it != component_extensions_.end(); ++it) {
127     Load(*it);
128   }
129 }
130
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));
135
136   if (!manifest.get() || !manifest->IsType(base::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<base::DictionaryValue*>(manifest.release());
142 }
143
144 void 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
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);
160   if (!manifest)
161     return std::string();
162
163   ComponentExtensionInfo info(manifest, root_directory);
164   return info.extension_id;
165 }
166
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);
173 }
174
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);
180   if (manifest)
181     return Add(manifest, root_directory);
182   return std::string();
183 }
184
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())
190     Load(info);
191   return info.extension_id;
192 }
193
194 std::string ComponentLoader::AddOrReplace(const base::FilePath& path) {
195   base::FilePath absolute_path = base::MakeAbsoluteFilePath(path);
196   std::string error;
197   scoped_ptr<base::DictionaryValue> manifest(
198       extension_file_util::LoadManifest(absolute_path, &error));
199   if (!manifest) {
200     LOG(ERROR) << "Could not load extension from '" <<
201                   absolute_path.value() << "'. " << error;
202     return std::string();
203   }
204   Remove(GenerateId(manifest.get(), absolute_path));
205
206   return Add(manifest.release(), absolute_path);
207 }
208
209 void ComponentLoader::Reload(const std::string& extension_id) {
210   for (RegisteredComponentExtensions::iterator it =
211          component_extensions_.begin(); it != component_extensions_.end();
212          ++it) {
213     if (it->extension_id == extension_id) {
214       Load(*it);
215       break;
216     }
217   }
218 }
219
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;
224
225   std::string error;
226
227   scoped_refptr<const Extension> extension(Extension::Create(
228       info.root_directory,
229       Manifest::COMPONENT,
230       *info.manifest,
231       flags,
232       &error));
233   if (!extension.get()) {
234     LOG(ERROR) << error;
235     return;
236   }
237
238   CHECK_EQ(info.extension_id, extension->id()) << extension->name();
239   extension_service_->AddComponentExtension(extension.get());
240 }
241
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));
248       break;
249     }
250   }
251 }
252
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);
259       break;
260     }
261   }
262 }
263
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)
269       return true;
270   return false;
271 }
272
273 void ComponentLoader::AddFileManagerExtension() {
274 #if defined(FILE_MANAGER_EXTENSION)
275 #ifndef NDEBUG
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);
281     return;
282   }
283 #endif  // NDEBUG
284   Add(IDR_FILEMANAGER_MANIFEST,
285       base::FilePath(FILE_PATH_LITERAL("file_manager")));
286 #endif  // defined(FILE_MANAGER_EXTENSION)
287 }
288
289 void ComponentLoader::AddHangoutServicesExtension() {
290   Add(IDR_HANGOUT_SERVICES_MANIFEST,
291       base::FilePath(FILE_PATH_LITERAL("hangout_services")));
292 }
293
294 void ComponentLoader::AddImageLoaderExtension() {
295 #if defined(IMAGE_LOADER_EXTENSION)
296 #ifndef NDEBUG
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);
302     return;
303   }
304 #endif  // NDEBUG
305   Add(IDR_IMAGE_LOADER_MANIFEST,
306       base::FilePath(FILE_PATH_LITERAL("image_loader")));
307 #endif  // defined(IMAGE_LOADER_EXTENSION)
308 }
309
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")));
316 #endif
317 }
318
319 void ComponentLoader::AddNetworkSpeechSynthesisExtension() {
320   Add(IDR_NETWORK_SPEECH_SYNTHESIS_MANIFEST,
321       base::FilePath(FILE_PATH_LITERAL("network_speech_synthesis")));
322 }
323
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();
330
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);
334
335   if (manifest) {
336     // Update manifest to use a proper name.
337     manifest->SetString(manifest_keys::kName, name);
338     Add(manifest, root_directory);
339   }
340 }
341
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));
347 #endif
348 }
349
350 void ComponentLoader::AddKeyboardApp() {
351 #if defined(USE_AURA)
352   if (keyboard::IsKeyboardEnabled())
353     Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard")));
354 #endif
355 }
356
357 void ComponentLoader::AddWebStoreApp() {
358   AddWithName(IDR_WEBSTORE_MANIFEST,
359               base::FilePath(FILE_PATH_LITERAL("web_store")),
360               LookupWebstoreName());
361 }
362
363 // static
364 void ComponentLoader::EnableBackgroundExtensionsForTesting() {
365   enable_background_extensions_during_testing = true;
366 }
367
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")));
375
376 #if defined(GOOGLE_CHROME_BUILD)
377   {
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));
384     }
385   }
386   if (browser_defaults::enable_help_app) {
387     Add(IDR_HELP_MANIFEST, base::FilePath(FILE_PATH_LITERAL(
388                                "/usr/share/chromeos-assets/helpapp")));
389   }
390 #endif
391
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();
397
398     Add(IDR_CROSH_BUILTIN_MANIFEST, base::FilePath(FILE_PATH_LITERAL(
399         "/usr/share/chromeos-assets/crosh_builtin")));
400   }
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")));
407 #endif
408
409   if (!skip_session_components) {
410     AddWebStoreApp();
411     AddChromeApp();
412   }
413
414   AddKeyboardApp();
415
416   AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components);
417 }
418
419 void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages(
420     bool skip_session_components) {
421   const CommandLine* command_line = CommandLine::ForCurrentProcess();
422
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))) {
429     return;
430   }
431
432   if (!skip_session_components) {
433     // Apps Debugger
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")));
438     }
439
440
441     AddFileManagerExtension();
442     AddHangoutServicesExtension();
443     AddImageLoaderExtension();
444
445 #if defined(ENABLE_SETTINGS_APP)
446     Add(IDR_SETTINGS_APP_MANIFEST,
447         base::FilePath(FILE_PATH_LITERAL("settings_app")));
448 #endif
449   }
450
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")));
459
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;
468       }
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();
482       }
483     }
484 #endif  // defined(GOOGLE_CHROME_BUILD)
485
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);
491     }
492     Add(IDR_ECHO_MANIFEST, echo_extension_path);
493
494     if (!command_line->HasSwitch(chromeos::switches::kGuestSession)) {
495       Add(IDR_WALLPAPERMANAGER_MANIFEST,
496           base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager")));
497     }
498
499     Add(IDR_NETWORK_CONFIGURATION_MANIFEST,
500         base::FilePath(FILE_PATH_LITERAL("chromeos/network_configuration")));
501
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));
506   }
507
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);
514   }
515 #endif  // defined(OS_CHROMEOS)
516
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);
523
524   bool enabled_via_field_trial = field_trial_result.compare(
525       0,
526       enable_prefix.length(),
527       enable_prefix) == 0;
528
529   // Enable the feature on trybots.
530   bool enabled_via_trunk_build = chrome::VersionInfo::GetChannel() ==
531       chrome::VersionInfo::CHANNEL_UNKNOWN;
532
533   bool enabled_via_flag =
534       chrome::VersionInfo::GetChannel() !=
535           chrome::VersionInfo::CHANNEL_STABLE &&
536       CommandLine::ForCurrentProcess()->HasSwitch(
537           switches::kEnableGoogleNowIntegration);
538
539   bool enabled =
540       enabled_via_field_trial || enabled_via_trunk_build || enabled_via_flag;
541
542   bool disabled_via_flag =
543       CommandLine::ForCurrentProcess()->HasSwitch(
544           switches::kDisableGoogleNowIntegration);
545
546   if (enabled && !disabled_via_flag) {
547     Add(IDR_GOOGLE_NOW_MANIFEST,
548         base::FilePath(FILE_PATH_LITERAL("google_now")));
549   }
550 #endif
551
552 #if defined(GOOGLE_CHROME_BUILD)
553   AddNetworkSpeechSynthesisExtension();
554 #endif  // defined(GOOGLE_CHROME_BUILD)
555 }
556
557 void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) {
558   delete component->manifest;
559   if (extension_service_->is_ready()) {
560     extension_service_->
561         RemoveComponentExtension(component->extension_id);
562   }
563 }
564
565 }  // namespace extensions