blob: 6debea293fa5169b1e4353f3016b0a16c8c339cb [file] [log] [blame]
// Copyright 2018 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_install_finalizer.h"
#include <map>
#include <utility>
#include "ash/constants/web_app_id_constants.h"
#include "base/barrier_callback.h"
#include "base/check_is_test.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.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/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_integrity_block_data.h"
#include "chrome/browser/web_applications/jobs/uninstall/remove_install_source_job.h"
#include "chrome/browser/web_applications/jobs/uninstall/remove_install_url_job.h"
#include "chrome/browser/web_applications/jobs/uninstall/remove_web_app_job.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/os_integration/web_app_shortcuts_menu.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/scope_extension_info.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_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_icon_generator.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_manager.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_origin_association_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_translation_manager.h"
#include "chrome/browser/web_applications/web_app_ui_manager.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/sync/base/time.h"
#include "components/sync/protocol/web_app_specifics.pb.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webapps/isolated_web_apps/types/storage_location.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/origin.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/ash/experiences/system_web_apps/types/system_web_app_data.h"
#endif
namespace web_app {
namespace {
// Overwrite the user display mode if the install source indicates a
// user-initiated installation
bool ShouldInstallOverwriteUserDisplayMode(
webapps::WebappInstallSource source) {
using InstallSource = webapps::WebappInstallSource;
switch (source) {
case InstallSource::MENU_BROWSER_TAB:
case InstallSource::MENU_CUSTOM_TAB:
case InstallSource::AUTOMATIC_PROMPT_BROWSER_TAB:
case InstallSource::AUTOMATIC_PROMPT_CUSTOM_TAB:
case InstallSource::API_BROWSER_TAB:
case InstallSource::API_CUSTOM_TAB:
case InstallSource::AMBIENT_BADGE_BROWSER_TAB:
case InstallSource::AMBIENT_BADGE_CUSTOM_TAB:
case InstallSource::RICH_INSTALL_UI_WEBLAYER:
case InstallSource::ARC:
case InstallSource::CHROME_SERVICE:
case InstallSource::ML_PROMOTION:
case InstallSource::OMNIBOX_INSTALL_ICON:
case InstallSource::MENU_CREATE_SHORTCUT:
case InstallSource::PROFILE_MENU:
case InstallSource::ALMANAC_INSTALL_APP_URI:
case InstallSource::WEBAPK_RESTORE:
case InstallSource::OOBE_APP_RECOMMENDATIONS:
case InstallSource::WEB_INSTALL:
case InstallSource::CHROMEOS_HELP_APP:
return true;
case InstallSource::DEVTOOLS:
case InstallSource::MANAGEMENT_API:
case InstallSource::INTERNAL_DEFAULT:
case InstallSource::IWA_DEV_UI:
case InstallSource::IWA_DEV_COMMAND_LINE:
case InstallSource::IWA_GRAPHICAL_INSTALLER:
case InstallSource::IWA_EXTERNAL_POLICY:
case InstallSource::IWA_SHIMLESS_RMA:
case InstallSource::EXTERNAL_DEFAULT:
case InstallSource::EXTERNAL_POLICY:
case InstallSource::EXTERNAL_LOCK_SCREEN:
case InstallSource::SYSTEM_DEFAULT:
case InstallSource::SYNC:
case InstallSource::SUB_APP:
case InstallSource::KIOSK:
case InstallSource::PRELOADED_OEM:
case InstallSource::PRELOADED_DEFAULT:
case InstallSource::MICROSOFT_365_SETUP:
return false;
}
}
#if BUILDFLAG(IS_CHROMEOS)
// When web apps are added to sync on ChromeOS the value of
// user_display_mode_default should be set in certain cases to avoid poor sync
// install states on pre-M122 devices and non-CrOS devices with particular web
// apps.
// See switch for specific cases being mitigated against.
// See go/udm-desync#bookmark=id.cg753kjyrruo for design doc.
// TODO(b/320771282): Add automated tests.
void ApplyUserDisplayModeSyncMitigations(
const WebAppInstallFinalizer::FinalizeOptions& options,
WebApp& web_app) {
if (WebAppInstallFinalizer::
DisableUserDisplayModeSyncMitigationsForTesting()) {
return;
}
// Guaranteed by EnsureAppsHaveUserDisplayModeForCurrentPlatform().
CHECK(web_app.sync_proto().has_user_display_mode_cros());
// Don't mitigate installations from sync, this is only for installs that will
// be newly uploaded to sync.
if (options.install_surface == webapps::WebappInstallSource::SYNC) {
return;
}
// Only mitigate if web app is being added to sync.
if (options.source != WebAppManagement::Type::kSync) {
return;
}
// Don't override existing default-platform value.
if (web_app.sync_proto().has_user_display_mode_default()) {
return;
}
sync_pb::WebAppSpecifics sync_proto = web_app.sync_proto();
switch (web_app.sync_proto().user_display_mode_cros()) {
case sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER:
// Pre-M122 CrOS devices use the user_display_mode_default sync field
// instead of user_display_mode_cros. If user_display_mode_default is ever
// unset they will fallback to using kStandalone even if
// user_display_mode_cros is set to kBrowser. This mitigation ensures
// user_display_mode_default is set to kBrowser for these devices. Example
// user journey:
// - Install web app as browser shortcut on post-M122 CrOS device.
// - Sync installation to pre-M122 CrOS device.
// - Check that it is synced as a browser shortcut.
// TODO(b/321617981): Remove when there are sufficiently few pre-M122 CrOS
// devices in circulation.
sync_proto.set_user_display_mode_default(
sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);
break;
case sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE: {
// Ensure standalone averse apps don't get defaulted to kStandalone on
// non-CrOS devices via sync.
// Example user journey:
// - Install Google Docs as a standalone web app.
// - Sync installation to non-CrOS device.
// - Check that it is synced as a browser shortcut.
// TODO(b/321617972): Remove when Windows/Mac/Linux support for tabbed web
// apps is in sufficient circulation.
bool is_standalone_averse_app =
web_app.app_id() == ash::kGoogleDocsAppId ||
web_app.app_id() == ash::kGoogleSheetsAppId ||
web_app.app_id() == ash::kGoogleSlidesAppId;
if (!is_standalone_averse_app) {
break;
}
sync_proto.set_user_display_mode_default(
sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);
break;
}
case sync_pb::WebAppSpecifics_UserDisplayMode_TABBED:
// This can only be reached when kDesktopPWAsTabStripSettings is enabled,
// this is only for testing and is planned to be removed.
return;
case sync_pb::WebAppSpecifics_UserDisplayMode_UNSPECIFIED:
// Ignore unknown UserDisplayMode values.
return;
}
web_app.SetSyncProto(std::move(sync_proto));
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
WebAppInstallFinalizer::FinalizeOptions::IwaOptions::IwaOptions(
IsolatedWebAppStorageLocation location,
std::optional<IsolatedWebAppIntegrityBlockData> integrity_block_data)
: location(std::move(location)),
integrity_block_data(std::move(integrity_block_data)) {}
WebAppInstallFinalizer::FinalizeOptions::IwaOptions::~IwaOptions() = default;
WebAppInstallFinalizer::FinalizeOptions::IwaOptions::IwaOptions(
const IwaOptions&) = default;
WebAppInstallFinalizer::FinalizeOptions::FinalizeOptions(
webapps::WebappInstallSource install_surface)
: source(ConvertInstallSurfaceToWebAppSource(install_surface)),
install_surface(install_surface) {}
WebAppInstallFinalizer::FinalizeOptions::~FinalizeOptions() = default;
WebAppInstallFinalizer::FinalizeOptions::FinalizeOptions(
const FinalizeOptions&) = default;
bool& WebAppInstallFinalizer::
DisableUserDisplayModeSyncMitigationsForTesting() {
static bool disable = false;
return disable;
}
WebAppInstallFinalizer::WebAppInstallFinalizer(Profile* profile)
: profile_(profile) {}
WebAppInstallFinalizer::~WebAppInstallFinalizer() = default;
void WebAppInstallFinalizer::FinalizeInstall(
const WebAppInstallInfo& web_app_info,
const FinalizeOptions& options,
InstallFinalizedCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(crbug.com/40693380): Implement a before-start queue in
// WebAppInstallManager and replace this runtime error in
// WebAppInstallFinalizer with DCHECK(started_).
if (!started_) {
std::move(callback).Run(
webapps::AppId(), webapps::InstallResultCode::kWebAppProviderNotReady);
return;
}
webapps::ManifestId manifest_id = web_app_info.manifest_id();
// parent_app_manifest_id can only exist if installing as a sub-app.
CHECK((options.install_surface == webapps::WebappInstallSource::SUB_APP &&
web_app_info.parent_app_manifest_id.has_value()) ||
(options.install_surface != webapps::WebappInstallSource::SUB_APP &&
!web_app_info.parent_app_manifest_id.has_value()));
webapps::AppId app_id = GenerateAppIdFromManifestId(
manifest_id, web_app_info.parent_app_manifest_id);
OnDidGetWebAppOriginAssociations origin_association_validated_callback =
base::BindOnce(&WebAppInstallFinalizer::OnOriginAssociationValidated,
weak_ptr_factory_.GetWeakPtr(), web_app_info.Clone(),
options, std::move(callback), app_id);
if (options.skip_origin_association_validation ||
web_app_info.scope_extensions.empty() ||
web_app_info.validated_scope_extensions.has_value()) {
std::move(origin_association_validated_callback).Run(ScopeExtensions());
return;
}
provider_->origin_association_manager().GetWebAppOriginAssociations(
manifest_id, web_app_info.scope_extensions,
std::move(origin_association_validated_callback));
}
void WebAppInstallFinalizer::OnOriginAssociationValidated(
WebAppInstallInfo web_app_info,
FinalizeOptions options,
InstallFinalizedCallback callback,
webapps::AppId app_id,
ScopeExtensions validated_scope_extensions) {
const WebApp* existing_web_app =
provider_->registrar_unsafe().GetAppById(app_id);
std::unique_ptr<WebApp> web_app;
if (existing_web_app) {
web_app = std::make_unique<WebApp>(*existing_web_app);
} else {
// TODO(b/344718166): Ensure that manifest_id corresponds to app_id here.
web_app = std::make_unique<WebApp>(app_id);
web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
// Ensure `web_app` has a start_url and manifest_id set before other calls
// that depend on state being complete, eg. `WebApp::sync_proto()`.
web_app->SetStartUrl(web_app_info.start_url());
web_app->SetManifestId(web_app_info.manifest_id());
}
for (auto& scope_extension : validated_scope_extensions) {
// This is done to prune any queries or fragments from the scope URL which
// may have been skipped by WebAppOriginAssociationManager validation.
scope_extension = ScopeExtensionInfo::CreateForScope(
scope_extension.scope, scope_extension.has_origin_wildcard);
}
web_app->SetValidatedScopeExtensions(validated_scope_extensions);
// When testing, the database state is compared with the in-memory registry,
// and because proto time has less granularity, this comparison fails unless
// we pre-downgrade to proto time and back before saving in our database.
const base::Time now_time =
syncer::ProtoTimeToTime(syncer::TimeToProtoTime(clock_->Now()));
// The UI may initiate a full install to overwrite the existing
// non-locally-installed app. Therefore, `install_state` can be
// promoted to `INSTALLED_WITH_OS_INTEGRATION`, but not vice versa.
if (options.install_state ==
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION) {
web_app->SetInstallState(
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
// The last install time is always updated if the app has been locally
// installed, but the first install time is updated only once.
if (web_app->first_install_time().is_null()) {
web_app->SetFirstInstallTime(now_time);
}
// The last install time is updated whenever we (re)install/update.
web_app->SetLatestInstallTime(now_time);
}
// Handle going from SUGGESTED_FROM_ANOTHER_DEVICE ->
// INSTALLED_WITHOUT_OS_INTEGRATION
if (web_app->install_state() ==
proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE &&
options.install_state ==
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION) {
web_app->SetInstallState(
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION);
}
// Set |user_display_mode| and any user-controllable fields here if this
// install is user initiated or it's a new app.
if (ShouldInstallOverwriteUserDisplayMode(options.install_surface) ||
!existing_web_app) {
DCHECK(web_app_info.user_display_mode.has_value());
web_app->SetUserDisplayMode(*web_app_info.user_display_mode);
}
#if BUILDFLAG(IS_CHROMEOS)
ApplyUserDisplayModeSyncMitigations(options, *web_app);
#endif // BUILDFLAG(IS_CHROMEOS)
CHECK(HasCurrentPlatformUserDisplayMode(web_app->sync_proto()));
#if BUILDFLAG(IS_MAC)
// Only set this flag for newly installed DIY apps on Mac
if (web_app->is_diy_app() &&
(!existing_web_app || options.overwrite_existing_manifest_fields)) {
web_app->SetDiyAppIconsMaskedOnMac(true);
}
#endif
// `WebApp::chromeos_data` has a default value already. Only override if the
// caller provided a new value.
if (options.chromeos_data.has_value())
web_app->SetWebAppChromeOsData(options.chromeos_data.value());
if (provider_->policy_manager().IsWebAppInDisabledList(app_id) &&
web_app->chromeos_data().has_value() &&
!web_app->chromeos_data()->is_disabled) {
std::optional<WebAppChromeOsData> cros_data = web_app->chromeos_data();
cros_data->is_disabled = true;
web_app->SetWebAppChromeOsData(std::move(cros_data));
}
#if BUILDFLAG(IS_CHROMEOS)
// `WebApp::system_web_app_data` has a default value already. Only override if
// the caller provided a new value.
if (options.system_web_app_data.has_value()) {
web_app->client_data()->system_web_app_data =
options.system_web_app_data.value();
}
#endif
if (options.iwa_options) {
UpdateIsolationDataAndResetPendingUpdateInfo(
web_app.get(), options.iwa_options->location,
web_app_info.isolated_web_app_version,
options.iwa_options->integrity_block_data);
HostContentSettingsMap* const host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile_);
host_content_settings_map->SetContentSettingDefaultScope(
web_app_info.scope, web_app_info.scope, ContentSettingsType::POPUPS,
CONTENT_SETTING_ALLOW);
}
web_app->SetParentAppId(web_app_info.parent_app_id);
web_app->SetAdditionalSearchTerms(web_app_info.additional_search_terms);
web_app->AddSource(options.source);
if (options.source == WebAppManagement::kUserInstalled &&
IsSyncEnabledForApps(profile_)) {
web_app->AddSource(WebAppManagement::kSync);
}
web_app->SetIsFromSyncAndPendingInstallation(false);
web_app->SetLatestInstallSource(options.install_surface);
if (!web_app->generated_icon_fix().has_value()) {
web_app->SetGeneratedIconFix(web_app_info.generated_icon_fix);
}
WriteExternalConfigMapInfo(
*web_app, options.source, web_app_info.is_placeholder,
web_app_info.install_url, web_app_info.additional_policy_ids);
if (options.install_state !=
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION) {
DCHECK(!(options.add_to_applications_menu || options.add_to_desktop ||
options.add_to_quick_launch_bar))
<< "Cannot create os hooks for a non-fully installed app";
}
CommitCallback commit_callback = base::BindOnce(
&WebAppInstallFinalizer::OnDatabaseCommitCompletedForInstall,
weak_ptr_factory_.GetWeakPtr(), std::move(callback), app_id, options);
if (options.overwrite_existing_manifest_fields || !existing_web_app) {
SetWebAppManifestFieldsAndWriteData(
web_app_info, std::move(web_app), std::move(commit_callback),
options.skip_icon_writes_on_download_failure);
} else {
// Updates the web app with an additional source.
CommitToSyncBridge(std::move(web_app), std::move(commit_callback),
/*success=*/true);
}
}
void WebAppInstallFinalizer::FinalizeUpdate(
const WebAppInstallInfo& web_app_info,
InstallFinalizedCallback callback) {
CHECK(started_);
webapps::ManifestId manifest_id = web_app_info.manifest_id();
const webapps::AppId app_id = GenerateAppIdFromManifestId(manifest_id);
const WebApp* existing_web_app =
provider_->registrar_unsafe().GetAppById(app_id);
if (!existing_web_app ||
existing_web_app->is_from_sync_and_pending_installation() ||
app_id != existing_web_app->app_id()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), webapps::AppId(),
webapps::InstallResultCode::kWebAppDisabled));
return;
}
CommitCallback commit_callback = base::BindOnce(
&WebAppInstallFinalizer::OnDatabaseCommitCompletedForUpdate,
weak_ptr_factory_.GetWeakPtr(), std::move(callback), app_id,
provider_->registrar_unsafe().GetAppShortName(app_id),
GetFileHandlerUpdateAction(app_id, web_app_info), web_app_info.Clone());
auto web_app = std::make_unique<WebApp>(*existing_web_app);
if (web_app->isolation_data().has_value()) {
const std::optional<IsolationData::PendingUpdateInfo>& pending_update_info =
web_app->isolation_data()->pending_update_info();
CHECK(pending_update_info.has_value())
<< "Isolated Web Apps can only be updated if "
"`IsolationData::PendingUpdateInfo` is set.";
CHECK_EQ(web_app_info.isolated_web_app_version,
pending_update_info->version);
UpdateIsolationDataAndResetPendingUpdateInfo(
web_app.get(), pending_update_info->location,
pending_update_info->version,
pending_update_info->integrity_block_data);
}
// Prepare copy-on-write to update existing app.
// This is not reached unless the data obtained from the manifest
// update process is valid, so an invariant of the system is that
// icons are valid here.
SetWebAppManifestFieldsAndWriteData(
web_app_info, std::move(web_app), std::move(commit_callback),
/*skip_icon_writes_on_download_failure=*/false);
}
void WebAppInstallFinalizer::SetProvider(base::PassKey<WebAppProvider>,
WebAppProvider& provider) {
provider_ = &provider;
}
void WebAppInstallFinalizer::Start() {
DCHECK(!started_);
started_ = true;
}
void WebAppInstallFinalizer::Shutdown() {
started_ = false;
// TODO(crbug.com/40810770): Turn WebAppInstallFinalizer into a command so it
// can properly call callbacks on shutdown instead of dropping them on
// shutdown.
weak_ptr_factory_.InvalidateWeakPtrs();
}
void WebAppInstallFinalizer::SetClockForTesting(base::Clock* clock) {
clock_ = clock;
}
void WebAppInstallFinalizer::UpdateIsolationDataAndResetPendingUpdateInfo(
WebApp* web_app,
const IsolatedWebAppStorageLocation& location,
const base::Version& version,
std::optional<IsolatedWebAppIntegrityBlockData> integrity_block_data) {
CHECK(version.IsValid());
IsolationData::Builder builder(location, version);
if (web_app->isolation_data()) {
builder.PersistFieldsForUpdate(*web_app->isolation_data());
}
if (integrity_block_data) {
builder.SetIntegrityBlockData(std::move(*integrity_block_data));
}
web_app->SetIsolationData(std::move(builder).Build());
}
void WebAppInstallFinalizer::SetWebAppManifestFieldsAndWriteData(
const WebAppInstallInfo& web_app_info,
std::unique_ptr<WebApp> web_app,
CommitCallback commit_callback,
bool skip_icon_writes_on_download_failure) {
SetWebAppManifestFields(web_app_info, *web_app,
skip_icon_writes_on_download_failure);
webapps::AppId app_id = web_app->app_id();
auto write_translations_callback = base::BindOnce(
&WebAppInstallFinalizer::WriteTranslations,
weak_ptr_factory_.GetWeakPtr(), app_id, web_app_info.translations);
auto commit_to_sync_bridge_callback =
base::BindOnce(&WebAppInstallFinalizer::CommitToSyncBridge,
weak_ptr_factory_.GetWeakPtr(), std::move(web_app));
auto on_icon_write_complete_callback =
base::BindOnce(std::move(write_translations_callback),
base::BindOnce(std::move(commit_to_sync_bridge_callback),
std::move(commit_callback)));
// Do not overwrite the icon data in the DB if icon downloading has failed. We
// skip directly to writing translations and then writing the app via the
// WebAppSyncBridge.
if (skip_icon_writes_on_download_failure) {
std::move(on_icon_write_complete_callback).Run(/*success=*/true);
} else {
IconBitmaps icon_bitmaps = web_app_info.icon_bitmaps;
ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps =
web_app_info.shortcuts_menu_icon_bitmaps;
IconsMap other_icon_bitmaps = web_app_info.other_icon_bitmaps;
IconBitmaps trusted_icon_bitmaps = web_app_info.trusted_icon_bitmaps;
provider_->icon_manager().WriteData(
app_id, std::move(icon_bitmaps), std::move(trusted_icon_bitmaps),
std::move(shortcuts_menu_icon_bitmaps), std::move(other_icon_bitmaps),
std::move(on_icon_write_complete_callback));
}
}
void WebAppInstallFinalizer::WriteTranslations(
const webapps::AppId& app_id,
const base::flat_map<std::string, blink::Manifest::TranslationItem>&
translations,
CommitCallback commit_callback,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(commit_callback).Run(success);
return;
}
provider_->translation_manager().WriteTranslations(
app_id, translations, std::move(commit_callback));
}
void WebAppInstallFinalizer::CommitToSyncBridge(std::unique_ptr<WebApp> web_app,
CommitCallback commit_callback,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(commit_callback).Run(success);
return;
}
webapps::AppId app_id = web_app->app_id();
ScopedRegistryUpdate update =
provider_->sync_bridge_unsafe().BeginUpdate(std::move(commit_callback));
WebApp* app_to_override = update->UpdateApp(app_id);
if (app_to_override) {
*app_to_override = std::move(*web_app);
} else {
update->CreateApp(std::move(web_app));
}
}
void WebAppInstallFinalizer::OnDatabaseCommitCompletedForInstall(
InstallFinalizedCallback callback,
webapps::AppId app_id,
FinalizeOptions finalize_options,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(callback).Run(webapps::AppId(),
webapps::InstallResultCode::kWriteDataFailed);
return;
}
provider_->install_manager().NotifyWebAppInstalled(app_id);
const WebApp* web_app = provider_->registrar_unsafe().GetAppById(app_id);
// TODO(dmurph): Verify this check is not needed and remove after
// isolation work is done. https://crbug.com/1298130
if (!web_app) {
std::move(callback).Run(
webapps::AppId(),
webapps::InstallResultCode::kAppNotInRegistrarAfterCommit);
return;
}
SynchronizeOsOptions synchronize_options;
synchronize_options.add_shortcut_to_desktop = finalize_options.add_to_desktop;
synchronize_options.add_to_quick_launch_bar =
finalize_options.add_to_quick_launch_bar;
switch (finalize_options.source) {
case WebAppManagement::kSystem:
case WebAppManagement::kPolicy:
case WebAppManagement::kIwaPolicy:
case WebAppManagement::kDefault:
case WebAppManagement::kOem:
case WebAppManagement::kApsDefault:
case WebAppManagement::kIwaShimlessRma:
synchronize_options.reason = SHORTCUT_CREATION_AUTOMATED;
break;
case WebAppManagement::kKiosk:
case WebAppManagement::kSubApp:
case WebAppManagement::kWebAppStore:
case WebAppManagement::kOneDriveIntegration:
case WebAppManagement::kSync:
case WebAppManagement::kUserInstalled:
case WebAppManagement::kIwaUserInstalled:
synchronize_options.reason = SHORTCUT_CREATION_BY_USER;
break;
}
provider_->os_integration_manager().Synchronize(
app_id,
base::BindOnce(&WebAppInstallFinalizer::OnInstallHooksFinished,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
app_id),
synchronize_options);
}
void WebAppInstallFinalizer::OnInstallHooksFinished(
InstallFinalizedCallback callback,
webapps::AppId app_id) {
// Only notify that os hooks were added if the installation was a 'full'
// installation.
if (provider_->registrar_unsafe().GetInstallState(app_id) ==
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION) {
callback = std::move(callback).Then(base::BindOnce(
&WebAppInstallFinalizer::NotifyWebAppInstalledWithOsHooks,
weak_ptr_factory_.GetWeakPtr(), app_id));
}
std::move(callback).Run(app_id,
webapps::InstallResultCode::kSuccessNewInstall);
}
void WebAppInstallFinalizer::NotifyWebAppInstalledWithOsHooks(
webapps::AppId app_id) {
provider_->install_manager().NotifyWebAppInstalledWithOsHooks(app_id);
}
void WebAppInstallFinalizer::OnDatabaseCommitCompletedForUpdate(
InstallFinalizedCallback callback,
webapps::AppId app_id,
std::string old_name,
FileHandlerUpdateAction file_handlers_need_os_update,
const WebAppInstallInfo& web_app_info,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(callback).Run(webapps::AppId(),
webapps::InstallResultCode::kWriteDataFailed);
return;
}
// OS integration should always be enabled on ChromeOS for manifest updates.
bool should_skip_os_integration_on_manifest_update = false;
#if !BUILDFLAG(IS_CHROMEOS)
// If the app being updated was installed by default and not also manually
// installed by the user or an enterprise policy, disable os integration.
should_skip_os_integration_on_manifest_update =
provider_->registrar_unsafe().GetInstallState(app_id) ==
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION;
#endif // !BUILDFLAG(IS_CHROMEOS)
if (should_skip_os_integration_on_manifest_update) {
provider_->install_manager().NotifyWebAppManifestUpdated(app_id);
std::move(callback).Run(
app_id, webapps::InstallResultCode::kSuccessAlreadyInstalled);
return;
}
provider_->os_integration_manager().Synchronize(
app_id, base::BindOnce(&WebAppInstallFinalizer::OnUpdateHooksFinished,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback), app_id));
}
void WebAppInstallFinalizer::OnUpdateHooksFinished(
InstallFinalizedCallback callback,
webapps::AppId app_id) {
provider_->install_manager().NotifyWebAppManifestUpdated(app_id);
std::move(callback).Run(app_id,
webapps::InstallResultCode::kSuccessAlreadyInstalled);
}
void WebAppInstallFinalizer::WriteExternalConfigMapInfo(
WebApp& web_app,
WebAppManagement::Type source,
bool is_placeholder,
GURL install_url,
std::vector<std::string> additional_policy_ids) {
DCHECK(!(source == WebAppManagement::Type::kSync && is_placeholder));
DCHECK(!(source == WebAppManagement::Type::kUserInstalled && is_placeholder));
if (source != WebAppManagement::Type::kSync &&
source != WebAppManagement::Type::kUserInstalled &&
!WebAppManagement::IsIwaType(source)) {
web_app.AddPlaceholderInfoToManagementExternalConfigMap(source,
is_placeholder);
if (install_url.is_valid()) {
web_app.AddInstallURLToManagementExternalConfigMap(
source, std::move(install_url));
}
for (const auto& policy_id : additional_policy_ids) {
web_app.AddPolicyIdToManagementExternalConfigMap(source,
std::move(policy_id));
}
}
}
FileHandlerUpdateAction WebAppInstallFinalizer::GetFileHandlerUpdateAction(
const webapps::AppId& app_id,
const WebAppInstallInfo& new_web_app_info) {
// TODO(crbug.com/411632946): Add test case: Update file handler in
// manifest for an already installed app + override user choice by
// adding the app to file handlers policy.
if (provider_->registrar_unsafe().GetAppFileHandlerUserApprovalState(
app_id) == ApiApprovalState::kDisallowed) {
return FileHandlerUpdateAction::kNoUpdate;
}
// TODO(crbug.com/40176713): Consider trying to re-use the comparison
// results from the ManifestUpdateDataFetchCommand.
const apps::FileHandlers* old_handlers =
provider_->registrar_unsafe().GetAppFileHandlers(app_id);
DCHECK(old_handlers);
if (*old_handlers == new_web_app_info.file_handlers)
return FileHandlerUpdateAction::kNoUpdate;
return FileHandlerUpdateAction::kUpdate;
}
} // namespace web_app