Home | History | Annotate | Download | only in extensions
      1 // Copyright 2013 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/extension_sync_service.h"
      6 
      7 #include <iterator>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "base/threading/sequenced_worker_pool.h"
     12 #include "base/threading/thread_restrictions.h"
     13 #include "chrome/browser/extensions/app_sync_data.h"
     14 #include "chrome/browser/extensions/bookmark_app_helper.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/extensions/extension_sync_data.h"
     17 #include "chrome/browser/extensions/extension_sync_service_factory.h"
     18 #include "chrome/browser/extensions/extension_util.h"
     19 #include "chrome/browser/extensions/launch_util.h"
     20 #include "chrome/browser/profiles/profile.h"
     21 #include "chrome/browser/sync/glue/sync_start_util.h"
     22 #include "chrome/common/extensions/extension_constants.h"
     23 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     24 #include "chrome/common/extensions/sync_helper.h"
     25 #include "chrome/common/web_application_info.h"
     26 #include "components/sync_driver/sync_prefs.h"
     27 #include "extensions/browser/app_sorting.h"
     28 #include "extensions/browser/extension_prefs.h"
     29 #include "extensions/browser/extension_registry.h"
     30 #include "extensions/browser/extension_util.h"
     31 #include "extensions/common/extension.h"
     32 #include "extensions/common/extension_icon_set.h"
     33 #include "extensions/common/feature_switch.h"
     34 #include "extensions/common/manifest_constants.h"
     35 #include "extensions/common/manifest_handlers/icons_handler.h"
     36 #include "sync/api/sync_change.h"
     37 #include "sync/api/sync_error_factory.h"
     38 #include "ui/gfx/image/image_family.h"
     39 
     40 using extensions::Extension;
     41 using extensions::ExtensionPrefs;
     42 using extensions::ExtensionRegistry;
     43 using extensions::FeatureSwitch;
     44 
     45 namespace {
     46 
     47 void OnWebApplicationInfoLoaded(
     48     WebApplicationInfo synced_info,
     49     base::WeakPtr<ExtensionService> extension_service,
     50     const WebApplicationInfo& loaded_info) {
     51   DCHECK_EQ(synced_info.app_url, loaded_info.app_url);
     52 
     53   if (!extension_service)
     54     return;
     55 
     56   // Use the old icons if they exist.
     57   synced_info.icons = loaded_info.icons;
     58   CreateOrUpdateBookmarkApp(extension_service.get(), synced_info);
     59 }
     60 
     61 }  // namespace
     62 
     63 ExtensionSyncService::ExtensionSyncService(Profile* profile,
     64                                            ExtensionPrefs* extension_prefs,
     65                                            ExtensionService* extension_service)
     66     : profile_(profile),
     67       extension_prefs_(extension_prefs),
     68       extension_service_(extension_service),
     69       app_sync_bundle_(this),
     70       extension_sync_bundle_(this),
     71       pending_app_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
     72                                extension_prefs_->pref_service())),
     73                            &app_sync_bundle_,
     74                            syncer::APPS),
     75       pending_extension_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
     76                                      extension_prefs_->pref_service())),
     77                                  &extension_sync_bundle_,
     78                                  syncer::EXTENSIONS) {
     79   SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
     80       profile_->GetPath()));
     81 
     82   extension_service_->set_extension_sync_service(this);
     83   extension_prefs_->app_sorting()->SetExtensionSyncService(this);
     84 }
     85 
     86 ExtensionSyncService::~ExtensionSyncService() {}
     87 
     88 // static
     89 ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
     90   return ExtensionSyncServiceFactory::GetForProfile(profile);
     91 }
     92 
     93 syncer::SyncChange ExtensionSyncService::PrepareToSyncUninstallExtension(
     94     const extensions::Extension* extension, bool extensions_ready) {
     95   // Extract the data we need for sync now, but don't actually sync until we've
     96   // completed the uninstallation.
     97   // TODO(tim): If we get here and IsSyncing is false, this will cause
     98   // "back from the dead" style bugs, because sync will add-back the extension
     99   // that was uninstalled here when MergeDataAndStartSyncing is called.
    100   // See crbug.com/256795.
    101   if (extensions::util::ShouldSyncApp(extension, profile_)) {
    102     if (app_sync_bundle_.IsSyncing())
    103       return app_sync_bundle_.CreateSyncChangeToDelete(extension);
    104     else if (extensions_ready && !flare_.is_null())
    105       flare_.Run(syncer::APPS);  // Tell sync to start ASAP.
    106   } else if (extensions::sync_helper::IsSyncableExtension(extension)) {
    107     if (extension_sync_bundle_.IsSyncing())
    108       return extension_sync_bundle_.CreateSyncChangeToDelete(extension);
    109     else if (extensions_ready && !flare_.is_null())
    110       flare_.Run(syncer::EXTENSIONS);  // Tell sync to start ASAP.
    111   }
    112 
    113   return syncer::SyncChange();
    114 }
    115 
    116 void ExtensionSyncService::ProcessSyncUninstallExtension(
    117     const std::string& extension_id,
    118     const syncer::SyncChange& sync_change) {
    119   if (app_sync_bundle_.HasExtensionId(extension_id) &&
    120       sync_change.sync_data().GetDataType() == syncer::APPS) {
    121     app_sync_bundle_.ProcessDeletion(extension_id, sync_change);
    122   } else if (extension_sync_bundle_.HasExtensionId(extension_id) &&
    123              sync_change.sync_data().GetDataType() == syncer::EXTENSIONS) {
    124     extension_sync_bundle_.ProcessDeletion(extension_id, sync_change);
    125   }
    126 }
    127 
    128 void ExtensionSyncService::SyncEnableExtension(
    129     const extensions::Extension& extension) {
    130 
    131   // Syncing may not have started yet, so handle pending enables.
    132   if (extensions::util::ShouldSyncApp(&extension, profile_))
    133     pending_app_enables_.OnExtensionEnabled(extension.id());
    134 
    135   if (extensions::util::ShouldSyncExtension(&extension, profile_))
    136     pending_extension_enables_.OnExtensionEnabled(extension.id());
    137 
    138   SyncExtensionChangeIfNeeded(extension);
    139 }
    140 
    141 void ExtensionSyncService::SyncDisableExtension(
    142     const extensions::Extension& extension) {
    143 
    144   // Syncing may not have started yet, so handle pending enables.
    145   if (extensions::util::ShouldSyncApp(&extension, profile_))
    146     pending_app_enables_.OnExtensionDisabled(extension.id());
    147 
    148   if (extensions::util::ShouldSyncExtension(&extension, profile_))
    149     pending_extension_enables_.OnExtensionDisabled(extension.id());
    150 
    151   SyncExtensionChangeIfNeeded(extension);
    152 }
    153 
    154 syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
    155     syncer::ModelType type,
    156     const syncer::SyncDataList& initial_sync_data,
    157     scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
    158     scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
    159   CHECK(sync_processor.get());
    160   CHECK(sync_error_factory.get());
    161 
    162   switch (type) {
    163     case syncer::EXTENSIONS:
    164       extension_sync_bundle_.SetupSync(sync_processor.release(),
    165                                        sync_error_factory.release(),
    166                                        initial_sync_data);
    167       pending_extension_enables_.OnSyncStarted(extension_service_);
    168       break;
    169 
    170     case syncer::APPS:
    171       app_sync_bundle_.SetupSync(sync_processor.release(),
    172                                  sync_error_factory.release(),
    173                                  initial_sync_data);
    174       pending_app_enables_.OnSyncStarted(extension_service_);
    175       break;
    176 
    177     default:
    178       LOG(FATAL) << "Got " << type << " ModelType";
    179   }
    180 
    181   // Process local extensions.
    182   // TODO(yoz): Determine whether pending extensions should be considered too.
    183   //            See crbug.com/104399.
    184   syncer::SyncDataList sync_data_list = GetAllSyncData(type);
    185   syncer::SyncChangeList sync_change_list;
    186   for (syncer::SyncDataList::const_iterator i = sync_data_list.begin();
    187        i != sync_data_list.end();
    188        ++i) {
    189     switch (type) {
    190         case syncer::EXTENSIONS:
    191           sync_change_list.push_back(
    192               extension_sync_bundle_.CreateSyncChange(*i));
    193           break;
    194         case syncer::APPS:
    195           sync_change_list.push_back(app_sync_bundle_.CreateSyncChange(*i));
    196           break;
    197       default:
    198         LOG(FATAL) << "Got " << type << " ModelType";
    199     }
    200   }
    201 
    202 
    203   if (type == syncer::EXTENSIONS) {
    204     extension_sync_bundle_.ProcessSyncChangeList(sync_change_list);
    205   } else if (type == syncer::APPS) {
    206     app_sync_bundle_.ProcessSyncChangeList(sync_change_list);
    207   }
    208 
    209   return syncer::SyncMergeResult(type);
    210 }
    211 
    212 void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
    213   if (type == syncer::APPS) {
    214     app_sync_bundle_.Reset();
    215   } else if (type == syncer::EXTENSIONS) {
    216     extension_sync_bundle_.Reset();
    217   }
    218 }
    219 
    220 syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
    221     syncer::ModelType type) const {
    222   if (type == syncer::EXTENSIONS)
    223     return extension_sync_bundle_.GetAllSyncData();
    224   if (type == syncer::APPS)
    225     return app_sync_bundle_.GetAllSyncData();
    226 
    227   // We should only get sync data for extensions and apps.
    228   NOTREACHED();
    229 
    230   return syncer::SyncDataList();
    231 }
    232 
    233 syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
    234     const tracked_objects::Location& from_here,
    235     const syncer::SyncChangeList& change_list) {
    236   for (syncer::SyncChangeList::const_iterator i = change_list.begin();
    237       i != change_list.end();
    238       ++i) {
    239     syncer::ModelType type = i->sync_data().GetDataType();
    240     if (type == syncer::EXTENSIONS) {
    241       extension_sync_bundle_.ProcessSyncChange(
    242           extensions::ExtensionSyncData(*i));
    243     } else if (type == syncer::APPS) {
    244       app_sync_bundle_.ProcessSyncChange(extensions::AppSyncData(*i));
    245     }
    246   }
    247 
    248   extension_prefs_->app_sorting()->FixNTPOrdinalCollisions();
    249 
    250   return syncer::SyncError();
    251 }
    252 
    253 extensions::ExtensionSyncData ExtensionSyncService::GetExtensionSyncData(
    254     const Extension& extension) const {
    255   return extensions::ExtensionSyncData(
    256       extension,
    257       extension_service_->IsExtensionEnabled(extension.id()),
    258       extensions::util::IsIncognitoEnabled(extension.id(), profile_),
    259       extension_prefs_->HasDisableReason(extension.id(),
    260                                          Extension::DISABLE_REMOTE_INSTALL));
    261 }
    262 
    263 extensions::AppSyncData ExtensionSyncService::GetAppSyncData(
    264     const Extension& extension) const {
    265   return extensions::AppSyncData(
    266       extension,
    267       extension_service_->IsExtensionEnabled(extension.id()),
    268       extensions::util::IsIncognitoEnabled(extension.id(), profile_),
    269       extension_prefs_->HasDisableReason(extension.id(),
    270                                          Extension::DISABLE_REMOTE_INSTALL),
    271       extension_prefs_->app_sorting()->GetAppLaunchOrdinal(extension.id()),
    272       extension_prefs_->app_sorting()->GetPageOrdinal(extension.id()),
    273       extensions::GetLaunchTypePrefValue(extension_prefs_, extension.id()));
    274 }
    275 
    276 std::vector<extensions::ExtensionSyncData>
    277   ExtensionSyncService::GetExtensionSyncDataList() const {
    278   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
    279   std::vector<extensions::ExtensionSyncData> extension_sync_list;
    280   extension_sync_bundle_.GetExtensionSyncDataListHelper(
    281       registry->enabled_extensions(), &extension_sync_list);
    282   extension_sync_bundle_.GetExtensionSyncDataListHelper(
    283       registry->disabled_extensions(), &extension_sync_list);
    284   extension_sync_bundle_.GetExtensionSyncDataListHelper(
    285       registry->terminated_extensions(), &extension_sync_list);
    286 
    287   std::vector<extensions::ExtensionSyncData> pending_extensions =
    288       extension_sync_bundle_.GetPendingData();
    289   extension_sync_list.insert(extension_sync_list.begin(),
    290                              pending_extensions.begin(),
    291                              pending_extensions.end());
    292 
    293   return extension_sync_list;
    294 }
    295 
    296 std::vector<extensions::AppSyncData> ExtensionSyncService::GetAppSyncDataList()
    297     const {
    298   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
    299   std::vector<extensions::AppSyncData> app_sync_list;
    300   app_sync_bundle_.GetAppSyncDataListHelper(
    301       registry->enabled_extensions(), &app_sync_list);
    302   app_sync_bundle_.GetAppSyncDataListHelper(
    303       registry->disabled_extensions(), &app_sync_list);
    304   app_sync_bundle_.GetAppSyncDataListHelper(
    305       registry->terminated_extensions(), &app_sync_list);
    306 
    307   std::vector<extensions::AppSyncData> pending_apps =
    308       app_sync_bundle_.GetPendingData();
    309   app_sync_list.insert(app_sync_list.begin(),
    310                        pending_apps.begin(),
    311                        pending_apps.end());
    312 
    313   return app_sync_list;
    314 }
    315 
    316 bool ExtensionSyncService::ProcessExtensionSyncData(
    317     const extensions::ExtensionSyncData& extension_sync_data) {
    318   if (!ProcessExtensionSyncDataHelper(extension_sync_data,
    319                                       syncer::EXTENSIONS)) {
    320     extension_sync_bundle_.AddPendingExtension(extension_sync_data.id(),
    321                                                extension_sync_data);
    322     extension_service_->CheckForUpdatesSoon();
    323     return false;
    324   }
    325 
    326   return true;
    327 }
    328 
    329 bool ExtensionSyncService::ProcessAppSyncData(
    330     const extensions::AppSyncData& app_sync_data) {
    331   const std::string& id = app_sync_data.id();
    332 
    333   if (app_sync_data.app_launch_ordinal().IsValid() &&
    334       app_sync_data.page_ordinal().IsValid()) {
    335     extension_prefs_->app_sorting()->SetAppLaunchOrdinal(
    336         id,
    337         app_sync_data.app_launch_ordinal());
    338     extension_prefs_->app_sorting()->SetPageOrdinal(
    339         id,
    340         app_sync_data.page_ordinal());
    341   }
    342 
    343   // The corresponding validation of this value during AppSyncData population
    344   // is in AppSyncData::PopulateAppSpecifics.
    345   if (app_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
    346       app_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
    347     extensions::SetLaunchType(extension_service_, id,
    348                               app_sync_data.launch_type());
    349   }
    350 
    351   if (!app_sync_data.bookmark_app_url().empty())
    352     ProcessBookmarkAppSyncData(app_sync_data);
    353 
    354   if (!ProcessExtensionSyncDataHelper(app_sync_data.extension_sync_data(),
    355                                       syncer::APPS)) {
    356     app_sync_bundle_.AddPendingApp(id, app_sync_data);
    357     extension_service_->CheckForUpdatesSoon();
    358     return false;
    359   }
    360 
    361   return true;
    362 }
    363 
    364 void ExtensionSyncService::ProcessBookmarkAppSyncData(
    365     const extensions::AppSyncData& app_sync_data) {
    366   // Process bookmark app sync if necessary.
    367   GURL bookmark_app_url(app_sync_data.bookmark_app_url());
    368   if (!bookmark_app_url.is_valid() ||
    369       app_sync_data.extension_sync_data().uninstalled()) {
    370     return;
    371   }
    372 
    373   const extensions::Extension* extension =
    374       extension_service_->GetInstalledExtension(
    375           app_sync_data.extension_sync_data().id());
    376 
    377   // Return if there are no bookmark app details that need updating.
    378   if (extension && extension->non_localized_name() ==
    379                        app_sync_data.extension_sync_data().name() &&
    380       extension->description() == app_sync_data.bookmark_app_description()) {
    381     return;
    382   }
    383 
    384   WebApplicationInfo web_app_info;
    385   web_app_info.app_url = bookmark_app_url;
    386   web_app_info.title =
    387       base::UTF8ToUTF16(app_sync_data.extension_sync_data().name());
    388   web_app_info.description =
    389       base::UTF8ToUTF16(app_sync_data.bookmark_app_description());
    390 
    391   // If the bookmark app already exists, keep the old icons.
    392   if (!extension) {
    393     CreateOrUpdateBookmarkApp(extension_service_, web_app_info);
    394   } else {
    395     app_sync_data.extension_sync_data().name();
    396     GetWebApplicationInfoFromApp(profile_,
    397                                  extension,
    398                                  base::Bind(&OnWebApplicationInfoLoaded,
    399                                             web_app_info,
    400                                             extension_service_->AsWeakPtr()));
    401   }
    402 }
    403 
    404 void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
    405   const extensions::Extension* ext = extension_service_->GetInstalledExtension(
    406       extension_id);
    407 
    408   if (ext)
    409     SyncExtensionChangeIfNeeded(*ext);
    410 }
    411 
    412 void ExtensionSyncService::SetSyncStartFlare(
    413     const syncer::SyncableService::StartSyncFlare& flare) {
    414   flare_ = flare;
    415 }
    416 
    417 bool ExtensionSyncService::IsCorrectSyncType(const Extension& extension,
    418                                          syncer::ModelType type) const {
    419   if (type == syncer::EXTENSIONS &&
    420       extensions::sync_helper::IsSyncableExtension(&extension)) {
    421     return true;
    422   }
    423 
    424   if (type == syncer::APPS &&
    425       extensions::sync_helper::IsSyncableApp(&extension)) {
    426     return true;
    427   }
    428 
    429   return false;
    430 }
    431 
    432 bool ExtensionSyncService::IsPendingEnable(
    433     const std::string& extension_id) const {
    434   return pending_app_enables_.Contains(extension_id) ||
    435       pending_extension_enables_.Contains(extension_id);
    436 }
    437 
    438 bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
    439     const extensions::ExtensionSyncData& extension_sync_data,
    440     syncer::ModelType type) {
    441   const std::string& id = extension_sync_data.id();
    442   const Extension* extension = extension_service_->GetInstalledExtension(id);
    443 
    444   // TODO(bolms): we should really handle this better.  The particularly bad
    445   // case is where an app becomes an extension or vice versa, and we end up with
    446   // a zombie extension that won't go away.
    447   if (extension && !IsCorrectSyncType(*extension, type))
    448     return true;
    449 
    450   // Handle uninstalls first.
    451   if (extension_sync_data.uninstalled()) {
    452     if (!extension_service_->UninstallExtensionHelper(extension_service_, id)) {
    453       LOG(WARNING) << "Could not uninstall extension " << id
    454                    << " for sync";
    455     }
    456     return true;
    457   }
    458 
    459   // Extension from sync was uninstalled by the user as external extensions.
    460   // Honor user choice and skip installation/enabling.
    461   if (extensions::ExtensionPrefs::Get(profile_)
    462           ->IsExternalExtensionUninstalled(id)) {
    463     LOG(WARNING) << "Extension with id " << id
    464                  << " from sync was uninstalled as external extension";
    465     return true;
    466   }
    467 
    468   // Set user settings.
    469   // If the extension has been disabled from sync, it may not have
    470   // been installed yet, so we don't know if the disable reason was a
    471   // permissions increase.  That will be updated once CheckPermissionsIncrease
    472   // is called for it.
    473   // However if the extension is marked as a remote install in sync, we know
    474   // what the disable reason is, so set it to that directly. Note that when
    475   // CheckPermissionsIncrease runs, it might still add permissions increase
    476   // as a disable reason for the extension.
    477   if (extension_sync_data.enabled()) {
    478     extension_service_->EnableExtension(id);
    479   } else if (!IsPendingEnable(id)) {
    480     if (extension_sync_data.remote_install()) {
    481       extension_service_->DisableExtension(id,
    482                                            Extension::DISABLE_REMOTE_INSTALL);
    483     } else {
    484       extension_service_->DisableExtension(
    485           id, Extension::DISABLE_UNKNOWN_FROM_SYNC);
    486     }
    487   }
    488 
    489   // We need to cache some version information here because setting the
    490   // incognito flag invalidates the |extension| pointer (it reloads the
    491   // extension).
    492   bool extension_installed = (extension != NULL);
    493   int version_compare_result = extension ?
    494       extension->version()->CompareTo(extension_sync_data.version()) : 0;
    495 
    496   // If the target extension has already been installed ephemerally, it can
    497   // be promoted to a regular installed extension and downloading from the Web
    498   // Store is not necessary.
    499   if (extension && extensions::util::IsEphemeralApp(id, profile_))
    500     extension_service_->PromoteEphemeralApp(extension, true);
    501 
    502   // Update the incognito flag.
    503   extensions::util::SetIsIncognitoEnabled(
    504       id, profile_, extension_sync_data.incognito_enabled());
    505   extension = NULL;  // No longer safe to use.
    506 
    507   if (extension_installed) {
    508     // If the extension is already installed, check if it's outdated.
    509     if (version_compare_result < 0) {
    510       // Extension is outdated.
    511       return false;
    512     }
    513   } else {
    514     // TODO(akalin): Replace silent update with a list of enabled
    515     // permissions.
    516     const bool kInstallSilently = true;
    517 
    518     CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
    519     extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter =
    520         (type == syncer::APPS) ? extensions::sync_helper::IsSyncableApp :
    521                                  extensions::sync_helper::IsSyncableExtension;
    522 
    523     if (!extension_service_->pending_extension_manager()->AddFromSync(
    524             id,
    525             extension_sync_data.update_url(),
    526             filter,
    527             kInstallSilently,
    528             extension_sync_data.remote_install())) {
    529       LOG(WARNING) << "Could not add pending extension for " << id;
    530       // This means that the extension is already pending installation, with a
    531       // non-INTERNAL location.  Add to pending_sync_data, even though it will
    532       // never be removed (we'll never install a syncable version of the
    533       // extension), so that GetAllSyncData() continues to send it.
    534     }
    535     // Track pending extensions so that we can return them in GetAllSyncData().
    536     return false;
    537   }
    538 
    539   return true;
    540 }
    541 
    542 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
    543     const Extension& extension) {
    544   if (extensions::util::ShouldSyncApp(&extension, profile_)) {
    545     if (app_sync_bundle_.IsSyncing())
    546       app_sync_bundle_.SyncChangeIfNeeded(extension);
    547     else if (extension_service_->is_ready() && !flare_.is_null())
    548       flare_.Run(syncer::APPS);
    549   } else if (extensions::util::ShouldSyncExtension(&extension, profile_)) {
    550     if (extension_sync_bundle_.IsSyncing())
    551       extension_sync_bundle_.SyncChangeIfNeeded(extension);
    552     else if (extension_service_->is_ready() && !flare_.is_null())
    553       flare_.Run(syncer::EXTENSIONS);
    554   }
    555 }
    556