blob: 09409a7550a6f93c4feea7cb6a4a78610f782ef5 [file] [log] [blame]
// Copyright 2019 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/web_applications/web_app_sync_bridge.h"
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "base/metrics/user_metrics.h"
#include "base/types/pass_key.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/app_registry_controller.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
#include "chrome/browser/web_applications/components/web_app_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_database.h"
#include "chrome/browser/web_applications/web_app_database_factory.h"
#include "chrome/browser/web_applications/web_app_proto_utils.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_install_delegate.h"
#include "chrome/common/channel_info.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/report_unrecoverable_error.h"
#include "components/sync/model/client_tag_based_model_type_processor.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/model_type_store.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/web_app_specifics.pb.h"
#include "content/public/common/content_features.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
namespace web_app {
std::unique_ptr<syncer::EntityData> CreateSyncEntityData(const WebApp& app) {
// The Sync System doesn't allow empty entity_data name.
DCHECK(!app.name().empty());
auto entity_data = std::make_unique<syncer::EntityData>();
entity_data->name = app.name();
// TODO(crbug.com/1103570): Remove this fallback later.
if (entity_data->name.empty())
entity_data->name = app.start_url().spec();
*(entity_data->specifics.mutable_web_app()) = WebAppToSyncProto(app);
return entity_data;
}
void ApplySyncDataToApp(const sync_pb::WebAppSpecifics& sync_data,
WebApp* app) {
app->AddSource(Source::kSync);
// app_id is a hash of start_url. Parse start_url first:
GURL start_url(sync_data.start_url());
if (start_url.is_empty() || !start_url.is_valid()) {
DLOG(ERROR) << "ApplySyncDataToApp: start_url parse error.";
return;
}
absl::optional<std::string> manifest_id = absl::nullopt;
if (sync_data.has_manifest_id())
manifest_id = absl::optional<std::string>(sync_data.manifest_id());
if (app->app_id() != GenerateAppId(manifest_id, start_url)) {
DLOG(ERROR) << "ApplySyncDataToApp: app_id doesn't match id generated "
"from manifest id or start_url.";
return;
}
if (!app->manifest_id().has_value()) {
app->SetManifestId(manifest_id);
} else if (app->manifest_id() != manifest_id) {
DLOG(ERROR) << "ApplySyncDataToApp: existing manifest_id doesn't match "
"manifest_id.";
return;
}
if (app->start_url().is_empty()) {
app->SetStartUrl(std::move(start_url));
} else if (app->start_url() != start_url) {
DLOG(ERROR)
<< "ApplySyncDataToApp: existing start_url doesn't match start_url.";
return;
}
// Always override user_display mode with a synced value.
app->SetUserDisplayMode(ToMojomDisplayMode(sync_data.user_display_mode()));
app->SetUserPageOrdinal(syncer::StringOrdinal(sync_data.user_page_ordinal()));
app->SetUserLaunchOrdinal(
syncer::StringOrdinal(sync_data.user_launch_ordinal()));
absl::optional<WebApp::SyncFallbackData> parsed_sync_fallback_data =
ParseSyncFallbackDataStruct(sync_data);
if (!parsed_sync_fallback_data.has_value()) {
// ParseSyncFallbackDataStruct() reports any errors.
return;
}
app->SetSyncFallbackData(std::move(parsed_sync_fallback_data.value()));
}
WebAppSyncBridge::WebAppSyncBridge(
Profile* profile,
AbstractWebAppDatabaseFactory* database_factory,
WebAppRegistrarMutable* registrar,
SyncInstallDelegate* install_delegate)
: WebAppSyncBridge(
profile,
database_factory,
registrar,
install_delegate,
std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
syncer::WEB_APPS,
base::BindRepeating(&syncer::ReportUnrecoverableError,
chrome::GetChannel()))) {}
WebAppSyncBridge::WebAppSyncBridge(
Profile* profile,
AbstractWebAppDatabaseFactory* database_factory,
WebAppRegistrarMutable* registrar,
SyncInstallDelegate* install_delegate,
std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor)
: AppRegistryController(profile),
syncer::ModelTypeSyncBridge(std::move(change_processor)),
registrar_(registrar),
install_delegate_(install_delegate) {
DCHECK(database_factory);
DCHECK(registrar_);
database_ = std::make_unique<WebAppDatabase>(
database_factory,
base::BindRepeating(&WebAppSyncBridge::ReportErrorToChangeProcessor,
base::Unretained(this)));
}
WebAppSyncBridge::~WebAppSyncBridge() = default;
std::unique_ptr<WebAppRegistryUpdate> WebAppSyncBridge::BeginUpdate() {
DCHECK(database_->is_opened());
DCHECK(!is_in_update_);
is_in_update_ = true;
return std::make_unique<WebAppRegistryUpdate>(
registrar_, base::PassKey<WebAppSyncBridge>());
}
void WebAppSyncBridge::CommitUpdate(
std::unique_ptr<WebAppRegistryUpdate> update,
CommitCallback callback) {
DCHECK(is_in_update_);
is_in_update_ = false;
if (update == nullptr || update->update_data().IsEmpty()) {
std::move(callback).Run(/*success*/ true);
return;
}
CheckRegistryUpdateData(update->update_data());
std::unique_ptr<RegistryUpdateData> update_data = update->TakeUpdateData();
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list =
CreateMetadataChangeList();
UpdateSync(*update_data, metadata_change_list.get());
database_->Write(
*update_data, std::move(metadata_change_list),
base::BindOnce(&WebAppSyncBridge::OnDataWritten,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
UpdateRegistrar(std::move(update_data));
}
void WebAppSyncBridge::Init(base::OnceClosure callback) {
database_->OpenDatabase(base::BindOnce(&WebAppSyncBridge::OnDatabaseOpened,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void WebAppSyncBridge::SetAppUserDisplayMode(const AppId& app_id,
DisplayMode user_display_mode,
bool is_user_action) {
if (is_user_action) {
switch (user_display_mode) {
case DisplayMode::kStandalone:
base::RecordAction(
base::UserMetricsAction("WebApp.SetWindowMode.Window"));
break;
case DisplayMode::kBrowser:
base::RecordAction(base::UserMetricsAction("WebApp.SetWindowMode.Tab"));
break;
default:
NOTREACHED();
}
}
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetUserDisplayMode(user_display_mode);
registrar_->NotifyWebAppUserDisplayModeChanged(app_id, user_display_mode);
}
void WebAppSyncBridge::SetAppRunOnOsLoginMode(const AppId& app_id,
RunOnOsLoginMode mode) {
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetRunOnOsLoginMode(mode);
}
void WebAppSyncBridge::SetAppWindowControlsOverlayEnabled(const AppId& app_id,
bool enabled) {
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetWindowControlsOverlayEnabled(enabled);
}
void WebAppSyncBridge::SetAppIsDisabled(const AppId& app_id, bool is_disabled) {
if (!IsChromeOsDataMandatory())
return;
bool notify = false;
{
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (!web_app)
return;
absl::optional<WebAppChromeOsData> cros_data = web_app->chromeos_data();
DCHECK(cros_data.has_value());
if (cros_data->is_disabled != is_disabled) {
cros_data->is_disabled = is_disabled;
web_app->SetWebAppChromeOsData(std::move(cros_data));
notify = true;
}
}
if (notify)
registrar_->NotifyWebAppDisabledStateChanged(app_id, is_disabled);
}
void WebAppSyncBridge::UpdateAppsDisableMode() {
if (!IsChromeOsDataMandatory())
return;
registrar_->NotifyWebAppsDisabledModeChanged();
}
void WebAppSyncBridge::SetExperimentalTabbedWindowMode(const AppId& app_id,
bool enabled,
bool is_user_action) {
if (enabled) {
DCHECK(base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip));
UpdateBoolWebAppPref(profile()->GetPrefs(), app_id,
kExperimentalTabbedWindowMode, true);
// Set non-experimental window mode to standalone for when the user disables
// this flag.
SetAppUserDisplayMode(app_id, DisplayMode::kStandalone, is_user_action);
} else {
RemoveWebAppPref(profile()->GetPrefs(), app_id,
kExperimentalTabbedWindowMode);
}
registrar_->NotifyWebAppExperimentalTabbedWindowModeChanged(app_id, enabled);
}
void WebAppSyncBridge::SetAppIsLocallyInstalled(const AppId& app_id,
bool is_locally_installed) {
{
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetIsLocallyInstalled(is_locally_installed);
}
registrar_->NotifyWebAppLocallyInstalledStateChanged(app_id,
is_locally_installed);
}
void WebAppSyncBridge::SetAppLastBadgingTime(const AppId& app_id,
const base::Time& time) {
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetLastBadgingTime(time);
}
void WebAppSyncBridge::SetAppLastLaunchTime(const AppId& app_id,
const base::Time& time) {
{
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetLastLaunchTime(time);
}
registrar_->NotifyWebAppLastLaunchTimeChanged(app_id, time);
}
void WebAppSyncBridge::SetAppInstallTime(const AppId& app_id,
const base::Time& time) {
{
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetInstallTime(time);
}
registrar_->NotifyWebAppInstallTimeChanged(app_id, time);
}
WebAppSyncBridge* WebAppSyncBridge::AsWebAppSyncBridge() {
return this;
}
void WebAppSyncBridge::SetUserPageOrdinal(const AppId& app_id,
syncer::StringOrdinal page_ordinal) {
ScopedRegistryUpdate update(this);
WebApp* web_app = update->UpdateApp(app_id);
// Due to the extensions sync system setting ordinals on sync, this can get
// called before the app is installed in the web apps system. Until apps are
// no longer double-installed on both systems, ignore this case.
// https://crbug.com/1101781
if (!registrar_->IsInstalled(app_id))
return;
if (web_app)
web_app->SetUserPageOrdinal(page_ordinal);
}
void WebAppSyncBridge::SetUserLaunchOrdinal(
const AppId& app_id,
syncer::StringOrdinal launch_ordinal) {
ScopedRegistryUpdate update(this);
// Due to the extensions sync system setting ordinals on sync, this can get
// called before the app is installed in the web apps system. Until apps are
// no longer double-installed on both systems, ignore this case.
// https://crbug.com/1101781
if (!registrar_->IsInstalled(app_id))
return;
WebApp* web_app = update->UpdateApp(app_id);
if (web_app)
web_app->SetUserLaunchOrdinal(launch_ordinal);
}
void WebAppSyncBridge::CheckRegistryUpdateData(
const RegistryUpdateData& update_data) const {
#if DCHECK_IS_ON()
for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_create) {
DCHECK(!registrar_->GetAppById(web_app->app_id()));
DCHECK(!web_app->name().empty());
}
for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_update) {
DCHECK(registrar_->GetAppById(web_app->app_id()));
DCHECK(!web_app->name().empty());
}
for (const AppId& app_id : update_data.apps_to_delete)
DCHECK(registrar_->GetAppById(app_id));
#endif
}
std::vector<std::unique_ptr<WebApp>> WebAppSyncBridge::UpdateRegistrar(
std::unique_ptr<RegistryUpdateData> update_data) {
registrar_->CountMutation();
std::vector<std::unique_ptr<WebApp>> apps_unregistered;
for (std::unique_ptr<WebApp>& web_app : update_data->apps_to_create) {
AppId app_id = web_app->app_id();
DCHECK(!registrar_->GetAppById(app_id));
DCHECK(web_app->IsSystemApp() || AreWebAppsUserInstallable(profile()));
registrar_->registry().emplace(std::move(app_id), std::move(web_app));
}
for (std::unique_ptr<WebApp>& web_app : update_data->apps_to_update) {
WebApp* original_web_app = registrar_->GetAppByIdMutable(web_app->app_id());
DCHECK(original_web_app);
DCHECK_EQ(web_app->IsSystemApp(), original_web_app->IsSystemApp());
// Commit previously created copy into original. Preserve original web_app
// object pointer value (the object's identity) to support stored pointers.
*original_web_app = std::move(*web_app);
}
for (const AppId& app_id : update_data->apps_to_delete) {
auto it = registrar_->registry().find(app_id);
DCHECK(it != registrar_->registry().end());
apps_unregistered.push_back(std::move(it->second));
registrar_->registry().erase(it);
}
return apps_unregistered;
}
void WebAppSyncBridge::UpdateSync(
const RegistryUpdateData& update_data,
syncer::MetadataChangeList* metadata_change_list) {
// We don't block web app subsystems on WebAppSyncBridge::MergeSyncData: we
// call WebAppProvider::OnRegistryControllerReady() right after
// change_processor()->ModelReadyToSync. As a result, subsystems may produce
// some local changes between OnRegistryControllerReady and MergeSyncData.
// Return early in this case. The processor cannot do any useful metadata
// tracking until MergeSyncData is called:
if (!change_processor()->IsTrackingMetadata())
return;
for (const std::unique_ptr<WebApp>& new_app : update_data.apps_to_create) {
if (new_app->IsSynced()) {
change_processor()->Put(new_app->app_id(), CreateSyncEntityData(*new_app),
metadata_change_list);
}
}
for (const std::unique_ptr<WebApp>& new_state : update_data.apps_to_update) {
const AppId& app_id = new_state->app_id();
// Find the current state of the app to be overritten.
const WebApp* current_state = registrar_->GetAppById(app_id);
DCHECK(current_state);
// Include the app in the sync "view" if IsSynced flag becomes true. Update
// the app if IsSynced flag stays true. Exclude the app from the sync "view"
// if IsSynced flag becomes false.
//
// TODO(loyso): Send an update to sync server only if any sync-specific
// data was changed. Implement some dirty flags in WebApp setter methods.
if (new_state->IsSynced()) {
change_processor()->Put(app_id, CreateSyncEntityData(*new_state),
metadata_change_list);
} else if (current_state->IsSynced()) {
change_processor()->Delete(app_id, metadata_change_list);
}
}
for (const AppId& app_id_to_delete : update_data.apps_to_delete) {
const WebApp* current_state = registrar_->GetAppById(app_id_to_delete);
DCHECK(current_state);
// Exclude the app from the sync "view" if IsSynced flag was true.
if (current_state->IsSynced())
change_processor()->Delete(app_id_to_delete, metadata_change_list);
}
}
void WebAppSyncBridge::OnDatabaseOpened(
base::OnceClosure callback,
Registry registry,
std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
DCHECK(database_->is_opened());
// Provide sync metadata to the processor _before_ any local changes occur.
change_processor()->ModelReadyToSync(std::move(metadata_batch));
registrar_->InitRegistry(std::move(registry));
std::move(callback).Run();
MaybeInstallAppsFromSyncAndPendingInstallation();
}
void WebAppSyncBridge::OnDataWritten(CommitCallback callback, bool success) {
if (!success)
DLOG(ERROR) << "WebAppSyncBridge commit failed";
std::move(callback).Run(success);
}
void WebAppSyncBridge::WebAppUninstalled(const AppId& app, bool uninstalled) {
// In the case `uninstalled` is false, the AppId should still be removed from
// the set, since uninstall failures are not yet handled, and there are no
// uninstall retry attempts.
apps_in_sync_uninstall_.erase(app);
}
void WebAppSyncBridge::ReportErrorToChangeProcessor(
const syncer::ModelError& error) {
change_processor()->ReportError(error);
}
void WebAppSyncBridge::MergeLocalAppsToSync(
const syncer::EntityChangeList& entity_data,
syncer::MetadataChangeList* metadata_change_list) {
// Build a helper set of the sync server apps to speed up lookups. The
// flat_set will reuse the underlying memory of this vector. app_id is storage
// key.
std::vector<AppId> storage_keys;
storage_keys.reserve(entity_data.size());
for (const auto& change : entity_data)
storage_keys.push_back(change->storage_key());
// Sort only once.
base::flat_set<AppId> sync_server_apps(std::move(storage_keys));
for (const WebApp& app : registrar_->GetAppsIncludingStubs()) {
if (!app.IsSynced())
continue;
bool exists_remotely = sync_server_apps.contains(app.app_id());
if (!exists_remotely) {
change_processor()->Put(app.app_id(), CreateSyncEntityData(app),
metadata_change_list);
}
}
}
void WebAppSyncBridge::ApplySyncDataChange(
const syncer::EntityChange& change,
RegistryUpdateData* update_local_data) {
// app_id is storage key.
const AppId& app_id = change.storage_key();
const WebApp* existing_web_app = registrar_->GetAppByIdMutable(app_id);
// Handle deletion first.
if (change.type() == syncer::EntityChange::ACTION_DELETE) {
if (!existing_web_app) {
DLOG(ERROR) << "ApplySyncDataChange error: no app to delete";
return;
}
// Do copy on write:
auto app_copy = std::make_unique<WebApp>(*existing_web_app);
app_copy->RemoveSource(Source::kSync);
if (app_copy->HasAnySources())
update_local_data->apps_to_update.push_back(std::move(app_copy));
else
update_local_data->apps_to_delete.push_back(app_id);
return;
}
// Handle EntityChange::ACTION_ADD and EntityChange::ACTION_UPDATE.
DCHECK(change.data().specifics.has_web_app());
const sync_pb::WebAppSpecifics& specifics = change.data().specifics.web_app();
if (existing_web_app) {
// Any entities that appear in both sets must be merged.
// Do copy on write:
auto app_copy = std::make_unique<WebApp>(*existing_web_app);
ApplySyncDataToApp(specifics, app_copy.get());
// Preserve web_app->is_locally_installed user's choice here.
update_local_data->apps_to_update.push_back(std::move(app_copy));
} else {
// Any remote entities that don’t exist locally must be written to local
// storage.
auto web_app = std::make_unique<WebApp>(app_id);
// Request a followup sync-initiated install for this stub app to fetch
// full local data and all the icons.
web_app->SetIsFromSyncAndPendingInstallation(true);
// The sync system requires non-empty name, populate temp name from
// the fallback sync data name:
web_app->SetName(specifics.name());
// Or use syncer::EntityData::name as a last resort.
if (web_app->name().empty())
web_app->SetName(change.data().name);
ApplySyncDataToApp(specifics, web_app.get());
// For a new app, automatically choose if we want to install it locally.
web_app->SetIsLocallyInstalled(AreAppsLocallyInstalledBySync());
update_local_data->apps_to_create.push_back(std::move(web_app));
}
}
void WebAppSyncBridge::ApplySyncChangesToRegistrar(
std::unique_ptr<RegistryUpdateData> update_local_data) {
if (update_local_data->IsEmpty())
return;
// Notify observers that web apps will be updated.
// Prepare a short living read-only "view" to support const correctness:
// observers must not modify the |new_apps_state|.
if (!update_local_data->apps_to_update.empty()) {
std::vector<const WebApp*> new_apps_state;
new_apps_state.reserve(update_local_data->apps_to_update.size());
for (const std::unique_ptr<WebApp>& new_web_app_state :
update_local_data->apps_to_update) {
new_apps_state.push_back(new_web_app_state.get());
}
registrar_->NotifyWebAppsWillBeUpdatedFromSync(new_apps_state);
}
// Initiate any uninstall actions that need to happen before the app is
// removed from the registry. This includes starting ot uninstall os hooks,
// and notify observers WebAppWillBeUninstalled.
if (!update_local_data->apps_to_delete.empty()) {
install_delegate_->UninstallFromSyncBeforeRegistryUpdate(
update_local_data->apps_to_delete);
}
std::vector<WebApp*> apps_to_install;
for (const auto& web_app : update_local_data->apps_to_create)
apps_to_install.push_back(web_app.get());
std::vector<std::unique_ptr<WebApp>> apps_unregistered =
UpdateRegistrar(std::move(update_local_data));
// Do a full follow up install for all remote entities that don’t exist
// locally.
if (!apps_to_install.empty()) {
install_delegate_->InstallWebAppsAfterSync(std::move(apps_to_install),
base::DoNothing());
}
// Do a full follow up uninstall for all deleted remote entities that exist
// locally and not needed by other sources. We need to clean up disk data
// (icons).
if (!apps_unregistered.empty()) {
for (const auto& web_app : apps_unregistered) {
apps_in_sync_uninstall_.insert(web_app->app_id());
}
install_delegate_->UninstallFromSyncAfterRegistryUpdate(
std::move(apps_unregistered),
base::BindRepeating(&WebAppSyncBridge::WebAppUninstalled,
weak_ptr_factory_.GetWeakPtr()));
}
}
std::unique_ptr<syncer::MetadataChangeList>
WebAppSyncBridge::CreateMetadataChangeList() {
return syncer::ModelTypeStore::WriteBatch::CreateMetadataChangeList();
}
absl::optional<syncer::ModelError> WebAppSyncBridge::MergeSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
CHECK(change_processor()->IsTrackingMetadata());
auto update_local_data = std::make_unique<RegistryUpdateData>();
for (const auto& change : entity_data) {
DCHECK_NE(change->type(), syncer::EntityChange::ACTION_DELETE);
ApplySyncDataChange(*change, update_local_data.get());
}
MergeLocalAppsToSync(entity_data, metadata_change_list.get());
database_->Write(*update_local_data, std::move(metadata_change_list),
base::DoNothing());
ApplySyncChangesToRegistrar(std::move(update_local_data));
return absl::nullopt;
}
absl::optional<syncer::ModelError> WebAppSyncBridge::ApplySyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
CHECK(change_processor()->IsTrackingMetadata());
auto update_local_data = std::make_unique<RegistryUpdateData>();
for (const auto& change : entity_changes)
ApplySyncDataChange(*change, update_local_data.get());
database_->Write(*update_local_data, std::move(metadata_change_list),
base::DoNothing());
ApplySyncChangesToRegistrar(std::move(update_local_data));
return absl::nullopt;
}
void WebAppSyncBridge::GetData(StorageKeyList storage_keys,
DataCallback callback) {
auto data_batch = std::make_unique<syncer::MutableDataBatch>();
for (const AppId& app_id : storage_keys) {
const WebApp* app = registrar_->GetAppById(app_id);
if (app && app->IsSynced())
data_batch->Put(app->app_id(), CreateSyncEntityData(*app));
}
std::move(callback).Run(std::move(data_batch));
}
void WebAppSyncBridge::GetAllDataForDebugging(DataCallback callback) {
auto data_batch = std::make_unique<syncer::MutableDataBatch>();
for (const WebApp& app : registrar_->GetAppsIncludingStubs()) {
if (app.IsSynced())
data_batch->Put(app.app_id(), CreateSyncEntityData(app));
}
std::move(callback).Run(std::move(data_batch));
}
std::string WebAppSyncBridge::GetClientTag(
const syncer::EntityData& entity_data) {
DCHECK(entity_data.specifics.has_web_app());
const sync_pb::WebAppSpecifics& specifics = entity_data.specifics.web_app();
const GURL start_url(specifics.start_url());
DCHECK(!start_url.is_empty());
DCHECK(start_url.is_valid());
absl::optional<std::string> manifest_id = absl::nullopt;
if (specifics.has_manifest_id())
manifest_id = absl::optional<std::string>(specifics.manifest_id());
return GenerateAppId(manifest_id, start_url);
}
std::string WebAppSyncBridge::GetStorageKey(
const syncer::EntityData& entity_data) {
return GetClientTag(entity_data);
}
const std::set<AppId>& WebAppSyncBridge::GetAppsInSyncUninstallForTest() {
return apps_in_sync_uninstall_;
}
void WebAppSyncBridge::MaybeInstallAppsFromSyncAndPendingInstallation() {
std::vector<WebApp*> apps_in_sync_install;
for (WebApp& app : registrar_->GetAppsIncludingStubsMutable()) {
if (app.is_from_sync_and_pending_installation())
apps_in_sync_install.push_back(&app);
}
if (!apps_in_sync_install.empty()) {
install_delegate_->InstallWebAppsAfterSync(std::move(apps_in_sync_install),
base::DoNothing());
}
}
} // namespace web_app