blob: 938832180e44abd74a285c49fe7520ba9715f28a [file] [log] [blame]
// Copyright 2019 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/web_applications/web_app_sync_bridge.h"
#include <memory>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/types/pass_key.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/user_display_mode.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.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_helpers.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_install_manager.h"
#include "chrome/browser/web_applications/web_app_prefs_utils.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_utils.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"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/feature_list.h"
#include "chrome/common/chrome_features.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace web_app {
std::unique_ptr<syncer::EntityData> CreateSyncEntityData(const WebApp& app) {
// The Sync System doesn't allow empty entity_data name.
DCHECK(!app.untranslated_name().empty());
auto entity_data = std::make_unique<syncer::EntityData>();
entity_data->name = app.untranslated_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(WebAppManagement::kSync);
// app_id is a hash of start_url. Parse start_url first:
const GURL start_url(sync_data.start_url());
if (!start_url.is_valid()) {
DLOG(ERROR) << "ApplySyncDataToApp: start_url parse error.";
return;
}
ManifestId manifest_id;
if (sync_data.has_relative_manifest_id()) {
manifest_id =
GenerateManifestId(sync_data.relative_manifest_id(), start_url);
} else {
manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
}
if (app->app_id() != GenerateAppIdFromManifestId(manifest_id)) {
DLOG(ERROR) << "ApplySyncDataToApp: app_id doesn't match id generated "
"from manifest id or start_url.";
return;
}
if (app->start_url().is_empty()) {
app->SetStartUrl(start_url);
} else if (app->start_url() != start_url) {
DLOG(ERROR)
<< "ApplySyncDataToApp: existing start_url doesn't match start_url.";
return;
}
if (app->manifest_id() != manifest_id) {
DLOG(ERROR) << "ApplySyncDataToApp: existing manifest_id doesn't match "
"manifest_id. "
<< app->manifest_id().spec() << " vs " << manifest_id.spec();
return;
}
// Always override user_display mode with a synced value.
app->SetUserDisplayMode(
CreateUserDisplayModeFromWebAppSpecificsUserDisplayMode(
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(WebAppRegistrarMutable* registrar)
: WebAppSyncBridge(
registrar,
std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
syncer::WEB_APPS,
base::BindRepeating(&syncer::ReportUnrecoverableError,
chrome::GetChannel()))) {}
WebAppSyncBridge::WebAppSyncBridge(
WebAppRegistrarMutable* registrar,
std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor)
: syncer::ModelTypeSyncBridge(std::move(change_processor)),
registrar_(registrar) {
DCHECK(registrar_);
}
WebAppSyncBridge::~WebAppSyncBridge() = default;
void WebAppSyncBridge::SetSubsystems(
AbstractWebAppDatabaseFactory* database_factory,
WebAppCommandManager* command_manager,
WebAppCommandScheduler* command_scheduler,
WebAppInstallManager* install_manager) {
DCHECK(database_factory);
database_ = std::make_unique<WebAppDatabase>(
database_factory,
base::BindRepeating(&WebAppSyncBridge::ReportErrorToChangeProcessor,
base::Unretained(this)));
command_manager_ = command_manager;
command_scheduler_ = command_scheduler;
install_manager_ = install_manager;
}
[[nodiscard]] ScopedRegistryUpdate WebAppSyncBridge::BeginUpdate(
CommitCallback callback) {
DCHECK(database_->is_opened());
DCHECK(!is_in_update_);
is_in_update_ = true;
return ScopedRegistryUpdate(
base::PassKey<WebAppSyncBridge>(),
std::make_unique<WebAppRegistryUpdate>(registrar_,
base::PassKey<WebAppSyncBridge>()),
base::BindOnce(&WebAppSyncBridge::CommitUpdate,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
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,
mojom::UserDisplayMode user_display_mode,
bool is_user_action) {
if (is_user_action) {
switch (user_display_mode) {
case mojom::UserDisplayMode::kStandalone:
base::RecordAction(
base::UserMetricsAction("WebApp.SetWindowMode.Window"));
break;
case mojom::UserDisplayMode::kBrowser:
base::RecordAction(base::UserMetricsAction("WebApp.SetWindowMode.Tab"));
break;
case mojom::UserDisplayMode::kTabbed:
base::RecordAction(
base::UserMetricsAction("WebApp.SetWindowMode.Tabbed"));
break;
}
}
{
ScopedRegistryUpdate update = BeginUpdate();
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::SetAppWindowControlsOverlayEnabled(const AppId& app_id,
bool enabled) {
ScopedRegistryUpdate update = BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
if (web_app) {
web_app->SetWindowControlsOverlayEnabled(enabled);
}
}
void WebAppSyncBridge::SetAppIsDisabled(AppLock& lock,
const AppId& app_id,
bool is_disabled) {
if (!IsChromeOsDataMandatory()) {
return;
}
bool notify = false;
{
ScopedRegistryUpdate update = BeginUpdate();
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::SetAppLastBadgingTime(const AppId& app_id,
const base::Time& time) {
{
ScopedRegistryUpdate update = BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
if (web_app) {
web_app->SetLastBadgingTime(time);
}
}
registrar_->NotifyWebAppLastBadgingTimeChanged(app_id, time);
}
void WebAppSyncBridge::SetAppLastLaunchTime(const AppId& app_id,
const base::Time& time) {
{
ScopedRegistryUpdate update = BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
if (web_app) {
web_app->SetLastLaunchTime(time);
}
}
registrar_->NotifyWebAppLastLaunchTimeChanged(app_id, time);
}
void WebAppSyncBridge::SetAppFirstInstallTime(const AppId& app_id,
const base::Time& time) {
{
ScopedRegistryUpdate update = BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
if (web_app) {
web_app->SetFirstInstallTime(time);
}
}
registrar_->NotifyWebAppFirstInstallTimeChanged(app_id, time);
}
void WebAppSyncBridge::SetAppManifestUpdateTime(const AppId& app_id,
const base::Time& time) {
{
ScopedRegistryUpdate update = BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
if (web_app) {
web_app->SetManifestUpdateTime(time);
}
}
}
void WebAppSyncBridge::SetUserPageOrdinal(const AppId& app_id,
syncer::StringOrdinal page_ordinal) {
ScopedRegistryUpdate update = BeginUpdate();
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(std::move(page_ordinal));
}
}
void WebAppSyncBridge::SetUserLaunchOrdinal(
const AppId& app_id,
syncer::StringOrdinal launch_ordinal) {
ScopedRegistryUpdate update = BeginUpdate();
// 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(std::move(launch_ordinal));
}
}
#if BUILDFLAG(IS_MAC)
void WebAppSyncBridge::SetAlwaysShowToolbarInFullscreen(const AppId& app_id,
bool show) {
if (!registrar_->IsInstalled(app_id)) {
return;
}
{
ScopedRegistryUpdate update = BeginUpdate();
update->UpdateApp(app_id)->SetAlwaysShowToolbarInFullscreen(show);
}
registrar_->NotifyAlwaysShowToolbarInFullscreenChanged(app_id, show);
}
#endif
void WebAppSyncBridge::SetAppFileHandlerApprovalState(const AppId& app_id,
ApiApprovalState state) {
{
ScopedRegistryUpdate update = BeginUpdate();
update->UpdateApp(app_id)->SetFileHandlerApprovalState(state);
}
registrar_->NotifyWebAppFileHandlerApprovalStateChanged(app_id);
}
void WebAppSyncBridge::CommitUpdate(
CommitCallback callback,
std::unique_ptr<WebAppRegistryUpdate> update) {
DCHECK(is_in_update_);
is_in_update_ = false;
if (update == nullptr) {
std::move(callback).Run(/*success*/ true);
return;
}
std::unique_ptr<RegistryUpdateData> update_data =
update->TakeUpdateData(base::PassKey<WebAppSyncBridge>());
// Remove all unchanged apps.
RegistryUpdateData::Apps changed_apps_to_update;
for (std::unique_ptr<WebApp>& app_to_update : update_data->apps_to_update) {
const AppId& app_id = app_to_update->app_id();
if (*app_to_update != *registrar().GetAppById(app_id)) {
changed_apps_to_update.push_back(std::move(app_to_update));
}
}
update_data->apps_to_update = std::move(changed_apps_to_update);
if (update_data->IsEmpty()) {
std::move(callback).Run(/*success*/ true);
return;
}
if (!disable_checks_for_testing_) {
CheckRegistryUpdateData(*update_data);
}
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::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->untranslated_name().empty());
DCHECK(web_app->manifest_id().is_valid());
}
for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_update) {
DCHECK(registrar_->GetAppById(web_app->app_id()));
DCHECK(!web_app->untranslated_name().empty());
DCHECK(web_app->manifest_id().is_valid());
}
for (const AppId& app_id : update_data.apps_to_delete)
DCHECK(registrar_->GetAppById(app_id));
#endif
}
void WebAppSyncBridge::UpdateRegistrar(
std::unique_ptr<RegistryUpdateData> update_data) {
registrar_->CountMutation();
for (std::unique_ptr<WebApp>& web_app : update_data->apps_to_create) {
AppId app_id = web_app->app_id();
DCHECK(!registrar_->GetAppById(app_id));
#if BUILDFLAG(IS_CHROMEOS_ASH)
// We do not install non-system web apps in Ash when Lacros web apps are
// enabled.
DCHECK(web_app->IsSystemApp() || !IsWebAppsCrosapiEnabled());
#endif
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());
registrar_->registry().erase(it);
}
}
void WebAppSyncBridge::UpdateSync(
const RegistryUpdateData& update_data,
syncer::MetadataChangeList* metadata_change_list) {
// We don't block web app subsystems on WebAppSyncBridge::MergeFullSyncData:
// we call WebAppProvider::OnRegistryControllerReady() right after
// change_processor()->ModelReadyToSync. As a result, subsystems may produce
// some local changes between OnRegistryControllerReady and MergeFullSyncData.
// Return early in this case. The processor cannot do any useful metadata
// tracking until MergeFullSyncData 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.
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();
// Already have data stored in web app system and shouldn't expect further
// callbacks once `IsTrackingMetadata` is true.
if (!on_sync_connected_.is_signaled() &&
change_processor()->IsTrackingMetadata()) {
on_sync_connected_.Signal();
}
MaybeUninstallAppsPendingUninstall();
MaybeInstallAppsFromSyncAndPendingInstallation();
}
void WebAppSyncBridge::OnDataWritten(CommitCallback callback, bool success) {
if (!success)
DLOG(ERROR) << "WebAppSyncBridge commit failed";
base::UmaHistogramBoolean("WebApp.Database.WriteResult", success);
std::move(callback).Run(success);
}
void WebAppSyncBridge::OnWebAppUninstallComplete(
const AppId& app,
webapps::UninstallResultCode code) {
base::UmaHistogramBoolean("Webapp.SyncInitiatedUninstallResult",
UninstallSucceeded(code));
}
void WebAppSyncBridge::ReportErrorToChangeProcessor(
const syncer::ModelError& error) {
change_processor()->ReportError(error);
}
void WebAppSyncBridge::MergeLocalAppsToSync(
const syncer::EntityChangeList& entity_data,
syncer::MetadataChangeList* metadata_change_list) {
auto sync_server_apps = base::MakeFlatSet<AppId>(
entity_data, {}, &syncer::EntityChange::storage_key);
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::PrepareLocalUpdateFromSyncChange(
const syncer::EntityChange& change,
RegistryUpdateData* update_local_data,
std::vector<AppId>& apps_display_mode_changed) {
// 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(WebAppManagement::kSync);
if (!app_copy->HasAnySources()) {
// Uninstallation from the local database is a two-phase commit. Setting
// this flag to true signals that uninstallation should occur, and then
// when all asynchronous uninstallation tasks are complete then the entity
// is deleted from the database.
app_copy->SetIsUninstalling(true);
}
update_local_data->apps_to_update.push_back(std::move(app_copy));
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) {
if (specifics.has_user_display_mode() &&
specifics.user_display_mode() !=
ConvertUserDisplayModeToWebAppSpecificsUserDisplayMode(
existing_web_app->user_display_mode().value())) {
apps_display_mode_changed.push_back(app_id);
}
// 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);
const GURL start_url(specifics.start_url());
if (!start_url.is_valid()) {
DLOG(ERROR) << "WebAppSyncBridge: start_url parse error.";
return;
}
// Set the manifest id first, as ApplySyncDataToApp verifies that the
// computed manifest ids match.
ManifestId manifest_id;
if (specifics.has_relative_manifest_id()) {
manifest_id =
GenerateManifestId(specifics.relative_manifest_id(), start_url);
} else {
manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
}
web_app->SetManifestId(manifest_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->untranslated_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::ApplyIncrementalSyncChangesToRegistrar(
std::unique_ptr<RegistryUpdateData> update_local_data,
const std::vector<AppId>& apps_display_mode_changed) {
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);
}
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());
UpdateRegistrar(std::move(update_local_data));
for (const AppId& app_id : apps_display_mode_changed) {
const WebApp* app = registrar_->GetAppById(app_id);
DCHECK(app->user_display_mode().has_value());
registrar_->NotifyWebAppUserDisplayModeChanged(
app_id, app->user_display_mode().value());
}
std::vector<AppId> apps_to_delete;
for (const WebApp& app : registrar_->GetAppsIncludingStubsMutable()) {
if (app.is_uninstalling())
apps_to_delete.push_back(app.app_id());
}
// Initiate any uninstall actions to clean up os integration, disk data, etc.
if (!apps_to_delete.empty()) {
auto callback =
base::BindRepeating(&WebAppSyncBridge::OnWebAppUninstallComplete,
weak_ptr_factory_.GetWeakPtr());
if (uninstall_from_sync_before_registry_update_callback_for_testing_) {
uninstall_from_sync_before_registry_update_callback_for_testing_.Run(
apps_to_delete, callback);
} else {
for (const AppId& app_id : apps_to_delete) {
command_scheduler_->UninstallWebApp(
app_id, webapps::WebappUninstallSource::kSync,
base::BindOnce(callback, app_id));
}
}
}
// Do a full follow up install for all remote entities that don’t exist
// locally.
if (!apps_to_install.empty()) {
// TODO(dmurph): Just call the InstallFromSync command.
// https://crbug.com/1328968
InstallWebAppsAfterSync(std::move(apps_to_install), base::DoNothing());
}
}
std::unique_ptr<syncer::MetadataChangeList>
WebAppSyncBridge::CreateMetadataChangeList() {
return syncer::ModelTypeStore::WriteBatch::CreateMetadataChangeList();
}
absl::optional<syncer::ModelError> WebAppSyncBridge::MergeFullSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
CHECK(change_processor()->IsTrackingMetadata());
auto update_local_data = std::make_unique<RegistryUpdateData>();
std::vector<AppId> apps_display_mode_changed;
for (const auto& change : entity_data) {
DCHECK_NE(change->type(), syncer::EntityChange::ACTION_DELETE);
PrepareLocalUpdateFromSyncChange(*change, update_local_data.get(),
apps_display_mode_changed);
}
MergeLocalAppsToSync(entity_data, metadata_change_list.get());
database_->Write(
*update_local_data, std::move(metadata_change_list),
base::BindOnce(&WebAppSyncBridge::OnDataWritten,
weak_ptr_factory_.GetWeakPtr(), base::DoNothing()));
ApplyIncrementalSyncChangesToRegistrar(std::move(update_local_data),
apps_display_mode_changed);
if (!on_sync_connected_.is_signaled()) {
on_sync_connected_.Signal();
}
return absl::nullopt;
}
absl::optional<syncer::ModelError>
WebAppSyncBridge::ApplyIncrementalSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
// `change_processor()->IsTrackingMetadata()` may be false if the sync
// metadata is invalid and ClearPersistedMetadataIfInvalid() is resetting it.
auto update_local_data = std::make_unique<RegistryUpdateData>();
std::vector<AppId> apps_display_mode_changed;
for (const auto& change : entity_changes) {
PrepareLocalUpdateFromSyncChange(*change, update_local_data.get(),
apps_display_mode_changed);
}
database_->Write(
*update_local_data, std::move(metadata_change_list),
base::BindOnce(&WebAppSyncBridge::OnDataWritten,
weak_ptr_factory_.GetWeakPtr(), base::DoNothing()));
ApplyIncrementalSyncChangesToRegistrar(std::move(update_local_data),
apps_display_mode_changed);
if (!on_sync_connected_.is_signaled()) {
on_sync_connected_.Signal();
}
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());
if (start_url.is_empty() || !start_url.is_valid()) {
DLOG(ERROR) << "GetClientTag: start_url parse error.";
return std::string();
}
ManifestId manifest_id;
if (specifics.has_relative_manifest_id()) {
manifest_id =
GenerateManifestId(specifics.relative_manifest_id(), start_url);
} else {
manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
}
return GenerateAppIdFromManifestId(manifest_id);
}
std::string WebAppSyncBridge::GetStorageKey(
const syncer::EntityData& entity_data) {
return GetClientTag(entity_data);
}
void WebAppSyncBridge::SetRetryIncompleteUninstallsCallbackForTesting(
RetryIncompleteUninstallsCallback callback) {
retry_incomplete_uninstalls_callback_for_testing_ = std::move(callback);
}
void WebAppSyncBridge::SetInstallWebAppsAfterSyncCallbackForTesting(
InstallWebAppsAfterSyncCallback callback) {
install_web_apps_after_sync_callback_for_testing_ = std::move(callback);
}
void WebAppSyncBridge::SetUninstallFromSyncCallbackForTesting(
UninstallFromSyncCallback callback) {
uninstall_from_sync_before_registry_update_callback_for_testing_ =
std::move(callback);
}
void WebAppSyncBridge::SetAppIsLocallyInstalledForTesting(
const AppId& app_id,
bool is_locally_installed) {
{
ScopedRegistryUpdate update = BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
if (web_app) {
web_app->SetIsLocallyInstalled(is_locally_installed);
}
}
install_manager_->NotifyWebAppInstalledWithOsHooks(app_id);
}
void WebAppSyncBridge::MaybeUninstallAppsPendingUninstall() {
std::vector<AppId> apps_uninstalling;
for (WebApp& app : registrar_->GetAppsIncludingStubsMutable()) {
if (app.is_uninstalling())
apps_uninstalling.push_back(app.app_id());
}
base::UmaHistogramCounts100("WebApp.Uninstall.NonSyncIncompleteCount",
apps_uninstalling.size());
// Retrying incomplete uninstalls
if (!apps_uninstalling.empty()) {
if (retry_incomplete_uninstalls_callback_for_testing_) {
retry_incomplete_uninstalls_callback_for_testing_.Run(apps_uninstalling);
return;
}
auto callback =
base::BindRepeating(&WebAppSyncBridge::OnWebAppUninstallComplete,
weak_ptr_factory_.GetWeakPtr());
for (const auto& app_id : apps_uninstalling) {
command_scheduler_->UninstallWebApp(app_id,
webapps::WebappUninstallSource::kSync,
base::BindOnce(callback, app_id));
}
}
}
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()) {
if (install_web_apps_after_sync_callback_for_testing_) {
install_web_apps_after_sync_callback_for_testing_.Run(
std::move(apps_in_sync_install), base::DoNothing());
return;
}
InstallWebAppsAfterSync(std::move(apps_in_sync_install), base::DoNothing());
}
}
void WebAppSyncBridge::InstallWebAppsAfterSync(
std::vector<WebApp*> web_apps,
RepeatingInstallCallback callback) {
if (install_web_apps_after_sync_callback_for_testing_) {
install_web_apps_after_sync_callback_for_testing_.Run(std::move(web_apps),
callback);
return;
}
for (WebApp* web_app : web_apps) {
command_scheduler_->InstallFromSync(*web_app, base::DoNothing());
}
}
} // namespace web_app