| // 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/extensions/bookmark_app_install_finalizer.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/optional.h" |
| #include "chrome/browser/extensions/crx_installer.h" |
| #include "chrome/browser/extensions/launch_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/components/app_registry_controller.h" |
| #include "chrome/browser/web_applications/components/os_integration_manager.h" |
| #include "chrome/browser/web_applications/components/web_app_constants.h" |
| #include "chrome/browser/web_applications/components/web_app_helpers.h" |
| #include "chrome/browser/web_applications/components/web_app_prefs_utils.h" |
| #include "chrome/browser/web_applications/components/web_app_provider_base.h" |
| #include "chrome/browser/web_applications/components/web_app_utils.h" |
| #include "chrome/browser/web_applications/components/web_application_info.h" |
| #include "chrome/browser/web_applications/extensions/bookmark_app_finalizer_utils.h" |
| #include "chrome/browser/web_applications/extensions/bookmark_app_registrar.h" |
| #include "chrome/browser/web_applications/extensions/bookmark_app_util.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "extensions/browser/disable_reason.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/uninstall_reason.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_id.h" |
| #include "extensions/common/extension_set.h" |
| #include "url/gurl.h" |
| |
| using extensions::mojom::ManifestLocation; |
| |
| namespace extensions { |
| |
| BookmarkAppInstallFinalizer::BookmarkAppInstallFinalizer(Profile* profile) |
| : externally_installed_app_prefs_(profile->GetPrefs()), profile_(profile) { |
| crx_installer_factory_ = base::BindRepeating([](Profile* profile) { |
| ExtensionService* extension_service = |
| ExtensionSystem::Get(profile)->extension_service(); |
| DCHECK(extension_service); |
| return CrxInstaller::CreateSilent(extension_service); |
| }); |
| } |
| |
| BookmarkAppInstallFinalizer::~BookmarkAppInstallFinalizer() = default; |
| |
| void BookmarkAppInstallFinalizer::FinalizeInstall( |
| const WebApplicationInfo& web_app_info, |
| const FinalizeOptions& options, |
| InstallFinalizedCallback callback) { |
| scoped_refptr<CrxInstaller> crx_installer = |
| crx_installer_factory_.Run(profile_); |
| |
| extensions::LaunchType launch_type = |
| web_app_info.open_as_window ? LAUNCH_TYPE_WINDOW : LAUNCH_TYPE_REGULAR; |
| |
| crx_installer->set_installer_callback(base::BindOnce( |
| &BookmarkAppInstallFinalizer::OnExtensionInstalled, |
| weak_ptr_factory_.GetWeakPtr(), web_app_info.start_url, launch_type, |
| web_app_info.enable_experimental_tabbed_window, options.locally_installed, |
| std::move(callback), crx_installer)); |
| |
| switch (options.install_source) { |
| // TODO(nigeltao/ortuno): should these two cases lead to different |
| // mojom::ManifestLocation values: kInternal vs kExternalPrefDownload? |
| case webapps::WebappInstallSource::INTERNAL_DEFAULT: |
| case webapps::WebappInstallSource::EXTERNAL_DEFAULT: |
| crx_installer->set_install_source( |
| ManifestLocation::kExternalPrefDownload); |
| // CrxInstaller::InstallWebApp will OR the creation flags with |
| // FROM_BOOKMARK. |
| crx_installer->set_creation_flags(Extension::WAS_INSTALLED_BY_DEFAULT); |
| break; |
| case webapps::WebappInstallSource::EXTERNAL_POLICY: |
| crx_installer->set_install_source( |
| ManifestLocation::kExternalPolicyDownload); |
| break; |
| case webapps::WebappInstallSource::SYSTEM_DEFAULT: |
| // System Apps are considered EXTERNAL_COMPONENT as they are downloaded |
| // from the WebUI they point to. COMPONENT seems like the more correct |
| // value, but usages (icon loading, filesystem cleanup), are tightly |
| // coupled to this value, making it unsuitable. |
| crx_installer->set_install_source(ManifestLocation::kExternalComponent); |
| // InstallWebApp will OR the creation flags with FROM_BOOKMARK. |
| crx_installer->set_creation_flags(Extension::WAS_INSTALLED_BY_DEFAULT); |
| break; |
| case webapps::WebappInstallSource::ARC: |
| // Ensure that WebApk is not synced. There is some mechanism to propagate |
| // the local source of data in place of usual extension sync. |
| crx_installer->set_install_source( |
| ManifestLocation::kExternalPrefDownload); |
| break; |
| case webapps::WebappInstallSource::COUNT: |
| NOTREACHED(); |
| break; |
| default: |
| // All other install sources mean user-installed app. Do nothing. |
| break; |
| } |
| |
| const web_app::AppId app_id = |
| web_app::GenerateAppIdFromURL(web_app_info.start_url); |
| web_app::UpdateIntWebAppPref(profile_->GetPrefs(), app_id, |
| web_app::kLatestWebAppInstallSource, |
| static_cast<int>(options.install_source)); |
| crx_installer->InstallWebApp(web_app_info); |
| } |
| |
| void BookmarkAppInstallFinalizer::FinalizeUninstallAfterSync( |
| const web_app::AppId& app_id, |
| UninstallWebAppCallback callback) { |
| // Used only by the new USS-based sync system. |
| NOTREACHED(); |
| } |
| |
| void BookmarkAppInstallFinalizer::FinalizeUpdate( |
| const WebApplicationInfo& web_app_info, |
| content::WebContents* web_contents, |
| InstallFinalizedCallback callback) { |
| web_app::AppId expected_app_id = |
| web_app::GenerateAppIdFromURL(web_app_info.start_url); |
| |
| const Extension* existing_extension = GetEnabledExtension(expected_app_id); |
| if (!existing_extension) { |
| std::move(callback).Run(web_app::AppId(), |
| web_app::InstallResultCode::kWebAppDisabled); |
| return; |
| } |
| DCHECK(existing_extension->from_bookmark()); |
| |
| scoped_refptr<CrxInstaller> crx_installer = |
| crx_installer_factory_.Run(profile_); |
| crx_installer->set_installer_callback( |
| base::BindOnce(&BookmarkAppInstallFinalizer::OnExtensionUpdated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(expected_app_id), |
| existing_extension->short_name(), web_app_info, |
| std::move(callback), crx_installer)); |
| crx_installer->InitializeCreationFlagsForUpdate(existing_extension, |
| Extension::NO_FLAGS); |
| crx_installer->set_install_source(existing_extension->location()); |
| |
| crx_installer->InstallWebApp(web_app_info); |
| } |
| |
| void BookmarkAppInstallFinalizer::UninstallExternalWebApp( |
| const web_app::AppId& app_id, |
| webapps::WebappUninstallSource external_install_source, |
| UninstallWebAppCallback callback) { |
| // Bookmark apps don't support app installation from different sources. |
| // |external_install_source| is ignored here. |
| UninstallExtension(app_id, std::move(callback)); |
| } |
| |
| bool BookmarkAppInstallFinalizer::CanUserUninstallWebApp( |
| const web_app::AppId& app_id) const { |
| const Extension* app = GetEnabledExtension(app_id); |
| return app ? extensions::ExtensionSystem::Get(profile_) |
| ->management_policy() |
| ->UserMayModifySettings(app, nullptr) |
| : false; |
| } |
| |
| void BookmarkAppInstallFinalizer::UninstallWebApp( |
| const web_app::AppId& app_id, |
| webapps::WebappUninstallSource uninstall_source, |
| UninstallWebAppCallback callback) { |
| // Bookmark apps don't support app installation from different sources. |
| // Uninstall extension completely: |
| UninstallExtension(app_id, std::move(callback)); |
| } |
| |
| bool BookmarkAppInstallFinalizer::WasPreinstalledWebAppUninstalled( |
| const web_app::AppId& app_id) const { |
| return ExtensionPrefs::Get(profile_)->IsExternalExtensionUninstalled(app_id); |
| } |
| |
| void BookmarkAppInstallFinalizer::UninstallExtension( |
| const web_app::AppId& app_id, |
| UninstallWebAppCallback callback) { |
| if (!GetEnabledExtension(app_id)) { |
| LOG(WARNING) << "Couldn't uninstall app " << app_id |
| << "; Extension not installed."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), false)); |
| return; |
| } |
| |
| std::u16string error; |
| bool uninstalled = |
| ExtensionSystem::Get(profile_)->extension_service()->UninstallExtension( |
| app_id, UNINSTALL_REASON_ORPHANED_EXTERNAL_EXTENSION, &error); |
| |
| if (!uninstalled) { |
| LOG(WARNING) << "Couldn't uninstall app " << app_id << ". " << error; |
| } |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), uninstalled)); |
| } |
| |
| void BookmarkAppInstallFinalizer::SetCrxInstallerFactoryForTesting( |
| CrxInstallerFactory crx_installer_factory) { |
| crx_installer_factory_ = crx_installer_factory; |
| } |
| |
| const Extension* BookmarkAppInstallFinalizer::GetEnabledExtension( |
| const web_app::AppId& app_id) const { |
| const Extension* app = |
| ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(app_id); |
| return app; |
| } |
| |
| void BookmarkAppInstallFinalizer::OnExtensionInstalled( |
| const GURL& start_url, |
| LaunchType launch_type, |
| bool enable_experimental_tabbed_window, |
| bool is_locally_installed, |
| InstallFinalizedCallback callback, |
| scoped_refptr<CrxInstaller> crx_installer, |
| const base::Optional<CrxInstallError>& error) { |
| if (error) { |
| std::move(callback).Run( |
| web_app::AppId(), |
| web_app::InstallResultCode::kBookmarkExtensionInstallError); |
| return; |
| } |
| |
| const Extension* extension = crx_installer->extension(); |
| DCHECK(extension); |
| |
| if (extension != GetEnabledExtension(extension->id())) { |
| int extension_disabled_reasons = |
| ExtensionPrefs::Get(profile_)->GetDisableReasons(extension->id()); |
| LOG(ERROR) << "Installed extension was disabled: " |
| << extension_disabled_reasons; |
| std::move(callback).Run(web_app::AppId(), |
| web_app::InstallResultCode::kWebAppDisabled); |
| return; |
| } |
| |
| DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), start_url); |
| |
| SetLaunchType(profile_, extension->id(), launch_type); |
| |
| SetBookmarkAppIsLocallyInstalled(profile_, extension, is_locally_installed); |
| |
| if (!is_legacy_finalizer()) { |
| registry_controller().SetExperimentalTabbedWindowMode( |
| extension->id(), enable_experimental_tabbed_window, |
| /*is_user_action=*/false); |
| registrar().NotifyWebAppInstalled(extension->id()); |
| } |
| |
| std::move(callback).Run(extension->id(), |
| web_app::InstallResultCode::kSuccessNewInstall); |
| } |
| |
| void BookmarkAppInstallFinalizer::OnExtensionUpdated( |
| const web_app::AppId& expected_app_id, |
| const std::string& old_name, |
| const WebApplicationInfo& web_app_info, |
| InstallFinalizedCallback callback, |
| scoped_refptr<CrxInstaller> crx_installer, |
| const base::Optional<CrxInstallError>& error) { |
| if (error) { |
| std::move(callback).Run( |
| web_app::AppId(), |
| web_app::InstallResultCode::kBookmarkExtensionInstallError); |
| return; |
| } |
| |
| const Extension* extension = crx_installer->extension(); |
| DCHECK(extension); |
| DCHECK_EQ(extension->id(), expected_app_id); |
| |
| if (extension != GetEnabledExtension(extension->id())) { |
| std::move(callback).Run(web_app::AppId(), |
| web_app::InstallResultCode::kWebAppDisabled); |
| return; |
| } |
| |
| if (!is_legacy_finalizer()) { |
| // Using an empty `old_shortcut` and `file_handlers_need_os_update` here |
| // because BookmarkApp* is deprecated. Note that this code will not |
| // correctly update File Handlers on Linux, if un-deprecated. |
| std::unique_ptr<web_app::ShortcutInfo> old_shortcut; |
| os_integration_manager().UpdateOsHooks( |
| extension->id(), old_name, std::move(old_shortcut), |
| /*file_handlers_need_os_update=*/ |
| web_app::FileHandlerUpdateAction::kNoUpdate, web_app_info); |
| registrar().NotifyWebAppManifestUpdated(extension->id(), old_name); |
| } |
| std::move(callback).Run(extension->id(), |
| web_app::InstallResultCode::kSuccessAlreadyInstalled); |
| } |
| |
| } // namespace extensions |