| // Copyright 2018 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/web_app_install_finalizer.h" |
| |
| #include <map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/ash/system_web_apps/types/system_web_app_data.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/isolation_prefs_utils.h" |
| #include "chrome/browser/web_applications/manifest_update_task.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/user_display_mode.h" |
| #include "chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.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_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_prefs_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_translation_manager.h" |
| #include "chrome/browser/web_applications/web_app_ui_manager.h" |
| #include "chrome/browser/web_applications/web_app_uninstall_job.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| |
| namespace web_app { |
| |
| 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; |
| |
| 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/1084939): Implement a before-start queue in |
| // WebAppInstallManager and replace this runtime error in |
| // WebAppInstallFinalizer with DCHECK(started_). |
| if (!started_) { |
| std::move(callback).Run(AppId(), |
| webapps::InstallResultCode::kWebAppProviderNotReady, |
| OsHooksErrors()); |
| return; |
| } |
| |
| // TODO(loyso): Expose Source argument as a field of AppTraits struct. |
| const WebAppManagement::Type source = options.source; |
| |
| AppId app_id = |
| GenerateAppId(web_app_info.manifest_id, web_app_info.start_url); |
| const WebApp* existing_web_app = GetWebAppRegistrar().GetAppById(app_id); |
| // A web app might be sync installed with id received from WebAppSpecifics |
| // that's different from start_url hash, in this case we look up the app by |
| // start_url and respect the app_id from the existing WebApp. |
| if (!base::FeatureList::IsEnabled(blink::features::kWebAppEnableManifestId) && |
| !existing_web_app) { |
| existing_web_app = |
| GetWebAppRegistrar().GetAppByStartUrl(web_app_info.start_url); |
| } |
| std::unique_ptr<WebApp> web_app; |
| if (existing_web_app) { |
| app_id = existing_web_app->app_id(); |
| // Prepare copy-on-write: |
| // Allows changing manifest_id and start_url when manifest_id is enabled. |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kWebAppEnableManifestId)) { |
| DCHECK_EQ(web_app_info.start_url, existing_web_app->start_url()); |
| } |
| web_app = std::make_unique<WebApp>(*existing_web_app); |
| |
| // The UI may initiate a full install to overwrite the existing |
| // non-locally-installed app. Therefore, |is_locally_installed| can be |
| // promoted to |true|, but not vice versa. |
| if (!web_app->is_locally_installed()) |
| web_app->SetIsLocallyInstalled(options.locally_installed); |
| |
| // There is a chance that existing sources type(s) are user uninstallable |
| // but the newly added source type is NOT user uninstallable. In this |
| // case, the following call will unregister os uninstallation. |
| // TODO(https://crbug.com/1273270): This does NOT block installation, and |
| // there is a possible edge case here where installation completes before |
| // this os hook is written. The best place to fix this is to put this code |
| // is where OS Hooks are called - however that is currently separate from |
| // this class. See https://crbug.com/1273269. |
| MaybeUnregisterOsUninstall(web_app.get(), source, *os_integration_manager_); |
| } else { |
| // New app. |
| web_app = std::make_unique<WebApp>(app_id); |
| web_app->SetStartUrl(web_app_info.start_url); |
| web_app->SetManifestId(web_app_info.manifest_id); |
| web_app->SetIsLocallyInstalled(options.locally_installed); |
| if (options.locally_installed) |
| web_app->SetInstallTime(base::Time::Now()); |
| web_app->SetRunOnOsLoginOsIntegrationState(RunOnOsLoginMode::kNotRun); |
| } |
| |
| // Set |user_display_mode| and any user-controllable fields here if this |
| // install is user initiated or it's a new app. |
| if (webapps::InstallableMetrics::IsUserInitiatedInstallSource( |
| options.install_surface) || |
| !existing_web_app) { |
| DCHECK(web_app_info.user_display_mode.has_value()); |
| web_app->SetUserDisplayMode(*web_app_info.user_display_mode); |
| } |
| |
| // `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 (policy_manager_->IsWebAppInDisabledList(app_id) && |
| web_app->chromeos_data().has_value() && |
| !web_app->chromeos_data()->is_disabled) { |
| absl::optional<WebAppChromeOsData> cros_data = web_app->chromeos_data(); |
| cros_data->is_disabled = true; |
| web_app->SetWebAppChromeOsData(std::move(cros_data)); |
| } |
| |
| // `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(); |
| } |
| |
| web_app->SetAdditionalSearchTerms(web_app_info.additional_search_terms); |
| web_app->AddSource(source); |
| web_app->SetIsFromSyncAndPendingInstallation(false); |
| web_app->SetParentAppId(options.parent_app_id); |
| web_app->SetInstallSourceForMetrics(options.install_surface); |
| |
| WriteExternalConfigMapInfo(*web_app, source, web_app_info.is_placeholder, |
| web_app_info.install_url); |
| |
| if (!options.locally_installed) { |
| DCHECK(!(options.add_to_applications_menu || options.add_to_desktop || |
| options.add_to_quick_launch_bar)) |
| << "Cannot create os hooks for a non-locally installed "; |
| } |
| |
| 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)); |
| } else { |
| // Updates the web app with an additional source. |
| CommitToSyncBridge(std::move(web_app), std::move(commit_callback), |
| /*success=*/true); |
| } |
| } |
| |
| void WebAppInstallFinalizer::UninstallExternalWebApp( |
| const AppId& app_id, |
| WebAppManagement::Type external_install_source, |
| webapps::WebappUninstallSource uninstall_source, |
| UninstallWebAppCallback callback) { |
| DCHECK(started_); |
| |
| DCHECK(external_install_source == WebAppManagement::Type::kSystem || |
| external_install_source == WebAppManagement::Type::kPolicy || |
| external_install_source == WebAppManagement::Type::kSubApp || |
| external_install_source == WebAppManagement::Type::kWebAppStore || |
| external_install_source == WebAppManagement::Type::kDefault); |
| |
| UninstallExternalWebAppOrRemoveSource(app_id, external_install_source, |
| uninstall_source, std::move(callback)); |
| } |
| |
| void WebAppInstallFinalizer::UninstallExternalWebAppByUrl( |
| const GURL& app_url, |
| WebAppManagement::Type external_install_source, |
| webapps::WebappUninstallSource uninstall_source, |
| UninstallWebAppCallback callback) { |
| absl::optional<AppId> app_id = |
| GetWebAppRegistrar().LookupExternalAppId(app_url); |
| if (!app_id.has_value()) { |
| LOG(WARNING) << "Couldn't uninstall web app with url " << app_url |
| << "; No corresponding web app for url."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), |
| webapps::UninstallResultCode::kNoAppToUninstall)); |
| return; |
| } |
| |
| UninstallExternalWebApp(app_id.value(), external_install_source, |
| uninstall_source, std::move(callback)); |
| } |
| |
| bool WebAppInstallFinalizer::CanUserUninstallWebApp(const AppId& app_id) const { |
| DCHECK(started_); |
| |
| // TODO(loyso): Policy Apps: Implement ManagementPolicy taking |
| // extensions::ManagementPolicy::UserMayModifySettings as inspiration. |
| const WebApp* app = GetWebAppRegistrar().GetAppById(app_id); |
| return app ? app->CanUserUninstallWebApp() : false; |
| } |
| |
| void WebAppInstallFinalizer::UninstallWebApp( |
| const AppId& app_id, |
| webapps::WebappUninstallSource webapp_uninstall_source, |
| UninstallWebAppCallback callback) { |
| DCHECK(started_); |
| |
| // Check that the source was from a known 'user' or allowed ones such |
| // as kMigration. |
| DCHECK( |
| webapp_uninstall_source == webapps::WebappUninstallSource::kUnknown || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kAppMenu || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kAppsPage || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kOsSettings || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kSync || |
| webapp_uninstall_source == |
| webapps::WebappUninstallSource::kAppManagement || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kMigration || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kAppList || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kShelf || |
| webapp_uninstall_source == webapps::WebappUninstallSource::kSubApp); |
| |
| const WebApp* app = GetWebAppRegistrar().GetAppById(app_id); |
| DCHECK(app); |
| DCHECK(app->CanUserUninstallWebApp()); |
| |
| if (app->IsPreinstalledApp()) { |
| // Update the default uninstalled web_app prefs if it is a preinstalled app |
| // but being removed by user. |
| UserUninstalledPreinstalledWebAppPrefs(profile_->GetPrefs()) |
| .Add(app_id, app->management_to_external_config_map() |
| .at(WebAppManagement::kDefault) |
| .install_urls); |
| } |
| |
| // UninstallWebApp can wipe out an app with multiple sources. This |
| // is the behavior from the old bookmark-app based system, which does not |
| // support incremental AddSource/RemoveSource. Here we are preserving that |
| // behavior for now. |
| // TODO(loyso): Implement different uninstall flows in UI. For example, we |
| // should separate UninstallWebAppFromSyncByUser from |
| // UninstallWebApp. |
| UninstallWebAppInternal(app_id, webapp_uninstall_source, std::move(callback)); |
| } |
| |
| void WebAppInstallFinalizer::UninstallFromSync( |
| const std::vector<AppId>& web_apps, |
| RepeatingUninstallCallback callback) { |
| DCHECK(started_); |
| |
| for (auto& app_id : web_apps) { |
| if (base::Contains(pending_uninstalls_, app_id)) { |
| continue; |
| } |
| auto uninstall_task = std::make_unique<WebAppUninstallJob>( |
| os_integration_manager_, sync_bridge_, icon_manager_, registrar_, |
| install_manager_, this, translation_manager_, profile_->GetPrefs()); |
| uninstall_task->Start( |
| app_id, |
| url::Origin::Create(registrar_->GetAppById(app_id)->start_url()), |
| webapps::WebappUninstallSource::kSync, |
| base::BindOnce(&WebAppInstallFinalizer::OnUninstallComplete, |
| weak_ptr_factory_.GetWeakPtr(), app_id, |
| webapps::WebappUninstallSource::kSync, |
| base::BindOnce(callback, app_id))); |
| pending_uninstalls_[app_id] = std::move(uninstall_task); |
| } |
| } |
| |
| void WebAppInstallFinalizer::RetryIncompleteUninstalls( |
| const base::flat_set<AppId>& apps_to_uninstall) { |
| for (const AppId& app_id : apps_to_uninstall) { |
| if (base::Contains(pending_uninstalls_, app_id)) |
| continue; |
| auto uninstall_task = std::make_unique<WebAppUninstallJob>( |
| os_integration_manager_, sync_bridge_, icon_manager_, registrar_, |
| install_manager_, this, translation_manager_, profile_->GetPrefs()); |
| const WebApp* web_app = registrar_->GetAppById(app_id); |
| if (!web_app) |
| continue; |
| uninstall_task->Start( |
| app_id, url::Origin::Create(web_app->start_url()), |
| webapps::WebappUninstallSource::kStartupCleanup, |
| base::BindOnce(&WebAppInstallFinalizer::OnUninstallComplete, |
| weak_ptr_factory_.GetWeakPtr(), app_id, |
| webapps::WebappUninstallSource::kStartupCleanup, |
| base::DoNothing())); |
| pending_uninstalls_[app_id] = std::move(uninstall_task); |
| } |
| } |
| |
| bool WebAppInstallFinalizer::CanReparentTab(const AppId& app_id, |
| bool shortcut_created) const { |
| // Reparent the web contents into its own window only if that is the |
| // app's launch type. |
| DCHECK(registrar_); |
| if (registrar_->GetAppUserDisplayMode(app_id) == UserDisplayMode::kBrowser) |
| return false; |
| |
| return ui_manager_->CanReparentAppTabToWindow(app_id, shortcut_created); |
| } |
| |
| void WebAppInstallFinalizer::ReparentTab(const AppId& app_id, |
| bool shortcut_created, |
| content::WebContents* web_contents) { |
| DCHECK(web_contents); |
| return ui_manager_->ReparentAppTabToWindow(web_contents, app_id, |
| shortcut_created); |
| } |
| |
| void WebAppInstallFinalizer::FinalizeUpdate( |
| const WebAppInstallInfo& web_app_info, |
| InstallFinalizedCallback callback) { |
| CHECK(started_); |
| |
| const AppId app_id = |
| GenerateAppId(web_app_info.manifest_id, web_app_info.start_url); |
| const WebApp* existing_web_app = GetWebAppRegistrar().GetAppById(app_id); |
| |
| if (!existing_web_app || |
| existing_web_app->is_from_sync_and_pending_installation() || |
| app_id != existing_web_app->app_id()) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), AppId(), |
| webapps::InstallResultCode::kWebAppDisabled, |
| OsHooksErrors())); |
| return; |
| } |
| |
| CommitCallback commit_callback = base::BindOnce( |
| &WebAppInstallFinalizer::OnDatabaseCommitCompletedForUpdate, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), app_id, |
| GetWebAppRegistrar().GetAppShortName(app_id), |
| GetFileHandlerUpdateAction(app_id, web_app_info), web_app_info); |
| |
| // Prepare copy-on-write to update existing app. |
| SetWebAppManifestFieldsAndWriteData( |
| web_app_info, std::make_unique<WebApp>(*existing_web_app), |
| std::move(commit_callback)); |
| } |
| |
| void WebAppInstallFinalizer::Start() { |
| DCHECK(!started_); |
| started_ = true; |
| } |
| |
| void WebAppInstallFinalizer::Shutdown() { |
| pending_uninstalls_.clear(); |
| started_ = false; |
| } |
| |
| void WebAppInstallFinalizer::SetRemoveSourceCallbackForTesting( |
| base::RepeatingCallback<void(const AppId&)> callback) { |
| install_source_removed_callback_for_testing_ = std::move(callback); |
| } |
| |
| void WebAppInstallFinalizer::SetSubsystems( |
| WebAppInstallManager* install_manager, |
| WebAppRegistrar* registrar, |
| WebAppUiManager* ui_manager, |
| WebAppSyncBridge* sync_bridge, |
| OsIntegrationManager* os_integration_manager, |
| WebAppIconManager* icon_manager, |
| WebAppPolicyManager* policy_manager, |
| WebAppTranslationManager* translation_manager) { |
| install_manager_ = install_manager; |
| registrar_ = registrar; |
| ui_manager_ = ui_manager; |
| sync_bridge_ = sync_bridge; |
| os_integration_manager_ = os_integration_manager; |
| icon_manager_ = icon_manager; |
| policy_manager_ = policy_manager; |
| translation_manager_ = translation_manager; |
| } |
| |
| std::vector<AppId> WebAppInstallFinalizer::GetPendingUninstallsForTesting() |
| const { |
| std::vector<AppId> ids; |
| for (const auto& [key, _] : pending_uninstalls_) { |
| ids.push_back(key); |
| } |
| return ids; |
| } |
| |
| void WebAppInstallFinalizer::UninstallWebAppInternal( |
| const AppId& app_id, |
| webapps::WebappUninstallSource uninstall_source, |
| UninstallWebAppCallback callback) { |
| if (registrar_->GetAppById(app_id) == nullptr || |
| base::Contains(pending_uninstalls_, app_id)) { |
| std::move(callback).Run(webapps::UninstallResultCode::kNoAppToUninstall); |
| return; |
| } |
| auto uninstall_task = std::make_unique<WebAppUninstallJob>( |
| os_integration_manager_, sync_bridge_, icon_manager_, registrar_, |
| install_manager_, this, translation_manager_, profile_->GetPrefs()); |
| uninstall_task->Start( |
| app_id, url::Origin::Create(registrar_->GetAppById(app_id)->start_url()), |
| uninstall_source, |
| base::BindOnce(&WebAppInstallFinalizer::OnUninstallComplete, |
| weak_ptr_factory_.GetWeakPtr(), app_id, uninstall_source, |
| std::move(callback))); |
| pending_uninstalls_[app_id] = std::move(uninstall_task); |
| } |
| |
| void WebAppInstallFinalizer::OnUninstallComplete( |
| AppId app_id, |
| webapps::WebappUninstallSource source, |
| UninstallWebAppCallback callback, |
| webapps::UninstallResultCode code) { |
| DCHECK(base::Contains(pending_uninstalls_, app_id)); |
| pending_uninstalls_.erase(app_id); |
| if (source == webapps::WebappUninstallSource::kSync) { |
| base::UmaHistogramBoolean("Webapp.SyncInitiatedUninstallResult", |
| code == webapps::UninstallResultCode::kSuccess); |
| } |
| std::move(callback).Run(code); |
| } |
| |
| void WebAppInstallFinalizer::UninstallExternalWebAppOrRemoveSource( |
| const AppId& app_id, |
| WebAppManagement::Type install_source, |
| webapps::WebappUninstallSource uninstall_source, |
| UninstallWebAppCallback callback) { |
| const WebApp* app = GetWebAppRegistrar().GetAppById(app_id); |
| if (!app) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), |
| webapps::UninstallResultCode::kNoAppToUninstall)); |
| return; |
| } |
| |
| if (app->HasOnlySource(install_source)) { |
| UninstallWebAppInternal(app_id, uninstall_source, std::move(callback)); |
| } else { |
| // There is a chance that removed source type is NOT user uninstallable |
| // but the remaining source (after removal) types are user uninstallable. |
| // In this case, the following call will register os uninstallation. |
| MaybeRegisterOsUninstall( |
| app, install_source, *os_integration_manager_, |
| base::BindOnce(&WebAppInstallFinalizer::OnMaybeRegisterOsUninstall, |
| weak_ptr_factory_.GetWeakPtr(), app_id, install_source, |
| std::move(callback))); |
| } |
| } |
| |
| void WebAppInstallFinalizer::OnMaybeRegisterOsUninstall( |
| const AppId& app_id, |
| WebAppManagement::Type source, |
| UninstallWebAppCallback callback, |
| OsHooksErrors os_hooks_errors) { |
| ScopedRegistryUpdate update(sync_bridge_); |
| WebApp* app_to_update = update->UpdateApp(app_id); |
| app_to_update->RemoveSource(source); |
| if (source == WebAppManagement::kSubApp) { |
| app_to_update->SetParentAppId(absl::nullopt); |
| } |
| if (install_source_removed_callback_for_testing_) |
| install_source_removed_callback_for_testing_.Run(app_id); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), |
| webapps::UninstallResultCode::kSuccess)); |
| } |
| |
| void WebAppInstallFinalizer::SetWebAppManifestFieldsAndWriteData( |
| const WebAppInstallInfo& web_app_info, |
| std::unique_ptr<WebApp> web_app, |
| CommitCallback commit_callback) { |
| SetWebAppManifestFields(web_app_info, *web_app); |
| |
| AppId app_id = web_app->app_id(); |
| 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; |
| |
| auto write_icons_callback = base::BindOnce( |
| &WebAppIconManager::WriteData, icon_manager_, app_id, |
| std::move(icon_bitmaps), std::move(shortcuts_menu_icon_bitmaps), |
| std::move(other_icon_bitmaps)); |
| auto write_translations_callback = base::BindOnce( |
| &WebAppInstallFinalizer::WriteTranslations, |
| weak_ptr_factory_.GetWeakPtr(), app_id, std::move(web_app_info)); |
| auto commit_to_sync_bridge_callback = |
| base::BindOnce(&WebAppInstallFinalizer::CommitToSyncBridge, |
| weak_ptr_factory_.GetWeakPtr(), std::move(web_app)); |
| |
| std::move(write_icons_callback) |
| .Run(base::BindOnce( |
| std::move(write_translations_callback), |
| base::BindOnce(std::move(commit_to_sync_bridge_callback), |
| std::move(commit_callback)))); |
| } |
| |
| void WebAppInstallFinalizer::WriteTranslations( |
| const AppId& app_id, |
| const WebAppInstallInfo& web_app_info, |
| CommitCallback commit_callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!success) { |
| std::move(commit_callback).Run(success); |
| return; |
| } |
| |
| translation_manager_->WriteTranslations(app_id, web_app_info.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; |
| } |
| |
| // Save the isolation state to prefs. On browser startup we may need access |
| // to the isolation state before WebAppDatabase has finished loading, so we |
| // duplicate this state in a pref to prevent blocking startup. |
| RecordOrRemoveAppIsolationState(profile_->GetPrefs(), *web_app); |
| |
| AppId app_id = web_app->app_id(); |
| |
| std::unique_ptr<WebAppRegistryUpdate> update = sync_bridge_->BeginUpdate(); |
| |
| 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)); |
| |
| sync_bridge_->CommitUpdate(std::move(update), std::move(commit_callback)); |
| } |
| |
| void WebAppInstallFinalizer::OnDatabaseCommitCompletedForInstall( |
| InstallFinalizedCallback callback, |
| AppId app_id, |
| FinalizeOptions finalize_options, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!success) { |
| std::move(callback).Run( |
| AppId(), webapps::InstallResultCode::kWriteDataFailed, OsHooksErrors()); |
| return; |
| } |
| |
| install_manager_->NotifyWebAppInstalled(app_id); |
| |
| const WebApp* web_app = GetWebAppRegistrar().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( |
| AppId(), webapps::InstallResultCode::kAppNotInRegistrarAfterCommit, |
| OsHooksErrors()); |
| return; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) // Deeper OS integration is expected on ChromeOS. |
| const bool should_install_os_hooks = !finalize_options.bypass_os_hooks; |
| #else |
| const bool should_install_os_hooks = |
| !finalize_options.bypass_os_hooks && |
| !web_app->HasOnlySource(WebAppManagement::Type::kDefault) && |
| finalize_options.locally_installed; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| if (!should_install_os_hooks) { |
| std::move(callback).Run(app_id, |
| webapps::InstallResultCode::kSuccessNewInstall, |
| OsHooksErrors()); |
| return; |
| } |
| |
| InstallOsHooksOptions hooks_options; |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \ |
| (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS)) |
| hooks_options.os_hooks[OsHookType::kUrlHandlers] = true; |
| #else |
| hooks_options.os_hooks[OsHookType::kUrlHandlers] = false; |
| #endif |
| |
| hooks_options.os_hooks[OsHookType::kShortcuts] = |
| finalize_options.add_to_applications_menu; |
| hooks_options.os_hooks[OsHookType::kShortcutsMenu] = |
| finalize_options.add_to_applications_menu; |
| |
| { |
| web_app::RunOnOsLoginMode current_mode = |
| registrar_->GetAppRunOnOsLoginMode(app_id).value; |
| hooks_options.os_hooks[OsHookType::kRunOnOsLogin] = |
| current_mode == RunOnOsLoginMode::kWindowed; |
| } |
| |
| hooks_options.add_to_quick_launch_bar = |
| finalize_options.add_to_quick_launch_bar; |
| hooks_options.add_to_desktop = finalize_options.add_to_desktop; |
| |
| // Apps that can't be uninstalled from users shouldn't register to |
| // OS Settings. |
| hooks_options.os_hooks[OsHookType::kUninstallationViaOsSettings] = |
| web_app->CanUserUninstallWebApp(); |
| |
| hooks_options.os_hooks[OsHookType::kFileHandlers] = true; |
| hooks_options.os_hooks[OsHookType::kProtocolHandlers] = true; |
| |
| os_integration_manager_->InstallOsHooks( |
| app_id, |
| base::BindOnce(&WebAppInstallFinalizer::OnInstallHooksFinished, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| app_id), |
| /*web_app_info=*/nullptr, hooks_options); |
| } |
| |
| void WebAppInstallFinalizer::OnInstallHooksFinished( |
| InstallFinalizedCallback callback, |
| AppId app_id, |
| web_app::OsHooksErrors os_hooks_errors) { |
| auto joined = std::move(callback).Then( |
| base::BindOnce(&WebAppInstallFinalizer::NotifyWebAppInstalledWithOsHooks, |
| weak_ptr_factory_.GetWeakPtr(), app_id)); |
| |
| std::move(joined).Run(app_id, webapps::InstallResultCode::kSuccessNewInstall, |
| os_hooks_errors); |
| } |
| |
| void WebAppInstallFinalizer::NotifyWebAppInstalledWithOsHooks(AppId app_id) { |
| install_manager_->NotifyWebAppInstalledWithOsHooks(app_id); |
| } |
| |
| bool WebAppInstallFinalizer::ShouldUpdateOsHooks(const AppId& app_id) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // OS integration should always be enabled on ChromeOS. |
| return true; |
| #else |
| // If the app being updated was installed by default and not also manually |
| // installed by the user or an enterprise policy, disable os integration. |
| return !GetWebAppRegistrar().WasInstalledByDefaultOnly(app_id); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| void WebAppInstallFinalizer::OnDatabaseCommitCompletedForUpdate( |
| InstallFinalizedCallback callback, |
| 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( |
| AppId(), webapps::InstallResultCode::kWriteDataFailed, OsHooksErrors()); |
| return; |
| } |
| |
| if (ShouldUpdateOsHooks(app_id)) { |
| os_integration_manager_->UpdateOsHooks( |
| app_id, old_name, file_handlers_need_os_update, web_app_info, |
| base::BindOnce(&WebAppInstallFinalizer::OnUpdateHooksFinished, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| app_id, old_name)); |
| } else { |
| std::move(callback).Run( |
| app_id, webapps::InstallResultCode::kSuccessAlreadyInstalled, |
| OsHooksErrors()); |
| } |
| } |
| |
| void WebAppInstallFinalizer::OnUpdateHooksFinished( |
| InstallFinalizedCallback callback, |
| AppId app_id, |
| std::string old_name, |
| web_app::OsHooksErrors os_hooks_errors) { |
| install_manager_->NotifyWebAppManifestUpdated(app_id, old_name); |
| |
| std::move(callback).Run( |
| app_id, |
| os_hooks_errors.any() |
| ? webapps::InstallResultCode::kUpdateTaskFailed |
| : webapps::InstallResultCode::kSuccessAlreadyInstalled, |
| os_hooks_errors); |
| } |
| |
| const WebAppRegistrar& WebAppInstallFinalizer::GetWebAppRegistrar() const { |
| return *registrar_; |
| } |
| |
| void WebAppInstallFinalizer::WriteExternalConfigMapInfo( |
| WebApp& web_app, |
| WebAppManagement::Type source, |
| bool is_placeholder, |
| GURL install_url) { |
| DCHECK(!(source == WebAppManagement::Type::kSync && is_placeholder)); |
| if (source != WebAppManagement::Type::kSync) { |
| web_app.AddPlaceholderInfoToManagementExternalConfigMap(source, |
| is_placeholder); |
| if (install_url.is_valid()) { |
| web_app.AddInstallURLToManagementExternalConfigMap(source, install_url); |
| } |
| } |
| } |
| |
| FileHandlerUpdateAction WebAppInstallFinalizer::GetFileHandlerUpdateAction( |
| const AppId& app_id, |
| const WebAppInstallInfo& new_web_app_info) { |
| if (!os_integration_manager_->IsFileHandlingAPIAvailable(app_id)) |
| return FileHandlerUpdateAction::kNoUpdate; |
| |
| if (GetWebAppRegistrar().GetAppFileHandlerApprovalState(app_id) == |
| ApiApprovalState::kDisallowed) { |
| return FileHandlerUpdateAction::kNoUpdate; |
| } |
| |
| // TODO(https://crbug.com/1197013): Consider trying to re-use the comparison |
| // results from the ManifestUpdateTask. |
| const apps::FileHandlers* old_handlers = |
| GetWebAppRegistrar().GetAppFileHandlers(app_id); |
| DCHECK(old_handlers); |
| if (*old_handlers == new_web_app_info.file_handlers) |
| return FileHandlerUpdateAction::kNoUpdate; |
| |
| return FileHandlerUpdateAction::kUpdate; |
| } |
| |
| } // namespace web_app |