component_loader.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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/extensions/extension_manifest_constants.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 "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#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
53#endif
54
55#if defined(ENABLE_APP_LIST)
56#include "grit/chromium_strings.h"
57#endif
58
59namespace extensions {
60
61namespace {
62
63static bool enable_background_extensions_during_testing = false;
64
65std::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
89std::string GenerateId(const DictionaryValue* manifest,
90                       const base::FilePath& path) {
91  std::string raw_key;
92  std::string id_input;
93  CHECK(manifest->GetString(extension_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
101ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo(
102    const 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
112ComponentLoader::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
119ComponentLoader::~ComponentLoader() {
120  ClearAllRegistered();
121}
122
123void ComponentLoader::LoadAll() {
124  for (RegisteredComponentExtensions::iterator it =
125          component_extensions_.begin();
126      it != component_extensions_.end(); ++it) {
127    Load(*it);
128  }
129}
130
131DictionaryValue* ComponentLoader::ParseManifest(
132    const std::string& manifest_contents) const {
133  JSONStringValueSerializer serializer(manifest_contents);
134  scoped_ptr<Value> manifest(serializer.Deserialize(NULL, NULL));
135
136  if (!manifest.get() || !manifest->IsType(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<DictionaryValue*>(manifest.release());
142}
143
144void 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
154std::string ComponentLoader::Add(int manifest_resource_id,
155                                 const base::FilePath& root_directory) {
156  std::string manifest_contents =
157      ResourceBundle::GetSharedInstance().GetRawDataResource(
158          manifest_resource_id).as_string();
159  return Add(manifest_contents, root_directory);
160}
161
162std::string ComponentLoader::Add(const std::string& manifest_contents,
163                                 const base::FilePath& root_directory) {
164  // The Value is kept for the lifetime of the ComponentLoader. This is
165  // required in case LoadAll() is called again.
166  DictionaryValue* manifest = ParseManifest(manifest_contents);
167  if (manifest)
168    return Add(manifest, root_directory);
169  return std::string();
170}
171
172std::string ComponentLoader::Add(const DictionaryValue* parsed_manifest,
173                                 const base::FilePath& root_directory) {
174  ComponentExtensionInfo info(parsed_manifest, root_directory);
175  component_extensions_.push_back(info);
176  if (extension_service_->is_ready())
177    Load(info);
178  return info.extension_id;
179}
180
181std::string ComponentLoader::AddOrReplace(const base::FilePath& path) {
182  base::FilePath absolute_path = base::MakeAbsoluteFilePath(path);
183  std::string error;
184  scoped_ptr<DictionaryValue> manifest(
185      extension_file_util::LoadManifest(absolute_path, &error));
186  if (!manifest) {
187    LOG(ERROR) << "Could not load extension from '" <<
188                  absolute_path.value() << "'. " << error;
189    return std::string();
190  }
191  Remove(GenerateId(manifest.get(), absolute_path));
192
193  return Add(manifest.release(), absolute_path);
194}
195
196void ComponentLoader::Reload(const std::string& extension_id) {
197  for (RegisteredComponentExtensions::iterator it =
198         component_extensions_.begin(); it != component_extensions_.end();
199         ++it) {
200    if (it->extension_id == extension_id) {
201      Load(*it);
202      break;
203    }
204  }
205}
206
207void ComponentLoader::Load(const ComponentExtensionInfo& info) {
208  // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated
209  //               our component extensions to the new manifest version.
210  int flags = Extension::REQUIRE_KEY;
211
212  std::string error;
213
214  scoped_refptr<const Extension> extension(Extension::Create(
215      info.root_directory,
216      Manifest::COMPONENT,
217      *info.manifest,
218      flags,
219      &error));
220  if (!extension.get()) {
221    LOG(ERROR) << error;
222    return;
223  }
224
225  CHECK_EQ(info.extension_id, extension->id()) << extension->name();
226  extension_service_->AddComponentExtension(extension.get());
227}
228
229void ComponentLoader::RemoveAll() {
230  RegisteredComponentExtensions::iterator it = component_extensions_.begin();
231  for (; it != component_extensions_.end(); ++it)
232    UnloadComponent(&(*it));
233
234  component_extensions_.clear();
235}
236
237void ComponentLoader::Remove(const base::FilePath& root_directory) {
238  // Find the ComponentExtensionInfo for the extension.
239  RegisteredComponentExtensions::iterator it = component_extensions_.begin();
240  for (; it != component_extensions_.end(); ++it) {
241    if (it->root_directory == root_directory) {
242      Remove(GenerateId(it->manifest, root_directory));
243      break;
244    }
245  }
246}
247
248void ComponentLoader::Remove(const std::string& id) {
249  RegisteredComponentExtensions::iterator it = component_extensions_.begin();
250  for (; it != component_extensions_.end(); ++it) {
251    if (it->extension_id == id) {
252      UnloadComponent(&(*it));
253      it = component_extensions_.erase(it);
254      break;
255    }
256  }
257}
258
259bool ComponentLoader::Exists(const std::string& id) const {
260  RegisteredComponentExtensions::const_iterator it =
261      component_extensions_.begin();
262  for (; it != component_extensions_.end(); ++it)
263    if (it->extension_id == id)
264      return true;
265  return false;
266}
267
268void ComponentLoader::AddFileManagerExtension() {
269#if defined(FILE_MANAGER_EXTENSION)
270#ifndef NDEBUG
271  const CommandLine* command_line = CommandLine::ForCurrentProcess();
272  if (command_line->HasSwitch(switches::kFileManagerExtensionPath)) {
273    base::FilePath filemgr_extension_path(
274        command_line->GetSwitchValuePath(switches::kFileManagerExtensionPath));
275    Add(IDR_FILEMANAGER_MANIFEST, filemgr_extension_path);
276    return;
277  }
278#endif  // NDEBUG
279  Add(IDR_FILEMANAGER_MANIFEST,
280      base::FilePath(FILE_PATH_LITERAL("file_manager")));
281#endif  // defined(FILE_MANAGER_EXTENSION)
282}
283
284void ComponentLoader::AddImageLoaderExtension() {
285#if defined(IMAGE_LOADER_EXTENSION)
286#ifndef NDEBUG
287  const CommandLine* command_line = CommandLine::ForCurrentProcess();
288  if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) {
289    base::FilePath image_loader_extension_path(
290        command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath));
291    Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path);
292    return;
293  }
294#endif  // NDEBUG
295  Add(IDR_IMAGE_LOADER_MANIFEST,
296      base::FilePath(FILE_PATH_LITERAL("image_loader")));
297#endif  // defined(IMAGE_LOADER_EXTENSION)
298}
299
300void ComponentLoader::AddWithName(int manifest_resource_id,
301                                  const base::FilePath& root_directory,
302                                  const std::string& name) {
303  std::string manifest_contents =
304      ResourceBundle::GetSharedInstance().GetRawDataResource(
305          manifest_resource_id).as_string();
306
307  // The Value is kept for the lifetime of the ComponentLoader. This is
308  // required in case LoadAll() is called again.
309  DictionaryValue* manifest = ParseManifest(manifest_contents);
310
311  if (manifest) {
312    // Update manifest to use a proper name.
313    manifest->SetString(extension_manifest_keys::kName, name);
314    Add(manifest, root_directory);
315  }
316}
317
318void ComponentLoader::AddChromeApp() {
319#if defined(ENABLE_APP_LIST)
320  AddWithName(IDR_CHROME_APP_MANIFEST,
321              base::FilePath(FILE_PATH_LITERAL("chrome_app")),
322              l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME));
323#endif
324}
325
326void ComponentLoader::AddKeyboardApp() {
327#if defined(USE_AURA)
328  if (keyboard::IsKeyboardEnabled())
329    Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard")));
330#endif
331}
332
333void ComponentLoader::AddWebStoreApp() {
334  AddWithName(IDR_WEBSTORE_MANIFEST,
335              base::FilePath(FILE_PATH_LITERAL("web_store")),
336              LookupWebstoreName());
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    AddWebStoreApp();
380    AddChromeApp();
381  }
382
383  AddKeyboardApp();
384
385  AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components);
386}
387
388void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages(
389    bool skip_session_components) {
390  const CommandLine* command_line = CommandLine::ForCurrentProcess();
391
392  // Component extensions with background pages are not enabled during tests
393  // because they generate a lot of background behavior that can interfere.
394  if (!enable_background_extensions_during_testing &&
395      (command_line->HasSwitch(switches::kTestType) ||
396          command_line->HasSwitch(switches::kMetricsRecordingOnly))) {
397    return;
398  }
399
400  if (!skip_session_components) {
401    // Apps Debugger
402    if (CommandLine::ForCurrentProcess()->HasSwitch(
403        switches::kAppsDevtool)) {
404      Add(IDR_APPS_DEBUGGER_MANIFEST,
405          base::FilePath(FILE_PATH_LITERAL("apps_debugger")));
406    }
407
408    AddFileManagerExtension();
409    AddImageLoaderExtension();
410
411#if defined(ENABLE_SETTINGS_APP)
412    Add(IDR_SETTINGS_APP_MANIFEST,
413        base::FilePath(FILE_PATH_LITERAL("settings_app")));
414#endif
415  }
416
417#if defined(GOOGLE_CHROME_BUILD)
418    Add(IDR_FEEDBACK_MANIFEST, base::FilePath(FILE_PATH_LITERAL("feedback")));
419#endif  // defined(GOOGLE_CHROME_BUILD)
420
421#if defined(OS_CHROMEOS)
422  if (!skip_session_components) {
423    Add(IDR_WALLPAPERMANAGER_MANIFEST,
424        base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager")));
425
426#if defined(GOOGLE_CHROME_BUILD)
427    if (!command_line->HasSwitch(
428            chromeos::switches::kDisableQuickofficeComponentApp)) {
429      int manifest_id = IDR_QUICK_OFFICE_MANIFEST;
430      if (command_line->HasSwitch(switches::kEnableQuickofficeEdit)) {
431        manifest_id = IDR_QUICKOFFICE_EDITOR_MANIFEST;
432      }
433      std::string id = Add(manifest_id, base::FilePath(
434          FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office")));
435      if (command_line->HasSwitch(chromeos::switches::kGuestSession)) {
436        // TODO(dpolukhin): Hack to enable HTML5 temporary file system for
437        // Quickoffice. It doesn't work without temporary file system access.
438        Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
439        ExtensionService* service =
440            extensions::ExtensionSystem::Get(profile)->extension_service();
441        GURL site = service->GetSiteForExtensionId(id);
442        fileapi::FileSystemContext* context =
443            content::BrowserContext::GetStoragePartitionForSite(profile, site)->
444                GetFileSystemContext();
445        context->EnableTemporaryFileSystemInIncognito();
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(chromeos::switches::kEchoExtensionPath)) {
453      echo_extension_path = command_line->GetSwitchValuePath(
454          chromeos::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    Add(IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST,
462        base::FilePath(extension_misc::kConnectivityDiagnosticsPath));
463    Add(IDR_CONNECTIVITY_DIAGNOSTICS_LAUNCHER_MANIFEST,
464        base::FilePath(extension_misc::kConnectivityDiagnosticsLauncherPath));
465  }
466
467  // Load ChromeVox extension now if spoken feedback is enabled.
468  if (chromeos::AccessibilityManager::Get() &&
469      chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) {
470    base::FilePath path =
471        base::FilePath(extension_misc::kChromeVoxExtensionPath);
472    Add(IDR_CHROMEVOX_MANIFEST, path);
473  }
474#endif  // defined(OS_CHROMEOS)
475
476#if defined(ENABLE_GOOGLE_NOW)
477  if (base::FieldTrialList::FindFullName("GoogleNow") == "Enable" ||
478      CommandLine::ForCurrentProcess()->HasSwitch(
479          switches::kEnableGoogleNowIntegration)) {
480    Add(IDR_GOOGLE_NOW_MANIFEST,
481        base::FilePath(FILE_PATH_LITERAL("google_now")));
482  }
483#endif
484}
485
486void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) {
487  delete component->manifest;
488  if (extension_service_->is_ready()) {
489    extension_service_->
490        UnloadExtension(component->extension_id,
491                        extension_misc::UNLOAD_REASON_DISABLE);
492  }
493}
494
495}  // namespace extensions
496