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