blob: 56af9bbd7505a9e6d3d8cb3c7a770cfb15aac473 [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/manifest_update_task.h"
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/web_applications/commands/manifest_update_data_fetch_command.h"
#include "chrome/browser/web_applications/manifest_update_utils.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.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_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_finalizer.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_utils.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_ui_manager.h"
#include "chrome/common/chrome_features.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_manager.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "ui/gfx/skia_util.h"
namespace web_app {
namespace {
// This is used for metrics, so do not remove or reorder existing entries.
enum class AppIdentityDisplayMetric {
kNoAppIdentityChange = 0,
kIconChanging = 1,
// Values 2 and 3 are reserved for Android (icon mask).
kAppNameChanging = 4,
kAppNameAndIconChanging = 5,
// Values 6 through 15 (inclusive) are reserved for Android (icon mask/app
// short name).
kLastAndroidSpecificValue = 15,
// Add any new values above this one, and update kMaxValue to the highest
// enumerator value.
kMaxValue = 15
};
// Returns a shared instance of UpdatePendingCallback.
ManifestUpdateTask::UpdatePendingCallback* GetUpdatePendingCallbackMutable() {
static base::NoDestructor<ManifestUpdateTask::UpdatePendingCallback>
g_update_pending_callback;
return g_update_pending_callback.get();
}
// Some apps, such as pre-installed apps, have been vetted and are therefore
// considered safe and permitted to update their icon. For others, the feature
// flag needs to be on.
bool AllowUnpromptedIconUpdate(const AppId& app_id,
const WebAppRegistrar& registrar) {
const WebApp* web_app = registrar.GetAppById(app_id);
if (!web_app)
return false;
return CanWebAppUpdateIdentity(web_app) ||
base::FeatureList::IsEnabled(features::kWebAppManifestIconUpdating);
}
} // namespace
// static
void ManifestUpdateTask::SetUpdatePendingCallbackForTesting(
UpdatePendingCallback callback) {
*GetUpdatePendingCallbackMutable() = std::move(callback);
}
// static
bool& ManifestUpdateTask::BypassWindowCloseWaitingForTesting() {
static bool bypass_window_close_waiting_for_testing_ = false;
return bypass_window_close_waiting_for_testing_;
}
ManifestUpdateTask::ManifestUpdateTask(
const GURL& url,
const AppId& app_id,
base::WeakPtr<content::WebContents> web_contents,
StoppedCallback stopped_callback,
WebAppRegistrar& registrar,
WebAppIconManager& icon_manager,
WebAppUiManager* ui_manager,
WebAppInstallFinalizer* install_finalizer,
OsIntegrationManager& os_integration_manager,
WebAppSyncBridge* sync_bridge)
: web_contents_(web_contents),
registrar_(registrar),
icon_manager_(icon_manager),
ui_manager_(*ui_manager),
install_finalizer_(*install_finalizer),
os_integration_manager_(os_integration_manager),
sync_bridge_(sync_bridge),
url_(url),
app_id_(app_id),
stopped_callback_(std::move(stopped_callback)) {
Start();
}
ManifestUpdateTask::~ManifestUpdateTask() {
#if DCHECK_IS_ON()
if (destructor_called_ptr_) {
DCHECK(!(*destructor_called_ptr_));
*destructor_called_ptr_ = true;
}
#endif
}
void ManifestUpdateTask::Start() {
// We perform this check at the start as an early exit in case
// web_contents are destroyed before the task has started as well
// as ensuring that the web_contents_ are not destroyed when the
// InstallableManager is being invoked.
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
stage_ = ManifestUpdateStage::kPendingInstallableData;
webapps::InstallableParams params;
params.valid_primary_icon = true;
params.valid_manifest = true;
params.check_webapp_manifest_display = false;
webapps::InstallableManager::FromWebContents(web_contents_.get())
->GetData(params,
base::BindOnce(&ManifestUpdateTask::OnDidGetInstallableData,
AsWeakPtr()));
}
bool ManifestUpdateTask::IsWebContentsDestroyed() {
return !web_contents_ || web_contents_->IsBeingDestroyed();
}
void ManifestUpdateTask::OnDidGetInstallableData(
const webapps::InstallableData& data) {
// At this point, the ManifestUpdateTask is still collecting data, and this
// check ensures that the web_contents are still alive by the time we
// load icon contents.
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingInstallableData);
if (!data.NoBlockingErrors()) {
DestroySelf(ManifestUpdateResult::kAppNotEligible);
return;
}
CHECK(!web_contents_->IsBeingDestroyed());
install_info_.emplace();
UpdateWebAppInfoFromManifest(data.manifest, data.manifest_url,
&install_info_.value());
// We cannot allow the app ID to change via the manifest changing. We rely on
// fixed app IDs to determine whether web apps installed in the user sync
// profile has been sync installed across devices. If we allowed the app ID to
// change then the sync system would try to redeploy the old app indefinitely,
// additionally the new app ID would get added to the sync profile. This has
// the potential to flood the user sync profile with an infinite number of
// apps should the site be serving a random start_url on every navigation.
if (app_id_ !=
GenerateAppId(install_info_->manifest_id, install_info_->start_url)) {
DestroySelf(ManifestUpdateResult::kAppIdMismatch);
return;
}
LoadAndCheckIconContents();
}
bool ManifestUpdateTask::IsUpdateNeededForManifest() const {
DCHECK(install_info_.has_value());
const WebApp* app = registrar_.GetAppById(app_id_);
DCHECK(app);
// TODO(crbug.com/1259777): Check whether translations have been updated.
bool title_changing =
install_info_->title != base::UTF8ToUTF16(app->untranslated_name());
bool icons_changing = install_info_->manifest_icons != app->manifest_icons();
if (!NeedsAppIdentityUpdateDialog(title_changing, icons_changing, app_id_,
registrar_)) {
if (title_changing && AllowUnpromptedNameUpdate(app_id_, registrar_)) {
return true;
}
if (icons_changing && AllowUnpromptedIconUpdate(app_id_, registrar_)) {
return true;
}
}
// Allows updating start_url and manifest_id. Both fields are allowed to
// change as long as the app_id generated from them doesn't change.
{
if (install_info_->manifest_id != app->manifest_id())
return true;
if (install_info_->start_url != app->start_url())
return true;
}
if (install_info_->theme_color != app->theme_color())
return true;
if (install_info_->scope != app->scope())
return true;
if (install_info_->display_mode != app->display_mode())
return true;
if (install_info_->display_override != app->display_mode_override())
return true;
if (install_info_->shortcuts_menu_item_infos !=
app->shortcuts_menu_item_infos()) {
return true;
}
if (install_info_->share_target != app->share_target())
return true;
if (install_info_->protocol_handlers != app->protocol_handlers())
return true;
if (install_info_->url_handlers != app->url_handlers())
return true;
if (base::FeatureList::IsEnabled(
blink::features::kWebAppManifestLockScreen) &&
install_info_->lock_screen_start_url != app->lock_screen_start_url()) {
return true;
}
if (install_info_->note_taking_new_note_url !=
app->note_taking_new_note_url()) {
return true;
}
if (install_info_->capture_links != app->capture_links())
return true;
if (app->file_handlers() != install_info_->file_handlers)
return true;
if (install_info_->background_color != app->background_color())
return true;
if (install_info_->dark_mode_theme_color != app->dark_mode_theme_color()) {
return true;
}
if (install_info_->dark_mode_background_color !=
app->dark_mode_background_color()) {
return true;
}
if (install_info_->manifest_url != app->manifest_url())
return true;
if (install_info_->launch_handler != app->launch_handler())
return true;
if (install_info_->permissions_policy != app->permissions_policy())
return true;
// TODO(crbug.com/897314): Check changes to tab_strip field once icons are
// stored.
// TODO(crbug.com/1212849): Handle changes to is_storage_isolated.
// TODO(crbug.com/926083): Check more manifest fields.
return false;
}
void ManifestUpdateTask::UpdateAfterWindowsClose() {
// Ensure that the web_contents are still alive when the profile is being
// constructed from the browser context.
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK(stage_ == ManifestUpdateStage::kPendingInstallableData ||
stage_ == ManifestUpdateStage::kPendingAppIdentityCheck);
stage_ = ManifestUpdateStage::kAppWindowsClosed;
Profile* profile =
Profile::FromBrowserContext(web_contents_.get()->GetBrowserContext());
keep_alive_ = std::make_unique<ScopedKeepAlive>(
KeepAliveOrigin::APP_MANIFEST_UPDATE, KeepAliveRestartOption::DISABLED);
if (!profile->IsOffTheRecord()) {
// TODO(crbug.com/1369117): The DevTools Protocol's OTR profile can be
// destroyed at any time, which causes crashes...
profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
profile, ProfileKeepAliveOrigin::kWebAppUpdate);
}
if (BypassWindowCloseWaitingForTesting()) {
OnAllAppWindowsClosed();
} else {
ui_manager_.NotifyOnAllAppWindowsClosed(
app_id_, base::BindOnce(&ManifestUpdateTask::OnAllAppWindowsClosed,
AsWeakPtr()));
UpdatePendingCallback* callback = GetUpdatePendingCallbackMutable();
if (!callback->is_null())
std::move(*callback).Run(url_);
}
}
void ManifestUpdateTask::LoadAndCheckIconContents() {
// We need this check to ensure that the web_contents_ are still alive
// when the icon_downloader_ has started.
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingInstallableData);
stage_ = ManifestUpdateStage::kPendingIconDownload;
DCHECK(install_info_.has_value());
base::flat_set<GURL> icon_urls = GetValidIconUrlsToDownload(*install_info_);
icon_downloader_.emplace(
web_contents_.get(), std::move(icon_urls),
base::BindOnce(&ManifestUpdateTask::OnIconsDownloaded, AsWeakPtr()));
icon_downloader_->SkipPageFavicons();
icon_downloader_->FailAllIfAnyFail();
icon_downloader_->Start();
}
void ManifestUpdateTask::OnIconsDownloaded(
IconsDownloadedResult result,
IconsMap icons_map,
DownloadedIconsHttpResults icons_http_results) {
// At this point the ManifestUpdateTask is still at the kPendingIconDownload
// stage, so it is better to do a web_contents check before ending all icon
// reads.
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingIconDownload);
// TODO(crbug.com/1238622): Report `result` and `icons_http_results` in
// internals.
UMA_HISTOGRAM_ENUMERATION("WebApp.Icon.DownloadedResultOnUpdate", result);
RecordDownloadedIconHttpStatusCodes(
"WebApp.Icon.DownloadedHttpStatusCodeOnUpdate", icons_http_results);
if (result != IconsDownloadedResult::kCompleted) {
DestroySelf(ManifestUpdateResult::kIconDownloadFailed);
return;
}
RecordDownloadedIconsHttpResultsCodeClass(
"WebApp.Icon.HttpStatusCodeClassOnUpdate", result, icons_http_results);
stage_ = ManifestUpdateStage::kPendingIconReadFromDisk;
icon_manager_.ReadAllIcons(
app_id_, base::BindOnce(&ManifestUpdateTask::OnAllIconsRead, AsWeakPtr(),
std::move(icons_map)));
}
void ManifestUpdateTask::OnAllIconsRead(IconsMap downloaded_icons_map,
IconBitmaps disk_icon_bitmaps) {
// We still need to ensure that the web_contents_ are kept alive throughout
// the app identity check call to invoke the web_app identity update dialog
// in the end.
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingIconReadFromDisk);
if (disk_icon_bitmaps.empty()) {
DestroySelf(ManifestUpdateResult::kIconReadFromDiskFailed);
return;
}
DCHECK(install_info_.has_value());
stage_ = ManifestUpdateStage::kPendingAppIdentityCheck;
// These calls populate the |install_info_| with all icon bitmap
// data. If this data does not match what we already have on disk, then an
// update is necessary.
PopulateOtherIcons(&install_info_.value(), downloaded_icons_map);
PopulateProductIcons(&install_info_.value(), &downloaded_icons_map);
IconDiff icon_diff = IsUpdateNeededForIconContents(disk_icon_bitmaps);
std::u16string old_title =
base::UTF8ToUTF16(registrar_.GetAppShortName(app_id_));
std::u16string new_title = install_info_->title;
bool title_change = old_title != new_title;
bool icon_change = icon_diff.mismatch();
AppIdentityDisplayMetric app_id_changes =
AppIdentityDisplayMetric::kNoAppIdentityChange;
if (title_change && icon_change) {
app_id_changes = AppIdentityDisplayMetric::kAppNameAndIconChanging;
} else if (title_change || icon_change) {
app_id_changes = title_change ? AppIdentityDisplayMetric::kAppNameChanging
: AppIdentityDisplayMetric::kIconChanging;
}
// This catches the cases where the App Identity Dialog is not needed. That
// includes:
// - All Default-installed apps (since they are pre-approved for all updates).
// - Policy-installed apps w/kWebAppManifestPolicyAppIdentityUpdate exemption.
// - All icon changes when the kWebAppManifestIconUpdating override is set.
// - ... and apps that simply aren't requesting any app identity changes.
if (!NeedsAppIdentityUpdateDialog(title_change, icon_change, app_id_,
registrar_)) {
UMA_HISTOGRAM_ENUMERATION("Webapp.AppIdentityDialog.AlreadyApproved",
app_id_changes);
OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
return;
}
SkBitmap* before_icon = nullptr;
SkBitmap* after_icon = nullptr;
if (icon_change &&
base::FeatureList::IsEnabled(features::kPwaUpdateDialogForIcon)) {
before_icon = &icon_diff.before;
after_icon = &icon_diff.after;
} else {
auto it = disk_icon_bitmaps.any.find(kInstallIconSize);
if (it == disk_icon_bitmaps.any.end())
it = disk_icon_bitmaps.any.find(kLauncherIconSize);
if (it == disk_icon_bitmaps.any.end())
it = disk_icon_bitmaps.any.begin();
if (it != disk_icon_bitmaps.any.end()) {
before_icon = &it->second;
after_icon = &it->second;
}
}
// If there are any cases of Default-installed or Policy-installed apps that
// haven't been granted exceptions above (such as Policy apps without the
// special exemption), they should bail out now (with the icon set reset) so
// as to avoid showing the app identity dialog and allow other non-app
// identity changes to occur.
const WebApp* web_app = registrar_.GetAppById(app_id_);
if (web_app->IsPreinstalledApp() || web_app->IsPolicyInstalledApp()) {
UMA_HISTOGRAM_ENUMERATION("Webapp.AppIdentityDialog.NotShowing",
app_id_changes);
install_info_->icon_bitmaps = std::move(disk_icon_bitmaps);
install_info_->manifest_icons = web_app->manifest_icons();
install_info_->is_generated_icon = web_app->is_generated_icon();
OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
return;
}
// At this point we are only dealing with user-installed apps. Apps that don't
// ask for any identity updates are dealt with above, so this needs to handle
// updates to either the app title or icons.
if (icon_change &&
!base::FeatureList::IsEnabled(features::kPwaUpdateDialogForIcon)) {
// Icon changes are not supported, revert them and continue.
install_info_->icon_bitmaps = std::move(disk_icon_bitmaps);
install_info_->manifest_icons = web_app->manifest_icons();
install_info_->is_generated_icon = web_app->is_generated_icon();
icon_change = false;
}
if (title_change &&
!base::FeatureList::IsEnabled(features::kPwaUpdateDialogForName)) {
// Title changes are not supported, revert and continue.
install_info_->title = old_title;
new_title = old_title;
title_change = false;
}
// A title change requires showing the dialog, but unimportant icon changes
// are allowed to proceed.
if (!title_change && icon_change &&
!icon_diff.requires_app_identity_check()) {
UMA_HISTOGRAM_ENUMERATION("Webapp.AppIdentityDialog.AlreadyApproved",
app_id_changes);
OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kAllowed);
return;
}
if (!title_change && !icon_change) {
UMA_HISTOGRAM_ENUMERATION("Webapp.AppIdentityDialog.NotShowing",
app_id_changes);
OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
return;
}
if (before_icon == nullptr || after_icon == nullptr ||
before_icon->drawsNothing() || after_icon->drawsNothing()) {
UMA_HISTOGRAM_ENUMERATION("Webapp.AppIdentityDialog.NotShowing",
app_id_changes);
OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped);
return;
}
UMA_HISTOGRAM_ENUMERATION("Webapp.AppIdentityDialog.Showing", app_id_changes);
ui_manager_.ShowWebAppIdentityUpdateDialog(
app_id_, title_change, icon_change, old_title, new_title, *before_icon,
*after_icon, web_contents_.get(),
base::BindOnce(&ManifestUpdateTask::OnPostAppIdentityUpdateCheck,
AsWeakPtr()));
// Flow continues in OnPostAppIdentityUpdateCheck, once an action has been
// taken in the dialog.
}
void ManifestUpdateTask::OnPostAppIdentityUpdateCheck(
AppIdentityUpdate app_identity_update_allowed) {
// Perform an early exit if the web_contents are destroyed before calling
// UpdateAfterWindowsClose().
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAppIdentityCheck);
app_identity_update_allowed_ =
app_identity_update_allowed == AppIdentityUpdate::kAllowed;
if (app_identity_update_allowed_) {
UpdateAfterWindowsClose();
return;
}
if (IsUpdateNeededForManifest()) {
UpdateAfterWindowsClose();
return;
}
icon_manager_.ReadAllShortcutsMenuIcons(
app_id_, base::BindOnce(&ManifestUpdateTask::OnAllShortcutsMenuIconsRead,
AsWeakPtr()));
}
IconDiff ManifestUpdateTask::IsUpdateNeededForIconContents(
const IconBitmaps& disk_icon_bitmaps) const {
DCHECK(install_info_.has_value());
const WebApp* app = registrar_.GetAppById(app_id_);
DCHECK(app);
return HaveIconBitmapsChanged(disk_icon_bitmaps, install_info_->icon_bitmaps,
install_info_->manifest_icons,
app->manifest_icons(),
/* end_when_mismatch_detected= */ false);
}
void ManifestUpdateTask::OnAllShortcutsMenuIconsRead(
ShortcutsMenuIconBitmaps disk_shortcuts_menu_icon_bitmaps) {
// Perform an early exit if the web_contents are destroyed before calling
// UpdateAfterWindowsClose().
if (IsWebContentsDestroyed()) {
DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
return;
}
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAppIdentityCheck);
DCHECK(install_info_.has_value());
if (IsUpdateNeededForShortcutsMenuIconsContents(
disk_shortcuts_menu_icon_bitmaps)) {
UpdateAfterWindowsClose();
return;
}
NoManifestUpdateRequired();
}
bool ManifestUpdateTask::IsUpdateNeededForShortcutsMenuIconsContents(
const ShortcutsMenuIconBitmaps& disk_shortcuts_menu_icon_bitmaps) const {
DCHECK(install_info_.has_value());
const ShortcutsMenuIconBitmaps& downloaded_shortcuts_menu_icon_bitmaps =
install_info_->shortcuts_menu_icon_bitmaps;
if (downloaded_shortcuts_menu_icon_bitmaps.size() !=
disk_shortcuts_menu_icon_bitmaps.size()) {
return true;
}
const WebApp* app = registrar_.GetAppById(app_id_);
DCHECK(app);
for (size_t i = 0; i < downloaded_shortcuts_menu_icon_bitmaps.size(); ++i) {
const IconBitmaps& downloaded_icon_bitmaps =
downloaded_shortcuts_menu_icon_bitmaps[i];
const IconBitmaps& disk_icon_bitmaps = disk_shortcuts_menu_icon_bitmaps[i];
if (HaveIconBitmapsChanged(disk_icon_bitmaps, downloaded_icon_bitmaps,
install_info_->manifest_icons,
app->manifest_icons(),
/* end_when_mismatch_detected= */ true)
.mismatch())
return true;
}
return false;
}
bool ManifestUpdateTask::IsUpdateNeededForWebAppOriginAssociations() const {
// Web app origin association update is tied to the manifest update process.
// If there are url handlers for the current app, associations need to be
// revalidated.
DCHECK(install_info_.has_value());
return !install_info_->url_handlers.empty();
}
void ManifestUpdateTask::NoManifestUpdateRequired() {
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAppIdentityCheck);
stage_ = ManifestUpdateStage::kPendingAssociationsUpdate;
if (!IsUpdateNeededForWebAppOriginAssociations()) {
DestroySelf(ManifestUpdateResult::kAppUpToDate);
return;
}
os_integration_manager_.UpdateUrlHandlers(
app_id_,
base::BindOnce(&ManifestUpdateTask::OnWebAppOriginAssociationsUpdated,
AsWeakPtr()));
}
void ManifestUpdateTask::OnWebAppOriginAssociationsUpdated(bool success) {
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAssociationsUpdate);
success ? DestroySelf(ManifestUpdateResult::kAppAssociationsUpdated)
: DestroySelf(ManifestUpdateResult::kAppAssociationsUpdateFailed);
}
void ManifestUpdateTask::OnAllAppWindowsClosed() {
DCHECK_EQ(stage_, ManifestUpdateStage::kAppWindowsClosed);
DCHECK(install_info_.has_value());
if (!AllowUnpromptedNameUpdate(app_id_, registrar_) &&
!app_identity_update_allowed_) {
// The app's name must not change due to an automatic update, except for
// default installed apps (that have been vetted).
install_info_->title =
base::UTF8ToUTF16(registrar_.GetAppShortName(app_id_));
}
// Preserve the user's choice of form factor to open the app with.
install_info_->user_display_mode = registrar_.GetAppUserDisplayMode(app_id_);
stage_ = ManifestUpdateStage::kPendingFinalizerUpdate;
install_finalizer_.FinalizeUpdate(
*install_info_,
base::BindOnce(&ManifestUpdateTask::OnInstallationComplete, AsWeakPtr()));
}
void ManifestUpdateTask::OnInstallationComplete(const AppId& app_id,
webapps::InstallResultCode code,
OsHooksErrors os_hooks_errors) {
DCHECK_EQ(stage_, ManifestUpdateStage::kPendingFinalizerUpdate);
if (!IsSuccess(code)) {
DestroySelf(ManifestUpdateResult::kAppUpdateFailed);
return;
}
DCHECK_EQ(app_id_, app_id);
DCHECK(!IsUpdateNeededForManifest());
DCHECK_EQ(code, webapps::InstallResultCode::kSuccessAlreadyInstalled);
sync_bridge_->SetAppManifestUpdateTime(app_id, base::Time::Now());
DestroySelf(ManifestUpdateResult::kAppUpdated);
}
void ManifestUpdateTask::DestroySelf(ManifestUpdateResult result) {
// Asserts that calling the callback results in |this| getting deleted.
#if DCHECK_IS_ON()
bool destructor_called = false;
destructor_called_ptr_ = &destructor_called;
#endif
std::move(stopped_callback_).Run(*this, result);
#if DCHECK_IS_ON()
DCHECK(destructor_called);
#endif
}
} // namespace web_app