blob: 396ff57e47ae5f847e755e07d3c3566a2e1f2869 [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/web_applications/commands/manifest_silent_update_command.h"
#include <optional>
#include "base/containers/contains.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/web_applications/icons/trusted_icon_filter.h"
#include "chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.h"
#include "chrome/browser/web_applications/locks/noop_lock.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_helpers.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_origin_association_manager.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/common/chrome_features.h"
#include "components/webapps/browser/image_visual_diff.h"
#include "content/public/browser/web_contents.h"
namespace web_app {
namespace {
bool AreNonSecuritySensitiveDataChangesNeeded(
const WebApp& existing_web_app,
const ShortcutsMenuIconBitmaps* existing_shortcuts_menu_icon_bitmaps,
const WebAppInstallInfo& new_install_info) {
if (existing_web_app.manifest_id() != new_install_info.manifest_id()) {
return true;
}
if (existing_web_app.start_url() != new_install_info.start_url()) {
return true;
}
if (existing_web_app.theme_color() != new_install_info.theme_color) {
return true;
}
if (existing_web_app.scope() != new_install_info.scope) {
return true;
}
if (existing_web_app.display_mode() != new_install_info.display_mode) {
return true;
}
if (existing_web_app.display_mode_override() !=
new_install_info.display_override) {
return true;
}
if (existing_web_app.shortcuts_menu_item_infos() !=
new_install_info.shortcuts_menu_item_infos) {
return true;
}
if (existing_web_app.share_target() != new_install_info.share_target) {
return true;
}
if (existing_web_app.protocol_handlers() !=
new_install_info.protocol_handlers) {
return true;
}
if (existing_web_app.note_taking_new_note_url() !=
new_install_info.note_taking_new_note_url) {
return true;
}
if (existing_web_app.file_handlers() != new_install_info.file_handlers) {
return true;
}
if (existing_web_app.background_color() !=
new_install_info.background_color) {
return true;
}
if (existing_web_app.dark_mode_theme_color() !=
new_install_info.dark_mode_theme_color) {
return true;
}
if (existing_web_app.dark_mode_background_color() !=
new_install_info.dark_mode_background_color) {
return true;
}
if (existing_web_app.launch_handler() != new_install_info.launch_handler) {
return true;
}
if (existing_web_app.permissions_policy() !=
new_install_info.permissions_policy) {
return true;
}
if (existing_shortcuts_menu_icon_bitmaps &&
*existing_shortcuts_menu_icon_bitmaps !=
new_install_info.shortcuts_menu_icon_bitmaps) {
return true;
}
if (existing_web_app.scope_extensions() !=
new_install_info.scope_extensions) {
return true;
}
if (new_install_info.validated_scope_extensions.has_value() &&
existing_web_app.validated_scope_extensions() !=
new_install_info.validated_scope_extensions.value()) {
return true;
}
if (existing_web_app.tab_strip() != new_install_info.tab_strip) {
return true;
}
if (existing_web_app.related_applications() !=
new_install_info.related_applications) {
return true;
}
// TODO(crbug.com/424246884): Check more manifest fields.
return false;
}
sync_pb::WebAppIconInfo_Purpose ConvertIconPurposeToSyncPurpose(
apps::IconInfo::Purpose purpose) {
switch (purpose) {
case apps::IconInfo::Purpose::kAny:
return sync_pb::WebAppIconInfo_Purpose::WebAppIconInfo_Purpose_ANY;
case apps::IconInfo::Purpose::kMonochrome:
return sync_pb::WebAppIconInfo_Purpose::WebAppIconInfo_Purpose_MONOCHROME;
case apps::IconInfo::Purpose::kMaskable:
return sync_pb::WebAppIconInfo_Purpose::WebAppIconInfo_Purpose_MASKABLE;
}
}
blink::mojom::ManifestImageResource_Purpose
ConvertIconPurposeToManifestImagePurpose(apps::IconInfo::Purpose app_purpose) {
switch (app_purpose) {
case apps::IconInfo::Purpose::kAny:
return blink::mojom::ManifestImageResource_Purpose::ANY;
case apps::IconInfo::Purpose::kMonochrome:
return blink::mojom::ManifestImageResource_Purpose::MONOCHROME;
case apps::IconInfo::Purpose::kMaskable:
return blink::mojom::ManifestImageResource_Purpose::MASKABLE;
}
}
bool HasSecuritySensitiveChangesForPendingUpdate(
const proto::PendingUpdateInfo& pending_update_info) {
return pending_update_info.has_name() ||
(!pending_update_info.trusted_icons().empty() &&
!pending_update_info.manifest_icons().empty());
}
void CopyIconsToPendingUpdateInfo(
const std::vector<apps::IconInfo>& icon_infos,
google::protobuf::RepeatedPtrField<sync_pb::WebAppIconInfo>*
destination_icons) {
for (const auto& icon_info : icon_infos) {
sync_pb::WebAppIconInfo* pending_icon = destination_icons->Add();
pending_icon->set_url(icon_info.url.spec());
sync_pb::WebAppIconInfo_Purpose icon_purpose =
ConvertIconPurposeToSyncPurpose(icon_info.purpose);
pending_icon->set_purpose(icon_purpose);
if (icon_info.square_size_px.has_value()) {
pending_icon->set_size_in_px(icon_info.square_size_px.value());
}
}
}
} // namespace
std::ostream& operator<<(std::ostream& os,
ManifestSilentUpdateCommandStage stage) {
switch (stage) {
case ManifestSilentUpdateCommandStage::kFetchingNewManifestData:
return os << "kFetchingNewManifestData";
case ManifestSilentUpdateCommandStage::kLoadingExistingManifestData:
return os << "kLoadingExistingManifestData";
case ManifestSilentUpdateCommandStage::kAcquiringAppLock:
return os << "kAcquiringAppLock";
case ManifestSilentUpdateCommandStage::kComparingManifestData:
return os << "kComparingManifestData";
case ManifestSilentUpdateCommandStage::kFinalizingSilentManifestChanges:
return os << "kFinalizingSilentManifestChanges";
case ManifestSilentUpdateCommandStage::
kWritingPendingUpdateIconBitmapsToDisk:
return os << "kWritingPendingUpdateIconBitmapsToDisk";
case ManifestSilentUpdateCommandStage::kCompleteCommand:
return os << "kCompleteCommand";
}
}
std::ostream& operator<<(std::ostream& os,
ManifestSilentUpdateCheckResult stage) {
switch (stage) {
case ManifestSilentUpdateCheckResult::kAppNotInstalled:
return os << "kAppNotInstalled";
case ManifestSilentUpdateCheckResult::kAppUpdateFailedDuringInstall:
return os << "kAppUpdateFailedDuringInstall";
case ManifestSilentUpdateCheckResult::kSystemShutdown:
return os << "kSystemShutdown";
case ManifestSilentUpdateCheckResult::kAppSilentlyUpdated:
return os << "kAppSilentlyUpdated";
case ManifestSilentUpdateCheckResult::kAppUpToDate:
return os << "kAppUpToDate";
case ManifestSilentUpdateCheckResult::kIconReadFromDiskFailed:
return os << "kIconReadFromDiskFailed";
case ManifestSilentUpdateCheckResult::kWebContentsDestroyed:
return os << "kWebContentsDestroyed";
case ManifestSilentUpdateCheckResult::kAppOnlyHasSecurityUpdate:
return os << "kAppOnlyHasSecurityUpdate";
case ManifestSilentUpdateCheckResult::kAppHasNonSecurityAndSecurityChanges:
return os << "kAppHasNonSecurityAndSecurityChanges";
case ManifestSilentUpdateCheckResult::kPendingIconWriteToDiskFailed:
return os << "kPendingIconWriteToDiskFailed";
case ManifestSilentUpdateCheckResult::kInvalidManifest:
return os << "kInvalidManifest";
case ManifestSilentUpdateCheckResult::kInvalidPendingUpdateInfo:
return os << "kInvalidPendingUpdateInfo";
}
}
ManifestSilentUpdateCommand::ManifestSilentUpdateCommand(
const GURL& url,
base::WeakPtr<content::WebContents> web_contents,
CompletedCallback callback,
std::unique_ptr<WebAppDataRetriever> data_retriever,
std::unique_ptr<WebAppIconDownloader> icon_downloader)
: WebAppCommand<NoopLock, ManifestSilentUpdateCheckResult>(
"ManifestSilentUpdateCommand",
NoopLockDescription(),
base::BindOnce([](ManifestSilentUpdateCheckResult result) {
base::UmaHistogramEnumeration(
"Webapp.Update.ManifestSilentUpdateCheckResult", result);
return result;
}).Then(std::move(callback)),
/*args_for_shutdown=*/
std::make_tuple(ManifestSilentUpdateCheckResult::kSystemShutdown)),
url_(url),
web_contents_(web_contents),
data_retriever_(std::move(data_retriever)),
icon_downloader_(std::move(icon_downloader)) {
GetMutableDebugValue().Set("url", url_.spec());
GetMutableDebugValue().Set("stage", base::ToString(stage_));
}
ManifestSilentUpdateCommand::~ManifestSilentUpdateCommand() = default;
void ManifestSilentUpdateCommand::StartWithLock(
std::unique_ptr<NoopLock> lock) {
lock_ = std::move(lock);
if (IsWebContentsDestroyed()) {
AbortCommandOnWebContentsDestruction();
return;
}
Observe(web_contents_.get());
// ManifestSilentUpdateCommandStage::kAcquiringAppLock:
stage_ = ManifestSilentUpdateCommandStage::kAcquiringAppLock;
data_retriever_->CheckInstallabilityAndRetrieveManifest(
web_contents_.get(),
base::BindOnce(
&ManifestSilentUpdateCommand::OnManifestFetchedAcquireAppLock,
GetWeakPtr()),
webapps::InstallableParams());
}
void ManifestSilentUpdateCommand::OnManifestFetchedAcquireAppLock(
blink::mojom::ManifestPtr opt_manifest,
bool valid_manifest_for_web_app,
webapps::InstallableStatusCode installable_status) {
CHECK_EQ(stage_, ManifestSilentUpdateCommandStage::kAcquiringAppLock);
if (IsWebContentsDestroyed()) {
AbortCommandOnWebContentsDestruction();
return;
}
GetMutableDebugValue().Set(
"manifest_url", opt_manifest ? opt_manifest->manifest_url.spec() : "");
GetMutableDebugValue().Set("manifest_installable_result",
base::ToString(installable_status));
if (installable_status != webapps::InstallableStatusCode::NO_ERROR_DETECTED) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kAppUpdateFailedDuringInstall);
return;
}
// TODO(crbug.com/438266139): Ignore name field in the manifest and still
// allow silent updates to happen.
if (!opt_manifest->has_valid_specified_start_url ||
opt_manifest->name->empty()) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kInvalidManifest);
return;
}
CHECK(opt_manifest);
CHECK(opt_manifest->id.is_valid());
app_id_ = GenerateAppIdFromManifestId(opt_manifest->id);
// ManifestSilentUpdateCommandStage::kFetchingNewManifestData
stage_ = ManifestSilentUpdateCommandStage::kFetchingNewManifestData;
app_lock_ = std::make_unique<AppLock>();
command_manager()->lock_manager().UpgradeAndAcquireLock(
std::move(lock_), *app_lock_, {app_id_},
base::BindOnce(
&ManifestSilentUpdateCommand::StartManifestToInstallInfoJob,
weak_factory_.GetWeakPtr(), std::move(opt_manifest)));
}
void ManifestSilentUpdateCommand::StartManifestToInstallInfoJob(
blink::mojom::ManifestPtr opt_manifest) {
CHECK_EQ(stage_, ManifestSilentUpdateCommandStage::kFetchingNewManifestData);
CHECK(app_lock_->IsGranted());
if (!app_lock_->registrar().IsInRegistrar(app_id_)) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kAppNotInstalled);
return;
}
// Compare trusted icons from the new incoming manifest with the one seen for
// the existing web app. The latter is guaranteed, but the former is not, in
// which case, prefer to update silently without updating icons, mimicking the
// `Cache-Control:Immutable` behavior.
new_manifest_trusted_icon_metadata_ =
GetTrustedIconsFromManifest(opt_manifest->icons);
if (new_manifest_trusted_icon_metadata_.has_value()) {
CHECK(new_manifest_trusted_icon_metadata_->square_size_px.has_value());
existing_manifest_trusted_icon_metadata_ =
app_lock_->registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id_,
new_manifest_trusted_icon_metadata_->square_size_px.value());
has_icon_url_changed_ =
new_manifest_trusted_icon_metadata_.has_value() &&
existing_manifest_trusted_icon_metadata_.has_value() &&
new_manifest_trusted_icon_metadata_->url !=
existing_manifest_trusted_icon_metadata_->url;
}
WebAppInstallInfoConstructOptions construct_options;
construct_options.fail_all_if_any_fail = true;
if (!has_icon_url_changed_) {
construct_options.skip_primary_icon_download = true;
}
// The `background_installation` and `install_source` fields here don't matter
// because this is not logged anywhere.
manifest_to_install_info_job_ =
ManifestToWebAppInstallInfoJob::CreateAndStart(
*opt_manifest, *data_retriever_.get(),
/*background_installation=*/false,
webapps::WebappInstallSource::MENU_BROWSER_TAB, web_contents_,
[](IconUrlSizeSet&) {}, GetMutableDebugValue(),
base::BindOnce(
&ManifestSilentUpdateCommand::OnWebAppInfoCreatedFromManifest,
GetWeakPtr()),
construct_options);
}
void ManifestSilentUpdateCommand::OnWebAppInfoCreatedFromManifest(
std::unique_ptr<WebAppInstallInfo> install_info) {
CHECK_EQ(stage_, ManifestSilentUpdateCommandStage::kFetchingNewManifestData);
CHECK(!new_install_info_);
if (IsWebContentsDestroyed()) {
AbortCommandOnWebContentsDestruction();
return;
}
new_install_info_ = std::move(install_info);
// Start validating scope extensions.
ScopeExtensions new_scope_extensions = new_install_info_->scope_extensions;
app_lock_->origin_association_manager().GetWebAppOriginAssociations(
new_install_info_->manifest_id(), std::move(new_scope_extensions),
base::BindOnce(&ManifestSilentUpdateCommand::
StashValidatedScopeExtensionsAndLoadExistingManifest,
GetWeakPtr()));
}
void ManifestSilentUpdateCommand::
StashValidatedScopeExtensionsAndLoadExistingManifest(
ScopeExtensions validated_scope_extensions) {
CHECK_EQ(stage_, ManifestSilentUpdateCommandStage::kFetchingNewManifestData);
if (IsWebContentsDestroyed()) {
AbortCommandOnWebContentsDestruction();
return;
}
new_install_info_->validated_scope_extensions =
std::make_optional(std::move(validated_scope_extensions));
// ManifestSilentUpdateCommandStage::kLoadingExistingManifestData
stage_ = ManifestSilentUpdateCommandStage::kLoadingExistingManifestData;
app_lock_->icon_manager().ReadAllIcons(
app_id_,
base::BindOnce(&ManifestSilentUpdateCommand::StashExistingAppIcons,
GetWeakPtr()));
}
void ManifestSilentUpdateCommand::StashExistingAppIcons(
WebAppIconManager::WebAppBitmaps icon_bitmaps) {
CHECK_EQ(stage_,
ManifestSilentUpdateCommandStage::kLoadingExistingManifestData);
if (icon_bitmaps.manifest_icons.empty()) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kIconReadFromDiskFailed);
return;
}
existing_trusted_icon_bitmaps_ = std::move(icon_bitmaps.trusted_icons);
existing_manifest_icon_bitmaps_ = std::move(icon_bitmaps.manifest_icons);
app_lock_->icon_manager().ReadAllShortcutsMenuIcons(
app_id_,
base::BindOnce(&ManifestSilentUpdateCommand::
StashExistingShortcutsMenuIconsFinalizeUpdateIfNeeded,
GetWeakPtr()));
}
void ManifestSilentUpdateCommand::
StashExistingShortcutsMenuIconsFinalizeUpdateIfNeeded(
ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps) {
CHECK_EQ(stage_,
ManifestSilentUpdateCommandStage::kLoadingExistingManifestData);
existing_shortcuts_menu_icon_bitmaps_ =
std::move(shortcuts_menu_icon_bitmaps);
// ManifestSilentUpdateCommandStage::
// kComparingManifestData
stage_ = ManifestSilentUpdateCommandStage::kComparingManifestData;
const WebApp* web_app = app_lock_->registrar().GetAppById(app_id_);
CHECK(new_install_info_);
silent_update_required_ = AreNonSecuritySensitiveDataChangesNeeded(
*web_app, &existing_shortcuts_menu_icon_bitmaps_, *new_install_info_);
GetMutableDebugValue().Set("silent_update_required",
base::ToString(silent_update_required_));
proto::PendingUpdateInfo pending_update_info;
std::u16string new_title;
base::TrimWhitespace(new_install_info_->title, base::TRIM_ALL, &new_title);
bool has_name_changed =
!new_title.empty() && new_install_info_->title !=
base::UTF8ToUTF16(web_app->untranslated_name());
// Changes to preinstalled or admin installed web apps are always silently
// applied since they are installed by trusted sources. There should be no
// pending update info saved for these web apps.
if (base::FeatureList::IsEnabled(
features::kSilentPolicyAndDefaultAppUpdating) &&
(web_app->IsPolicyInstalledApp() || web_app->IsPreinstalledApp())) {
if (!has_icon_url_changed_ && !has_name_changed &&
!silent_update_required_) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kAppUpToDate);
return;
}
new_install_info_->trusted_icons = new_install_info_->manifest_icons;
new_install_info_->trusted_icon_bitmaps = new_install_info_->icon_bitmaps;
app_lock_->install_finalizer().FinalizeUpdate(
*new_install_info_,
base::BindOnce(&ManifestSilentUpdateCommand::
UpdateFinalizedWritePendingInfoIfNeeded,
GetWeakPtr(),
std::optional<proto::PendingUpdateInfo>()));
return;
}
if (has_name_changed) {
pending_update_info.set_name(base::UTF16ToUTF8(new_install_info_->title));
new_install_info_->title = base::UTF8ToUTF16(web_app->untranslated_name());
}
if (!has_icon_url_changed_ && !has_name_changed && !silent_update_required_) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kAppUpToDate);
return;
}
// Something in the system needs to be updated after this line.
if (!has_icon_url_changed_) {
if (!silent_update_required_) {
// App name has changed.
UpdateFinalizedWritePendingInfoIfNeeded(
std::move(pending_update_info), app_id_,
webapps::InstallResultCode::kSuccessAlreadyInstalled);
return;
}
// Trusted icons are not downloaded because the url has not changed. Thus,
// for the update, populate trusted icons from database.
new_install_info_->manifest_icons = web_app->manifest_icons();
new_install_info_->trusted_icons = web_app->trusted_icons();
new_install_info_->trusted_icon_bitmaps = existing_trusted_icon_bitmaps_;
new_install_info_->icon_bitmaps = existing_manifest_icon_bitmaps_;
std::optional<proto::PendingUpdateInfo> opt_pending_update =
HasSecuritySensitiveChangesForPendingUpdate(pending_update_info)
? pending_update_info
: std::optional<proto::PendingUpdateInfo>();
app_lock_->install_finalizer().FinalizeUpdate(
*new_install_info_,
base::BindOnce(&ManifestSilentUpdateCommand::
UpdateFinalizedWritePendingInfoIfNeeded,
GetWeakPtr(), std::move(opt_pending_update)));
return;
}
// After this line, the icon urls have changed. Those icons are either stored
// in PendingUpdateInfo if there is amore than 10% diff or silently updated
// otherwise.
CHECK(new_manifest_trusted_icon_metadata_.has_value());
CHECK(new_manifest_trusted_icon_metadata_->square_size_px.has_value());
int icon_size_to_use = *new_manifest_trusted_icon_metadata_->square_size_px;
CHECK(!new_install_info_->trusted_icons.empty());
CHECK(!new_install_info_->trusted_icon_bitmaps.empty());
auto existing_trusted_icon_bitmaps_to_use =
existing_trusted_icon_bitmaps_.GetBitmapsForPurpose(
ConvertIconPurposeToManifestImagePurpose(
existing_manifest_trusted_icon_metadata_->purpose));
auto new_trusted_icon_bitmaps_to_use =
new_install_info_->trusted_icon_bitmaps.GetBitmapsForPurpose(
ConvertIconPurposeToManifestImagePurpose(
new_manifest_trusted_icon_metadata_->purpose));
auto existing_trusted_icon_it =
existing_trusted_icon_bitmaps_to_use.find(icon_size_to_use);
auto new_trusted_icon_it =
new_trusted_icon_bitmaps_to_use.find(icon_size_to_use);
CHECK(new_trusted_icon_it != new_trusted_icon_bitmaps_to_use.end());
bool has_existing_trusted_icon =
existing_trusted_icon_it != existing_trusted_icon_bitmaps_to_use.end();
// TODO(crbug.com/437379182): HasMoreThanTenPercentImageDiff() should happen
// in a different thread.
// Case: The icons are being set in the PendingUpdateInfo to be updated later.
if (!has_existing_trusted_icon ||
HasMoreThanTenPercentImageDiff(&(existing_trusted_icon_it->second),
&(new_trusted_icon_it->second))) {
// PendingUpdateInfo is used in the optional user update UX.
CopyIconsToPendingUpdateInfo(new_install_info_->trusted_icons,
pending_update_info.mutable_trusted_icons());
CopyIconsToPendingUpdateInfo(new_install_info_->manifest_icons,
pending_update_info.mutable_manifest_icons());
pending_trusted_icon_bitmaps_ = new_install_info_->trusted_icon_bitmaps;
pending_manifest_icon_bitmaps_ = new_install_info_->icon_bitmaps;
new_install_info_->manifest_icons = web_app->manifest_icons();
new_install_info_->trusted_icons = web_app->trusted_icons();
new_install_info_->icon_bitmaps = existing_manifest_icon_bitmaps_;
new_install_info_->trusted_icon_bitmaps = existing_trusted_icon_bitmaps_;
} else {
// Silent updates are allowed if the icons are less than 10% diff.
silent_update_required_ = true;
}
std::optional<proto::PendingUpdateInfo> opt_pending_update =
HasSecuritySensitiveChangesForPendingUpdate(pending_update_info)
? pending_update_info
: std::optional<proto::PendingUpdateInfo>();
if (silent_update_required_) {
app_lock_->install_finalizer().FinalizeUpdate(
*new_install_info_,
base::BindOnce(&ManifestSilentUpdateCommand::
UpdateFinalizedWritePendingInfoIfNeeded,
GetWeakPtr(), std::move(opt_pending_update)));
} else {
UpdateFinalizedWritePendingInfoIfNeeded(
std::move(opt_pending_update), app_id_,
webapps::InstallResultCode::kSuccessAlreadyInstalled);
}
}
// ManifestUpdateCheckStage::kFinalizingSilentManifestChanges
void ManifestSilentUpdateCommand::UpdateFinalizedWritePendingInfoIfNeeded(
std::optional<proto::PendingUpdateInfo> pending_update_info,
const webapps::AppId& app_id,
webapps::InstallResultCode code) {
CHECK_EQ(stage_, ManifestSilentUpdateCommandStage::kComparingManifestData);
stage_ = ManifestSilentUpdateCommandStage::kFinalizingSilentManifestChanges;
if (!IsSuccess(code)) {
GetMutableDebugValue().Set("installation_code", base::ToString(code));
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kAppUpdateFailedDuringInstall);
return;
}
CHECK_EQ(app_id_, app_id);
CHECK(new_install_info_);
const WebApp* existing_web_app = app_lock_->registrar().GetAppById(app_id_);
CHECK(existing_web_app);
// Ensure that non security sensitive data changes are no longer needed post
// application.
// `existing_shortcuts_menu_icon_bitmaps` has to be nullptr, otherwise this
// CHECK will fail. This is because `existing_shortcuts_menu_icon_bitmaps` is
// cached from before the manifest changes are applied, and once they are
// applied, the value of `existing_shortcuts_menu_icon_bitmaps` will need to
// be updated. It is expensive to read the icons by calling the
// `WebAppIconManager` again, so the simpler solution is to pass in `nullptr`
// to bypass this CHECK.
CHECK(!AreNonSecuritySensitiveDataChangesNeeded(
*existing_web_app, /*existing_shortcuts_menu_icon_bitmaps=*/nullptr,
*new_install_info_));
CHECK_EQ(code, webapps::InstallResultCode::kSuccessAlreadyInstalled);
CHECK(!pending_update_info.has_value() ||
HasSecuritySensitiveChangesForPendingUpdate(*pending_update_info));
if (!pending_update_info.has_value()) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kAppSilentlyUpdated);
return;
}
// Update the web app with non-security sensitive changes and store security
// sensitive changes to pending update info.
{
web_app::ScopedRegistryUpdate update =
app_lock_->sync_bridge().BeginUpdate();
web_app::WebApp* app_to_update = update->UpdateApp(app_id);
CHECK(app_to_update);
app_to_update->SetPendingUpdateInfo(std::move(pending_update_info));
}
// Write the pending trusted and pending manifest icon bitmaps to disk.
stage_ =
ManifestSilentUpdateCommandStage::kWritingPendingUpdateIconBitmapsToDisk;
app_lock_->icon_manager().WritePendingIconData(
app_id_, pending_trusted_icon_bitmaps_, pending_manifest_icon_bitmaps_,
base::BindOnce(&ManifestSilentUpdateCommand::
VerifyPendingUpdateIconBitmapsWrittenToDisk,
GetWeakPtr()));
}
// ManifestUpdateCheckStage::kWritingPendingUpdateIconBitmapsToDisk
void ManifestSilentUpdateCommand::VerifyPendingUpdateIconBitmapsWrittenToDisk(
bool bitmaps_write_success) {
CHECK_EQ(
stage_,
ManifestSilentUpdateCommandStage::kWritingPendingUpdateIconBitmapsToDisk);
if (!bitmaps_write_success) {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kPendingIconWriteToDiskFailed);
return;
}
CompleteCommandAndSelfDestruct(
silent_update_required_
? ManifestSilentUpdateCheckResult::
kAppHasNonSecurityAndSecurityChanges
: ManifestSilentUpdateCheckResult::kAppOnlyHasSecurityUpdate);
}
// ManifestSilentUpdateCommandStage::kCompleteCommand
void ManifestSilentUpdateCommand::CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult check_result) {
stage_ = ManifestSilentUpdateCommandStage::kCompleteCommand;
GetMutableDebugValue().Set("result", base::ToString(check_result));
CommandResult command_result = [&] {
switch (check_result) {
case ManifestSilentUpdateCheckResult::kAppSilentlyUpdated:
case ManifestSilentUpdateCheckResult::kAppUpToDate:
case ManifestSilentUpdateCheckResult::kAppOnlyHasSecurityUpdate:
case ManifestSilentUpdateCheckResult::
kAppHasNonSecurityAndSecurityChanges:
case ManifestSilentUpdateCheckResult::kAppNotInstalled:
case ManifestSilentUpdateCheckResult::kWebContentsDestroyed:
case ManifestSilentUpdateCheckResult::kIconReadFromDiskFailed:
case ManifestSilentUpdateCheckResult::kPendingIconWriteToDiskFailed:
case ManifestSilentUpdateCheckResult::kInvalidManifest:
return CommandResult::kSuccess;
case ManifestSilentUpdateCheckResult::kAppUpdateFailedDuringInstall:
case ManifestSilentUpdateCheckResult::kInvalidPendingUpdateInfo:
return CommandResult::kFailure;
case ManifestSilentUpdateCheckResult::kSystemShutdown:
NOTREACHED() << "This should be handled by OnShutdown()";
}
}();
Observe(nullptr);
CompleteAndSelfDestruct(command_result, check_result);
}
bool ManifestSilentUpdateCommand::IsWebContentsDestroyed() {
return !web_contents_ || web_contents_->IsBeingDestroyed();
}
void ManifestSilentUpdateCommand::AbortCommandOnWebContentsDestruction() {
CompleteCommandAndSelfDestruct(
ManifestSilentUpdateCheckResult::kWebContentsDestroyed);
}
} // namespace web_app