普通文本  |  556行  |  20.94 KB

// 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);
  }
}