Home | History | Annotate | Download | only in extensions
      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 
     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 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 
    101 ComponentLoader::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 
    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 DictionaryValue* 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 
    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::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 
    162 std::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 
    172 std::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 
    181 std::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 
    196 void 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 
    207 void 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 
    229 void 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 
    237 void 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 
    248 void 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 
    259 bool 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 
    268 void 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 
    284 void 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 
    300 void 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 
    318 void 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 
    326 void 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 
    333 void ComponentLoader::AddWebStoreApp() {
    334   AddWithName(IDR_WEBSTORE_MANIFEST,
    335               base::FilePath(FILE_PATH_LITERAL("web_store")),
    336               LookupWebstoreName());
    337 }
    338 
    339 // static
    340 void ComponentLoader::EnableBackgroundExtensionsForTesting() {
    341   enable_background_extensions_during_testing = true;
    342 }
    343 
    344 void 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 
    388 void 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(OS_CHROMEOS)
    418   if (!skip_session_components) {
    419     Add(IDR_WALLPAPERMANAGER_MANIFEST,
    420         base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager")));
    421 
    422 #if defined(GOOGLE_CHROME_BUILD)
    423     if (!command_line->HasSwitch(
    424             chromeos::switches::kDisableQuickofficeComponentApp)) {
    425       int manifest_id = IDR_QUICKOFFICE_EDITOR_MANIFEST;
    426       if (command_line->HasSwitch(switches::kEnableQuickofficeViewing)) {
    427         manifest_id = IDR_QUICKOFFICE_VIEWING_MANIFEST;
    428       }
    429       std::string id = Add(manifest_id, base::FilePath(
    430           FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office")));
    431       if (command_line->HasSwitch(chromeos::switches::kGuestSession)) {
    432         // TODO(dpolukhin): Hack to enable HTML5 temporary file system for
    433         // Quickoffice. It doesn't work without temporary file system access.
    434         Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord();
    435         ExtensionService* service =
    436             extensions::ExtensionSystem::Get(profile)->extension_service();
    437         GURL site = service->GetSiteForExtensionId(id);
    438         fileapi::FileSystemContext* context =
    439             content::BrowserContext::GetStoragePartitionForSite(profile, site)->
    440                 GetFileSystemContext();
    441         context->EnableTemporaryFileSystemInIncognito();
    442       }
    443     }
    444 #endif  // defined(GOOGLE_CHROME_BUILD)
    445 
    446     base::FilePath echo_extension_path(FILE_PATH_LITERAL(
    447         "/usr/share/chromeos-assets/echo"));
    448     if (command_line->HasSwitch(chromeos::switches::kEchoExtensionPath)) {
    449       echo_extension_path = command_line->GetSwitchValuePath(
    450           chromeos::switches::kEchoExtensionPath);
    451     }
    452     Add(IDR_ECHO_MANIFEST, echo_extension_path);
    453 
    454     Add(IDR_NETWORK_CONFIGURATION_MANIFEST,
    455         base::FilePath(FILE_PATH_LITERAL("chromeos/network_configuration")));
    456 
    457     Add(IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST,
    458         base::FilePath(extension_misc::kConnectivityDiagnosticsPath));
    459     Add(IDR_CONNECTIVITY_DIAGNOSTICS_LAUNCHER_MANIFEST,
    460         base::FilePath(extension_misc::kConnectivityDiagnosticsLauncherPath));
    461   }
    462 
    463   // Load ChromeVox extension now if spoken feedback is enabled.
    464   if (chromeos::AccessibilityManager::Get() &&
    465       chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) {
    466     base::FilePath path =
    467         base::FilePath(extension_misc::kChromeVoxExtensionPath);
    468     Add(IDR_CHROMEVOX_MANIFEST, path);
    469   }
    470 #endif  // defined(OS_CHROMEOS)
    471 
    472 #if defined(ENABLE_GOOGLE_NOW)
    473   if (base::FieldTrialList::FindFullName("GoogleNow") == "Enable" ||
    474       CommandLine::ForCurrentProcess()->HasSwitch(
    475           switches::kEnableGoogleNowIntegration)) {
    476     Add(IDR_GOOGLE_NOW_MANIFEST,
    477         base::FilePath(FILE_PATH_LITERAL("google_now")));
    478   }
    479 #endif
    480 }
    481 
    482 void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) {
    483   delete component->manifest;
    484   if (extension_service_->is_ready()) {
    485     extension_service_->
    486         UnloadExtension(component->extension_id,
    487                         extension_misc::UNLOAD_REASON_DISABLE);
    488   }
    489 }
    490 
    491 }  // namespace extensions
    492