blob: 983c4ff63f17b9df3ce820021913a08e9f98f3aa [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/sync/extension_local_data_batch_uploader.h"
#include "base/barrier_callback.h"
#include "base/functional/bind.h"
#include "chrome/browser/extensions/sync/account_extension_tracker.h"
#include "chrome/browser/extensions/sync/extension_sync_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/service/local_data_description.h"
#include "extensions/browser/icon_util.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/icons/extension_icon_set.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "ui/gfx/image/image.h"
#include "url/gurl.h"
namespace extensions {
namespace {
constexpr int kBatchUploadIconSize = extension_misc::EXTENSION_ICON_SMALLISH;
// Called when the icon for `extension_id` is loaded. Must be called for all
// uploadable extensions before the `LocalDataDescription` is created.
syncer::LocalDataItemModel OnExtensionIconLoaded(
const ExtensionId& extension_id,
const std::string& extension_name,
const gfx::Image& icon) {
syncer::LocalDataItemModel extension_model;
extension_model.id = extension_id;
extension_model.title = extension_name;
GURL extension_icon_url =
icon.IsEmpty()
? GetPlaceholderIconUrl(kBatchUploadIconSize, extension_name)
: GetIconUrlFromImage(icon);
extension_model.icon =
syncer::LocalDataItemModel::PageUrlIcon(std::move(extension_icon_url));
return extension_model;
}
// Called when all uploadable extensions' icons have been loaded and a
// `LocalDataItemModel` has been created for each extension.
void OnAllExtensionModelsReady(
base::OnceCallback<void(syncer::LocalDataDescription)> callback,
std::vector<syncer::LocalDataItemModel> extension_models) {
syncer::LocalDataDescription local_data;
local_data.type = syncer::DataType::EXTENSIONS;
local_data.local_data_models = std::move(extension_models);
std::move(callback).Run(std::move(local_data));
}
} // namespace
ExtensionLocalDataBatchUploader::ExtensionLocalDataBatchUploader(
Profile* profile)
: profile_(profile) {}
ExtensionLocalDataBatchUploader::~ExtensionLocalDataBatchUploader() = default;
void ExtensionLocalDataBatchUploader::GetLocalDataDescription(
base::OnceCallback<void(syncer::LocalDataDescription)> callback) {
if (!sync_util::IsSyncingExtensionsInTransportMode(profile_) ||
!base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
std::move(callback).Run(syncer::LocalDataDescription());
return;
}
std::vector<const Extension*> uploadable_extensions =
AccountExtensionTracker::Get(profile_)->GetUploadableLocalExtensions();
if (uploadable_extensions.empty()) {
std::move(callback).Run(syncer::LocalDataDescription());
return;
}
auto barrier_callback = base::BarrierCallback<syncer::LocalDataItemModel>(
uploadable_extensions.size(),
base::BindOnce(&OnAllExtensionModelsReady, std::move(callback)));
auto* image_loader = ImageLoader::Get(profile_);
// Load each uploadable extension's icon.
for (const Extension* extension : uploadable_extensions) {
ExtensionResource icon = IconsInfo::GetIconResource(
extension, kBatchUploadIconSize, ExtensionIconSet::Match::kBigger);
if (icon.empty()) {
barrier_callback.Run(OnExtensionIconLoaded(
extension->id(), extension->name(), gfx::Image()));
} else {
gfx::Size max_size(kBatchUploadIconSize, kBatchUploadIconSize);
image_loader->LoadImageAsync(
extension, icon, max_size,
base::BindOnce(&OnExtensionIconLoaded, extension->id(),
extension->name())
.Then(barrier_callback));
}
}
}
void ExtensionLocalDataBatchUploader::TriggerLocalDataMigration() {
TriggerLocalDataMigrationForItemsInternal(/*ids_to_upload=*/std::nullopt);
}
void ExtensionLocalDataBatchUploader::TriggerLocalDataMigrationForItems(
std::vector<syncer::LocalDataItemModel::DataId> items) {
ExtensionIdSet ids_to_upload;
for (const auto& item_id : items) {
const std::string* item_id_str = std::get_if<std::string>(&item_id);
DCHECK(item_id_str);
ids_to_upload.insert(*item_id_str);
}
TriggerLocalDataMigrationForItemsInternal(std::move(ids_to_upload));
}
void ExtensionLocalDataBatchUploader::TriggerLocalDataMigrationForItemsInternal(
std::optional<ExtensionIdSet> ids_to_upload) {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile_);
AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
CHECK(!account_info.IsEmpty());
if (!sync_util::IsSyncingExtensionsInTransportMode(profile_) ||
!base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
return;
}
std::vector<const Extension*> uploadable_extensions =
AccountExtensionTracker::Get(profile_)->GetUploadableLocalExtensions();
// If the extension is specified in `ids_to_upload` or if `ids_to_upload` is
// null (implying no filter), upload it to the user's account.
for (const Extension* extension : uploadable_extensions) {
if (!ids_to_upload || ids_to_upload->contains(extension->id())) {
sync_util::UploadExtensionToAccount(profile_, *extension);
}
}
}
} // namespace extensions