| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/web_applications/manifest_update_task.h" |
| |
| #include <map> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/web_applications/os_integration_manager.h" |
| #include "chrome/browser/web_applications/web_app.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_manager.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_installation_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/browser/web_applications/web_application_info.h" |
| #include "chrome/common/chrome_features.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 { |
| |
| void HaveIconContentsChanged( |
| const std::map<SquareSizePx, SkBitmap>& disk_icon_bitmaps, |
| const std::map<SquareSizePx, SkBitmap>& downloaded_icon_bitmaps, |
| IconDiff* icon_diff, |
| const std::vector<SquareSizePx>& on_disk_sizes, |
| const std::vector<SquareSizePx>& downloaded_sizes, |
| bool end_when_mismatch_detected) { |
| if (downloaded_icon_bitmaps.size() != disk_icon_bitmaps.size()) { |
| icon_diff->diff_results |= MISMATCHED_IMAGE_SIZES; |
| if (end_when_mismatch_detected) |
| return; |
| } |
| |
| if (on_disk_sizes != downloaded_sizes) { |
| icon_diff->diff_results |= MISMATCHED_IMAGE_SIZES; |
| if (end_when_mismatch_detected) |
| return; |
| } |
| |
| for (const std::pair<const SquareSizePx, SkBitmap>& entry : |
| downloaded_icon_bitmaps) { |
| SquareSizePx size = entry.first; |
| const SkBitmap& downloaded_bitmap = entry.second; |
| |
| auto it = disk_icon_bitmaps.find(size); |
| if (it == disk_icon_bitmaps.end()) { |
| icon_diff->diff_results |= MISMATCHED_IMAGE_SIZES; |
| if (end_when_mismatch_detected) |
| return; |
| continue; |
| } |
| |
| const SkBitmap& disk_bitmap = it->second; |
| if (!gfx::BitmapsAreEqual(downloaded_bitmap, disk_bitmap)) { |
| if (end_when_mismatch_detected) { |
| icon_diff->diff_results |= ONE_OR_MORE_ICONS_CHANGED; |
| return; |
| } else { |
| // Icons that are specified in new manifest are of special interest, the |
| // rest is auto-generated. |
| bool important_icon = |
| std::find(downloaded_sizes.begin(), downloaded_sizes.end(), size) != |
| downloaded_sizes.end(); |
| if (!important_icon) { |
| icon_diff->diff_results |= GENERATED_ICON_CHANGED; |
| } else if ((icon_diff->diff_results & SINGLE_ICON_CHANGED) == 0 && |
| (icon_diff->diff_results & MULTIPLE_ICONS_CHANGED) == 0) { |
| icon_diff->diff_results |= SINGLE_ICON_CHANGED; |
| icon_diff->before = disk_bitmap; |
| icon_diff->after = downloaded_bitmap; |
| } else if (icon_diff->diff_results & SINGLE_ICON_CHANGED) { |
| icon_diff->diff_results &= ~SINGLE_ICON_CHANGED; |
| icon_diff->diff_results |= MULTIPLE_ICONS_CHANGED; |
| // The UI can only handle showing one image at a time, at the moment. |
| icon_diff->before = SkBitmap(); |
| icon_diff->after = SkBitmap(); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| // Some apps, such as pre-installed apps, have been vetted and are therefore |
| // considered safe and permitted to update their names. |
| bool AllowUnpromptedNameUpdate(const AppId& app_id, |
| const WebAppRegistrar& registrar) { |
| const WebApp* web_app = registrar.GetAppById(app_id); |
| if (!web_app) |
| return false; |
| return CanWebAppUpdateIdentity(web_app); |
| } |
| |
| // 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); |
| } |
| |
| bool NeedsAppIdentityUpdateDialog(bool title_changing, |
| bool icons_changing, |
| const AppId& app_id, |
| const WebAppRegistrar& registrar) { |
| if (title_changing && !AllowUnpromptedNameUpdate(app_id, registrar)) |
| return true; |
| if (icons_changing && !AllowUnpromptedIconUpdate(app_id, registrar)) |
| return true; |
| return false; |
| } |
| |
| } // namespace |
| |
| IconDiff HaveIconBitmapsChanged( |
| const IconBitmaps& disk_icon_bitmaps, |
| const IconBitmaps& downloaded_icon_bitmaps, |
| const std::vector<apps::IconInfo>& disk_icon_info, |
| const std::vector<apps::IconInfo>& downloaded_icon_info, |
| bool end_when_mismatch_detected) { |
| // The manifest information associated with the icons is a flat vector of |
| // IconInfo types. This needs to be split into vectors and keyed by purpose |
| // (any, masked, monochrome) so that it can be read by the icon diff. |
| std::map<apps::IconInfo::Purpose, std::vector<SquareSizePx>> on_disk_sizes; |
| std::map<apps::IconInfo::Purpose, std::vector<SquareSizePx>> downloaded_sizes; |
| on_disk_sizes[apps::IconInfo::Purpose::kAny] = std::vector<SquareSizePx>(); |
| downloaded_sizes[apps::IconInfo::Purpose::kAny] = std::vector<SquareSizePx>(); |
| on_disk_sizes[apps::IconInfo::Purpose::kMaskable] = |
| std::vector<SquareSizePx>(); |
| downloaded_sizes[apps::IconInfo::Purpose::kMaskable] = |
| std::vector<SquareSizePx>(); |
| on_disk_sizes[apps::IconInfo::Purpose::kMonochrome] = |
| std::vector<SquareSizePx>(); |
| downloaded_sizes[apps::IconInfo::Purpose::kMonochrome] = |
| std::vector<SquareSizePx>(); |
| // Put each entry found into the right map (sort by purpose). |
| for (auto entry : disk_icon_info) { |
| on_disk_sizes[entry.purpose].push_back(entry.square_size_px.value_or(-1)); |
| } |
| for (auto entry : downloaded_icon_info) { |
| downloaded_sizes[entry.purpose].push_back( |
| entry.square_size_px.value_or(-1)); |
| } |
| |
| IconDiff icon_diff; |
| HaveIconContentsChanged(disk_icon_bitmaps.any, downloaded_icon_bitmaps.any, |
| &icon_diff, |
| on_disk_sizes[apps::IconInfo::Purpose::kAny], |
| downloaded_sizes[apps::IconInfo::Purpose::kAny], |
| end_when_mismatch_detected); |
| if (icon_diff.mismatch() && end_when_mismatch_detected) |
| return icon_diff; |
| |
| HaveIconContentsChanged(disk_icon_bitmaps.maskable, |
| downloaded_icon_bitmaps.maskable, &icon_diff, |
| on_disk_sizes[apps::IconInfo::Purpose::kMaskable], |
| downloaded_sizes[apps::IconInfo::Purpose::kMaskable], |
| end_when_mismatch_detected); |
| if (icon_diff.mismatch() && end_when_mismatch_detected) |
| return icon_diff; |
| |
| HaveIconContentsChanged( |
| disk_icon_bitmaps.monochrome, downloaded_icon_bitmaps.monochrome, |
| &icon_diff, on_disk_sizes[apps::IconInfo::Purpose::kMonochrome], |
| downloaded_sizes[apps::IconInfo::Purpose::kMonochrome], |
| end_when_mismatch_detected); |
| return icon_diff; |
| } |
| |
| ManifestUpdateTask::ManifestUpdateTask( |
| const GURL& url, |
| const AppId& app_id, |
| content::WebContents* web_contents, |
| StoppedCallback stopped_callback, |
| bool hang_for_testing, |
| const WebAppRegistrar& registrar, |
| const WebAppIconManager& icon_manager, |
| WebAppUiManager* ui_manager, |
| WebAppInstallManager* install_manager, |
| OsIntegrationManager& os_integration_manager, |
| WebAppSyncBridge* sync_bridge) |
| : content::WebContentsObserver(web_contents), |
| registrar_(registrar), |
| icon_manager_(icon_manager), |
| ui_manager_(*ui_manager), |
| install_manager_(*install_manager), |
| os_integration_manager_(os_integration_manager), |
| sync_bridge_(sync_bridge), |
| url_(url), |
| app_id_(app_id), |
| stopped_callback_(std::move(stopped_callback)), |
| hang_for_testing_(hang_for_testing) { |
| // Task starts by waiting for DidFinishLoad() to be called. |
| stage_ = Stage::kPendingPageLoad; |
| } |
| |
| ManifestUpdateTask::~ManifestUpdateTask() { |
| #if DCHECK_IS_ON() |
| if (destructor_called_ptr_) { |
| DCHECK(!(*destructor_called_ptr_)); |
| *destructor_called_ptr_ = true; |
| } |
| #endif |
| } |
| |
| // content::WebContentsObserver: |
| void ManifestUpdateTask::DidFinishLoad( |
| content::RenderFrameHost* render_frame_host, |
| const GURL& validated_url) { |
| if (stage_ != Stage::kPendingPageLoad || hang_for_testing_) |
| return; |
| |
| if (render_frame_host->GetParent() != nullptr) |
| return; |
| |
| stage_ = Stage::kPendingInstallableData; |
| webapps::InstallableParams params; |
| params.valid_primary_icon = true; |
| params.valid_manifest = true; |
| params.check_webapp_manifest_display = false; |
| webapps::InstallableManager::FromWebContents(web_contents()) |
| ->GetData(params, |
| base::BindOnce(&ManifestUpdateTask::OnDidGetInstallableData, |
| AsWeakPtr())); |
| } |
| |
| // content::WebContentsObserver: |
| void ManifestUpdateTask::WebContentsDestroyed() { |
| switch (stage_) { |
| case Stage::kPendingPageLoad: |
| case Stage::kPendingInstallableData: |
| case Stage::kPendingIconDownload: |
| case Stage::kPendingIconReadFromDisk: |
| case Stage::kPendingAppIdentityCheck: |
| DestroySelf(ManifestUpdateResult::kWebContentsDestroyed); |
| return; |
| case Stage::kPendingWindowsClosed: |
| case Stage::kPendingMaybeReadExistingIcons: |
| case Stage::kPendingInstallation: |
| case Stage::kPendingAssociationsUpdate: |
| // These stages should have stopped listening to the web contents. |
| NOTREACHED() << static_cast<int>(stage_); |
| Observe(nullptr); |
| break; |
| } |
| } |
| |
| void ManifestUpdateTask::OnDidGetInstallableData( |
| const webapps::InstallableData& data) { |
| DCHECK_EQ(stage_, Stage::kPendingInstallableData); |
| |
| if (!data.NoBlockingErrors()) { |
| DestroySelf(ManifestUpdateResult::kAppNotEligible); |
| return; |
| } |
| |
| web_application_info_.emplace(); |
| UpdateWebAppInfoFromManifest(data.manifest, data.manifest_url, |
| &web_application_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(web_application_info_->manifest_id, |
| web_application_info_->start_url)) { |
| DestroySelf(ManifestUpdateResult::kAppIdMismatch); |
| return; |
| } |
| |
| if (IsUpdateNeededForManifest()) { |
| UpdateAfterWindowsClose(); |
| return; |
| } |
| |
| LoadAndCheckIconContents(); |
| } |
| |
| bool ManifestUpdateTask::IsUpdateNeededForManifest() const { |
| DCHECK(web_application_info_.has_value()); |
| const WebApp* app = registrar_.GetAppById(app_id_); |
| DCHECK(app); |
| |
| bool title_changing = |
| web_application_info_->title != base::UTF8ToUTF16(app->name()); |
| bool icons_changing = |
| web_application_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 when kWebAppEnableManifestId is |
| // enabled. Both fields are allowed to change as long as the app_id generated |
| // from them doesn't change. |
| if (base::FeatureList::IsEnabled(blink::features::kWebAppEnableManifestId)) { |
| if (web_application_info_->manifest_id != app->manifest_id()) |
| return true; |
| if (web_application_info_->start_url != app->start_url()) |
| return true; |
| } |
| |
| if (web_application_info_->theme_color != app->theme_color()) |
| return true; |
| |
| if (web_application_info_->scope != app->scope()) |
| return true; |
| |
| if (web_application_info_->display_mode != app->display_mode()) |
| return true; |
| |
| if (web_application_info_->display_override != app->display_mode_override()) |
| return true; |
| |
| if (web_application_info_->shortcuts_menu_item_infos != |
| app->shortcuts_menu_item_infos()) { |
| return true; |
| } |
| |
| if (web_application_info_->share_target != app->share_target()) |
| return true; |
| |
| if (web_application_info_->protocol_handlers != app->protocol_handlers()) |
| return true; |
| |
| if (web_application_info_->url_handlers != app->url_handlers()) |
| return true; |
| |
| if (web_application_info_->note_taking_new_note_url != |
| app->note_taking_new_note_url()) { |
| return true; |
| } |
| |
| if (web_application_info_->capture_links != app->capture_links()) |
| return true; |
| |
| if (os_integration_manager_.IsFileHandlingAPIAvailable(app_id_) && |
| app->file_handlers() != web_application_info_->file_handlers) { |
| return true; |
| } |
| |
| if (web_application_info_->background_color != app->background_color()) |
| return true; |
| |
| if (web_application_info_->dark_mode_theme_color != |
| app->dark_mode_theme_color()) { |
| return true; |
| } |
| |
| if (web_application_info_->manifest_url != app->manifest_url()) |
| return true; |
| |
| if (web_application_info_->launch_handler != app->launch_handler()) |
| return true; |
| |
| // TODO(crbug.com/1212849): Handle changes to is_storage_isolated. |
| |
| // TODO(crbug.com/926083): Check more manifest fields. |
| return false; |
| } |
| |
| void ManifestUpdateTask::UpdateAfterWindowsClose() { |
| DCHECK(stage_ == Stage::kPendingInstallableData || |
| stage_ == Stage::kPendingAppIdentityCheck); |
| stage_ = Stage::kPendingWindowsClosed; |
| Observe(nullptr); |
| |
| ui_manager_.NotifyOnAllAppWindowsClosed( |
| app_id_, |
| base::BindOnce(&ManifestUpdateTask::OnAllAppWindowsClosed, AsWeakPtr())); |
| } |
| |
| void ManifestUpdateTask::LoadAndCheckIconContents() { |
| DCHECK_EQ(stage_, Stage::kPendingInstallableData); |
| stage_ = Stage::kPendingIconDownload; |
| |
| DCHECK(web_application_info_.has_value()); |
| std::vector<GURL> icon_urls = |
| GetValidIconUrlsToDownload(*web_application_info_); |
| icon_downloader_.emplace( |
| web_contents(), std::move(icon_urls), |
| WebAppIconDownloader::Histogram::kForUpdate, |
| 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) { |
| DCHECK_EQ(stage_, Stage::kPendingIconDownload); |
| |
| if (result != IconsDownloadedResult::kCompleted) { |
| DestroySelf(ManifestUpdateResult::kIconDownloadFailed); |
| return; |
| } |
| // TODO(crbug.com/1238622): Report `result` and `DownloadedIconsHttpResults`in |
| // UMA and internals. |
| |
| stage_ = Stage::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) { |
| DCHECK_EQ(stage_, Stage::kPendingIconReadFromDisk); |
| |
| if (disk_icon_bitmaps.empty()) { |
| DestroySelf(ManifestUpdateResult::kIconReadFromDiskFailed); |
| return; |
| } |
| DCHECK(web_application_info_.has_value()); |
| |
| stage_ = Stage::kPendingAppIdentityCheck; |
| |
| // These calls populate the |web_application_info_| with all icon bitmap |
| // data. |
| // If this data does not match what we already have on disk, then an update |
| // is necessary. |
| // TODO(https://crbug.com/1184911): Reuse this data in the web app install |
| // task. |
| PopulateOtherIcons(&web_application_info_.value(), downloaded_icons_map); |
| PopulateProductIcons(&web_application_info_.value(), &downloaded_icons_map); |
| |
| if (!base::FeatureList::IsEnabled(features::kPwaUpdateDialogForNameAndIcon)) { |
| OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped); |
| return; |
| } |
| |
| IconDiff icon_diff = IsUpdateNeededForIconContents(disk_icon_bitmaps); |
| std::u16string old_title = |
| base::UTF8ToUTF16(registrar_.GetAppShortName(app_id_)); |
| std::u16string new_title = web_application_info_->title; |
| |
| bool title_change = old_title != new_title; |
| bool icon_change = icon_diff.mismatch(); |
| |
| if (!title_change && !icon_change) { |
| OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped); |
| return; |
| } |
| |
| if (!NeedsAppIdentityUpdateDialog(title_change, icon_change, app_id_, |
| registrar_)) { |
| // The app identity update can be skipped, because any update not requiring |
| // the AppIdentityUpdate dialog should have been triggered already by |
| // running IsUpdateNeededForManifest. It doesn't matter a great deal whether |
| // kSkipped or kAllowed is used here, except that updating should also work |
| // without approval here. So to be safe we return kSkipped. |
| OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped); |
| return; |
| } |
| |
| if (icon_change && !icon_diff.supported_for_app_identity_check()) { |
| OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped); |
| return; |
| } |
| |
| // Note: If icon and name changes are to be actually used later and not |
| // overridden, then OnPostAppIdentityUpdateCheck must be called with |
| // |AppIdentityUpdate::kAllowed| so |app_identity_update_allowed_| is true. |
| SkBitmap* before_icon = nullptr; |
| SkBitmap* after_icon = nullptr; |
| if (icon_diff.mismatch()) { |
| before_icon = &icon_diff.before; |
| after_icon = &icon_diff.after; |
| } else { |
| auto it = disk_icon_bitmaps.any.find(web_app::kWebAppIconSmall); |
| if (it != disk_icon_bitmaps.any.end()) { |
| before_icon = &it->second; |
| after_icon = &it->second; |
| } |
| } |
| |
| if (before_icon == nullptr || after_icon == nullptr || |
| before_icon->drawsNothing() || after_icon->drawsNothing()) { |
| OnPostAppIdentityUpdateCheck(AppIdentityUpdate::kSkipped); |
| return; |
| } |
| |
| content::WebContents* web_contents = WebContentsObserver::web_contents(); |
| ui_manager_.ShowWebAppIdentityUpdateDialog( |
| app_id_, title_change, icon_diff.mismatch(), old_title, new_title, |
| *before_icon, *after_icon, web_contents, |
| 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) { |
| DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck); |
| Observe(nullptr); |
| app_identity_update_allowed_ = |
| app_identity_update_allowed == AppIdentityUpdate::kAllowed; |
| if (app_identity_update_allowed_) { |
| UpdateAfterWindowsClose(); |
| return; |
| } |
| |
| icon_manager_.ReadAllShortcutsMenuIcons( |
| app_id_, base::BindOnce(&ManifestUpdateTask::OnAllShortcutsMenuIconsRead, |
| AsWeakPtr())); |
| } |
| |
| IconDiff ManifestUpdateTask::IsUpdateNeededForIconContents( |
| const IconBitmaps& disk_icon_bitmaps) const { |
| DCHECK(web_application_info_.has_value()); |
| const WebApp* app = registrar_.GetAppById(app_id_); |
| DCHECK(app); |
| |
| return HaveIconBitmapsChanged( |
| disk_icon_bitmaps, web_application_info_->icon_bitmaps, |
| web_application_info_->manifest_icons, app->manifest_icons(), |
| /* end_when_mismatch_detected= */ false); |
| } |
| |
| void ManifestUpdateTask::OnAllShortcutsMenuIconsRead( |
| ShortcutsMenuIconBitmaps disk_shortcuts_menu_icon_bitmaps) { |
| DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck); |
| |
| DCHECK(web_application_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(web_application_info_.has_value()); |
| const ShortcutsMenuIconBitmaps& downloaded_shortcuts_menu_icon_bitmaps = |
| web_application_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, |
| web_application_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(web_application_info_.has_value()); |
| return !web_application_info_->url_handlers.empty(); |
| } |
| |
| void ManifestUpdateTask::NoManifestUpdateRequired() { |
| DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck); |
| stage_ = Stage::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_, Stage::kPendingAssociationsUpdate); |
| success ? DestroySelf(ManifestUpdateResult::kAppAssociationsUpdated) |
| : DestroySelf(ManifestUpdateResult::kAppAssociationsUpdateFailed); |
| } |
| |
| void ManifestUpdateTask::OnAllAppWindowsClosed() { |
| DCHECK_EQ(stage_, Stage::kPendingWindowsClosed); |
| |
| DCHECK(web_application_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). |
| // TODO(crbug.com/1088338): Provide a safe way for apps to update their |
| // name. |
| web_application_info_->title = |
| base::UTF8ToUTF16(registrar_.GetAppShortName(app_id_)); |
| } |
| |
| // Preserve the user's choice of form factor to open the app with. |
| web_application_info_->user_display_mode = |
| registrar_.GetAppUserDisplayMode(app_id_); |
| |
| stage_ = Stage::kPendingMaybeReadExistingIcons; |
| // If icon updating is disabled, then read the existing icons so they can be |
| // populated on the WebApplicationInfo. |
| if (AllowUnpromptedIconUpdate(app_id_, registrar_) || |
| app_identity_update_allowed_) { |
| OnExistingIconsRead(IconBitmaps()); |
| return; |
| } |
| icon_manager_.ReadAllIcons( |
| app_id_, |
| base::BindOnce(&ManifestUpdateTask::OnExistingIconsRead, AsWeakPtr())); |
| } |
| |
| void ManifestUpdateTask::OnExistingIconsRead(IconBitmaps icon_bitmaps) { |
| DCHECK_EQ(stage_, Stage::kPendingMaybeReadExistingIcons); |
| |
| bool redownload_app_icons = icon_bitmaps.empty(); |
| if (!redownload_app_icons) |
| web_application_info_->icon_bitmaps = std::move(icon_bitmaps); |
| |
| stage_ = Stage::kPendingInstallation; |
| install_manager_.UpdateWebAppFromInfo( |
| app_id_, std::make_unique<WebApplicationInfo>(*web_application_info_), |
| /*redownload_app_icons=*/redownload_app_icons, |
| base::BindOnce(&ManifestUpdateTask::OnInstallationComplete, AsWeakPtr())); |
| } |
| |
| void ManifestUpdateTask::OnInstallationComplete( |
| const AppId& app_id, |
| InstallResultCode code) { |
| DCHECK_EQ(stage_, Stage::kPendingInstallation); |
| |
| if (!IsSuccess(code)) { |
| DestroySelf(ManifestUpdateResult::kAppUpdateFailed); |
| return; |
| } |
| |
| DCHECK_EQ(app_id_, app_id); |
| DCHECK(!IsUpdateNeededForManifest()); |
| DCHECK_EQ(code, 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 |