// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_sync_service.h"
#include <iterator>
#include "base/basictypes.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/extensions/app_sync_data.h"
#include "chrome/browser/extensions/bookmark_app_helper.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_sync_data.h"
#include "chrome/browser/extensions/extension_sync_service_factory.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/glue/sync_start_util.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/extensions/sync_helper.h"
#include "chrome/common/web_application_info.h"
#include "components/sync_driver/sync_prefs.h"
#include "extensions/browser/app_sorting.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "sync/api/sync_change.h"
#include "sync/api/sync_error_factory.h"
#include "ui/gfx/image/image_family.h"
using extensions::Extension;
using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry;
using extensions::FeatureSwitch;
namespace {
void OnWebApplicationInfoLoaded(
WebApplicationInfo synced_info,
base::WeakPtr<ExtensionService> extension_service,
const WebApplicationInfo& loaded_info) {
DCHECK_EQ(synced_info.app_url, loaded_info.app_url);
if (!extension_service)
return;
// Use the old icons if they exist.
synced_info.icons = loaded_info.icons;
CreateOrUpdateBookmarkApp(extension_service.get(), synced_info);
}
} // namespace
ExtensionSyncService::ExtensionSyncService(Profile* profile,
ExtensionPrefs* extension_prefs,
ExtensionService* extension_service)
: profile_(profile),
extension_prefs_(extension_prefs),
extension_service_(extension_service),
app_sync_bundle_(this),
extension_sync_bundle_(this),
pending_app_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
extension_prefs_->pref_service())),
&app_sync_bundle_,
syncer::APPS),
pending_extension_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
extension_prefs_->pref_service())),
&extension_sync_bundle_,
syncer::EXTENSIONS) {
SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
profile_->GetPath()));
extension_service_->set_extension_sync_service(this);
extension_prefs_->app_sorting()->SetExtensionSyncService(this);
}
ExtensionSyncService::~ExtensionSyncService() {}
// static
ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
return ExtensionSyncServiceFactory::GetForProfile(profile);
}
syncer::SyncChange ExtensionSyncService::PrepareToSyncUninstallExtension(
const extensions::Extension* extension, bool extensions_ready) {
// Extract the data we need for sync now, but don't actually sync until we've
// completed the uninstallation.
// TODO(tim): If we get here and IsSyncing is false, this will cause
// "back from the dead" style bugs, because sync will add-back the extension
// that was uninstalled here when MergeDataAndStartSyncing is called.
// See crbug.com/256795.
if (extensions::util::ShouldSyncApp(extension, profile_)) {
if (app_sync_bundle_.IsSyncing())
return app_sync_bundle_.CreateSyncChangeToDelete(extension);
else if (extensions_ready && !flare_.is_null())
flare_.Run(syncer::APPS); // Tell sync to start ASAP.
} else if (extensions::sync_helper::IsSyncableExtension(extension)) {
if (extension_sync_bundle_.IsSyncing())
return extension_sync_bundle_.CreateSyncChangeToDelete(extension);
else if (extensions_ready && !flare_.is_null())
flare_.Run(syncer::EXTENSIONS); // Tell sync to start ASAP.
}
return syncer::SyncChange();
}
void ExtensionSyncService::ProcessSyncUninstallExtension(
const std::string& extension_id,
const syncer::SyncChange& sync_change) {
if (app_sync_bundle_.HasExtensionId(extension_id) &&
sync_change.sync_data().GetDataType() == syncer::APPS) {
app_sync_bundle_.ProcessDeletion(extension_id, sync_change);
} else if (extension_sync_bundle_.HasExtensionId(extension_id) &&
sync_change.sync_data().GetDataType() == syncer::EXTENSIONS) {
extension_sync_bundle_.ProcessDeletion(extension_id, sync_change);
}
}
void ExtensionSyncService::SyncEnableExtension(
const extensions::Extension& extension) {
// Syncing may not have started yet, so handle pending enables.
if (extensions::util::ShouldSyncApp(&extension, profile_))
pending_app_enables_.OnExtensionEnabled(extension.id());
if (extensions::util::ShouldSyncExtension(&extension, profile_))
pending_extension_enables_.OnExtensionEnabled(extension.id());
SyncExtensionChangeIfNeeded(extension);
}
void ExtensionSyncService::SyncDisableExtension(
const extensions::Extension& extension) {
// Syncing may not have started yet, so handle pending enables.
if (extensions::util::ShouldSyncApp(&extension, profile_))
pending_app_enables_.OnExtensionDisabled(extension.id());
if (extensions::util::ShouldSyncExtension(&extension, profile_))
pending_extension_enables_.OnExtensionDisabled(extension.id());
SyncExtensionChangeIfNeeded(extension);
}
syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
CHECK(sync_processor.get());
CHECK(sync_error_factory.get());
switch (type) {
case syncer::EXTENSIONS:
extension_sync_bundle_.SetupSync(sync_processor.release(),
sync_error_factory.release(),
initial_sync_data);
pending_extension_enables_.OnSyncStarted(extension_service_);
break;
case syncer::APPS:
app_sync_bundle_.SetupSync(sync_processor.release(),
sync_error_factory.release(),
initial_sync_data);
pending_app_enables_.OnSyncStarted(extension_service_);
break;
default:
LOG(FATAL) << "Got " << type << " ModelType";
}
// Process local extensions.
// TODO(yoz): Determine whether pending extensions should be considered too.
// See crbug.com/104399.
syncer::SyncDataList sync_data_list = GetAllSyncData(type);
syncer::SyncChangeList sync_change_list;
for (syncer::SyncDataList::const_iterator i = sync_data_list.begin();
i != sync_data_list.end();
++i) {
switch (type) {
case syncer::EXTENSIONS:
sync_change_list.push_back(
extension_sync_bundle_.CreateSyncChange(*i));
break;
case syncer::APPS:
sync_change_list.push_back(app_sync_bundle_.CreateSyncChange(*i));
break;
default:
LOG(FATAL) << "Got " << type << " ModelType";
}
}
if (type == syncer::EXTENSIONS) {
extension_sync_bundle_.ProcessSyncChangeList(sync_change_list);
} else if (type == syncer::APPS) {
app_sync_bundle_.ProcessSyncChangeList(sync_change_list);
}
return syncer::SyncMergeResult(type);
}
void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
if (type == syncer::APPS) {
app_sync_bundle_.Reset();
} else if (type == syncer::EXTENSIONS) {
extension_sync_bundle_.Reset();
}
}
syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
syncer::ModelType type) const {
if (type == syncer::EXTENSIONS)
return extension_sync_bundle_.GetAllSyncData();
if (type == syncer::APPS)
return app_sync_bundle_.GetAllSyncData();
// We should only get sync data for extensions and apps.
NOTREACHED();
return syncer::SyncDataList();
}
syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
const tracked_objects::Location& from_here,
const syncer::SyncChangeList& change_list) {
for (syncer::SyncChangeList::const_iterator i = change_list.begin();
i != change_list.end();
++i) {
syncer::ModelType type = i->sync_data().GetDataType();
if (type == syncer::EXTENSIONS) {
extension_sync_bundle_.ProcessSyncChange(
extensions::ExtensionSyncData(*i));
} else if (type == syncer::APPS) {
app_sync_bundle_.ProcessSyncChange(extensions::AppSyncData(*i));
}
}
extension_prefs_->app_sorting()->FixNTPOrdinalCollisions();
return syncer::SyncError();
}
extensions::ExtensionSyncData ExtensionSyncService::GetExtensionSyncData(
const Extension& extension) const {
return extensions::ExtensionSyncData(
extension,
extension_service_->IsExtensionEnabled(extension.id()),
extensions::util::IsIncognitoEnabled(extension.id(), profile_),
extension_prefs_->HasDisableReason(extension.id(),
Extension::DISABLE_REMOTE_INSTALL));
}
extensions::AppSyncData ExtensionSyncService::GetAppSyncData(
const Extension& extension) const {
return extensions::AppSyncData(
extension,
extension_service_->IsExtensionEnabled(extension.id()),
extensions::util::IsIncognitoEnabled(extension.id(), profile_),
extension_prefs_->HasDisableReason(extension.id(),
Extension::DISABLE_REMOTE_INSTALL),
extension_prefs_->app_sorting()->GetAppLaunchOrdinal(extension.id()),
extension_prefs_->app_sorting()->GetPageOrdinal(extension.id()),
extensions::GetLaunchTypePrefValue(extension_prefs_, extension.id()));
}
std::vector<extensions::ExtensionSyncData>
ExtensionSyncService::GetExtensionSyncDataList() const {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
std::vector<extensions::ExtensionSyncData> extension_sync_list;
extension_sync_bundle_.GetExtensionSyncDataListHelper(
registry->enabled_extensions(), &extension_sync_list);
extension_sync_bundle_.GetExtensionSyncDataListHelper(
registry->disabled_extensions(), &extension_sync_list);
extension_sync_bundle_.GetExtensionSyncDataListHelper(
registry->terminated_extensions(), &extension_sync_list);
std::vector<extensions::ExtensionSyncData> pending_extensions =
extension_sync_bundle_.GetPendingData();
extension_sync_list.insert(extension_sync_list.begin(),
pending_extensions.begin(),
pending_extensions.end());
return extension_sync_list;
}
std::vector<extensions::AppSyncData> ExtensionSyncService::GetAppSyncDataList()
const {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
std::vector<extensions::AppSyncData> app_sync_list;
app_sync_bundle_.GetAppSyncDataListHelper(
registry->enabled_extensions(), &app_sync_list);
app_sync_bundle_.GetAppSyncDataListHelper(
registry->disabled_extensions(), &app_sync_list);
app_sync_bundle_.GetAppSyncDataListHelper(
registry->terminated_extensions(), &app_sync_list);
std::vector<extensions::AppSyncData> pending_apps =
app_sync_bundle_.GetPendingData();
app_sync_list.insert(app_sync_list.begin(),
pending_apps.begin(),
pending_apps.end());
return app_sync_list;
}
bool ExtensionSyncService::ProcessExtensionSyncData(
const extensions::ExtensionSyncData& extension_sync_data) {
if (!ProcessExtensionSyncDataHelper(extension_sync_data,
syncer::EXTENSIONS)) {
extension_sync_bundle_.AddPendingExtension(extension_sync_data.id(),
extension_sync_data);
extension_service_->CheckForUpdatesSoon();
return false;
}
return true;
}
bool ExtensionSyncService::ProcessAppSyncData(
const extensions::AppSyncData& app_sync_data) {
const std::string& id = app_sync_data.id();
if (app_sync_data.app_launch_ordinal().IsValid() &&
app_sync_data.page_ordinal().IsValid()) {
extension_prefs_->app_sorting()->SetAppLaunchOrdinal(
id,
app_sync_data.app_launch_ordinal());
extension_prefs_->app_sorting()->SetPageOrdinal(
id,
app_sync_data.page_ordinal());
}
// The corresponding validation of this value during AppSyncData population
// is in AppSyncData::PopulateAppSpecifics.
if (app_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
app_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
extensions::SetLaunchType(extension_service_, id,
app_sync_data.launch_type());
}
if (!app_sync_data.bookmark_app_url().empty())
ProcessBookmarkAppSyncData(app_sync_data);
if (!ProcessExtensionSyncDataHelper(app_sync_data.extension_sync_data(),
syncer::APPS)) {
app_sync_bundle_.AddPendingApp(id, app_sync_data);
extension_service_->CheckForUpdatesSoon();
return false;
}
return true;
}
void ExtensionSyncService::ProcessBookmarkAppSyncData(
const extensions::AppSyncData& app_sync_data) {
// Process bookmark app sync if necessary.
GURL bookmark_app_url(app_sync_data.bookmark_app_url());
if (!bookmark_app_url.is_valid() ||
app_sync_data.extension_sync_data().uninstalled()) {
return;
}
const extensions::Extension* extension =
extension_service_->GetInstalledExtension(
app_sync_data.extension_sync_data().id());
// Return if there are no bookmark app details that need updating.
if (extension && extension->non_localized_name() ==
app_sync_data.extension_sync_data().name() &&
extension->description() == app_sync_data.bookmark_app_description()) {
return;
}
WebApplicationInfo web_app_info;
web_app_info.app_url = bookmark_app_url;
web_app_info.title =
base::UTF8ToUTF16(app_sync_data.extension_sync_data().name());
web_app_info.description =
base::UTF8ToUTF16(app_sync_data.bookmark_app_description());
// If the bookmark app already exists, keep the old icons.
if (!extension) {
CreateOrUpdateBookmarkApp(extension_service_, web_app_info);
} else {
app_sync_data.extension_sync_data().name();
GetWebApplicationInfoFromApp(profile_,
extension,
base::Bind(&OnWebApplicationInfoLoaded,
web_app_info,
extension_service_->AsWeakPtr()));
}
}
void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
const extensions::Extension* ext = extension_service_->GetInstalledExtension(
extension_id);
if (ext)
SyncExtensionChangeIfNeeded(*ext);
}
void ExtensionSyncService::SetSyncStartFlare(
const syncer::SyncableService::StartSyncFlare& flare) {
flare_ = flare;
}
bool ExtensionSyncService::IsCorrectSyncType(const Extension& extension,
syncer::ModelType type) const {
if (type == syncer::EXTENSIONS &&
extensions::sync_helper::IsSyncableExtension(&extension)) {
return true;
}
if (type == syncer::APPS &&
extensions::sync_helper::IsSyncableApp(&extension)) {
return true;
}
return false;
}
bool ExtensionSyncService::IsPendingEnable(
const std::string& extension_id) const {
return pending_app_enables_.Contains(extension_id) ||
pending_extension_enables_.Contains(extension_id);
}
bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
const extensions::ExtensionSyncData& extension_sync_data,
syncer::ModelType type) {
const std::string& id = extension_sync_data.id();
const Extension* extension = extension_service_->GetInstalledExtension(id);
// TODO(bolms): we should really handle this better. The particularly bad
// case is where an app becomes an extension or vice versa, and we end up with
// a zombie extension that won't go away.
if (extension && !IsCorrectSyncType(*extension, type))
return true;
// Handle uninstalls first.
if (extension_sync_data.uninstalled()) {
if (!extension_service_->UninstallExtensionHelper(extension_service_, id)) {
LOG(WARNING) << "Could not uninstall extension " << id
<< " for sync";
}
return true;
}
// Extension from sync was uninstalled by the user as external extensions.
// Honor user choice and skip installation/enabling.
if (extensions::ExtensionPrefs::Get(profile_)
->IsExternalExtensionUninstalled(id)) {
LOG(WARNING) << "Extension with id " << id
<< " from sync was uninstalled as external extension";
return true;
}
// Set user settings.
// If the extension has been disabled from sync, it may not have
// been installed yet, so we don't know if the disable reason was a
// permissions increase. That will be updated once CheckPermissionsIncrease
// is called for it.
// However if the extension is marked as a remote install in sync, we know
// what the disable reason is, so set it to that directly. Note that when
// CheckPermissionsIncrease runs, it might still add permissions increase
// as a disable reason for the extension.
if (extension_sync_data.enabled()) {
extension_service_->EnableExtension(id);
} else if (!IsPendingEnable(id)) {
if (extension_sync_data.remote_install()) {
extension_service_->DisableExtension(id,
Extension::DISABLE_REMOTE_INSTALL);
} else {
extension_service_->DisableExtension(
id, Extension::DISABLE_UNKNOWN_FROM_SYNC);
}
}
// We need to cache some version information here because setting the
// incognito flag invalidates the |extension| pointer (it reloads the
// extension).
bool extension_installed = (extension != NULL);
int version_compare_result = extension ?
extension->version()->CompareTo(extension_sync_data.version()) : 0;
// If the target extension has already been installed ephemerally, it can
// be promoted to a regular installed extension and downloading from the Web
// Store is not necessary.
if (extension && extensions::util::IsEphemeralApp(id, profile_))
extension_service_->PromoteEphemeralApp(extension, true);
// Update the incognito flag.
extensions::util::SetIsIncognitoEnabled(
id, profile_, extension_sync_data.incognito_enabled());
extension = NULL; // No longer safe to use.
if (extension_installed) {
// If the extension is already installed, check if it's outdated.
if (version_compare_result < 0) {
// Extension is outdated.
return false;
}
} else {
// TODO(akalin): Replace silent update with a list of enabled
// permissions.
const bool kInstallSilently = true;
CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter =
(type == syncer::APPS) ? extensions::sync_helper::IsSyncableApp :
extensions::sync_helper::IsSyncableExtension;
if (!extension_service_->pending_extension_manager()->AddFromSync(
id,
extension_sync_data.update_url(),
filter,
kInstallSilently,
extension_sync_data.remote_install())) {
LOG(WARNING) << "Could not add pending extension for " << id;
// This means that the extension is already pending installation, with a
// non-INTERNAL location. Add to pending_sync_data, even though it will
// never be removed (we'll never install a syncable version of the
// extension), so that GetAllSyncData() continues to send it.
}
// Track pending extensions so that we can return them in GetAllSyncData().
return false;
}
return true;
}
void ExtensionSyncService::SyncExtensionChangeIfNeeded(
const Extension& extension) {
if (extensions::util::ShouldSyncApp(&extension, profile_)) {
if (app_sync_bundle_.IsSyncing())
app_sync_bundle_.SyncChangeIfNeeded(extension);
else if (extension_service_->is_ready() && !flare_.is_null())
flare_.Run(syncer::APPS);
} else if (extensions::util::ShouldSyncExtension(&extension, profile_)) {
if (extension_sync_bundle_.IsSyncing())
extension_sync_bundle_.SyncChangeIfNeeded(extension);
else if (extension_service_->is_ready() && !flare_.is_null())
flare_.Run(syncer::EXTENSIONS);
}
}