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