blob: 65e866ed644860da462766ac98067841c2a6fe99 [file] [log] [blame]
// 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/logging.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 "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/components/os_integration_manager.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_icon_generator.h"
#include "chrome/browser/web_applications/components/web_app_install_utils.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_shortcuts_menu.h"
#include "chrome/browser/web_applications/components/web_app_system_web_app_data.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/isolation_prefs_utils.h"
#include "chrome/browser/web_applications/manifest_update_task.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_installation_utils.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/skia/include/core/SkColor.h"
namespace web_app {
namespace {
Source::Type InferSourceFromWebAppUninstallSource(
webapps::WebappUninstallSource external_install_source) {
switch (external_install_source) {
case webapps::WebappUninstallSource::kAppList:
case webapps::WebappUninstallSource::kAppMenu:
case webapps::WebappUninstallSource::kAppManagement:
case webapps::WebappUninstallSource::kAppsPage:
case webapps::WebappUninstallSource::kMigration:
case webapps::WebappUninstallSource::kOsSettings:
case webapps::WebappUninstallSource::kSync:
case webapps::WebappUninstallSource::kShelf:
case webapps::WebappUninstallSource::kUnknown:
return Source::kSync;
case webapps::WebappUninstallSource::kExternalPreinstalled:
case webapps::WebappUninstallSource::kInternalPreinstalled:
case webapps::WebappUninstallSource::kPlaceholderReplacement:
return Source::kDefault;
case webapps::WebappUninstallSource::kExternalPolicy:
return Source::kPolicy;
case webapps::WebappUninstallSource::kSystemPreinstalled:
return Source::kSystem;
case webapps::WebappUninstallSource::kArc:
return Source::kWebAppStore;
}
}
webapps::WebappUninstallSource ConvertSourceTypeToWebAppUninstallSource(
Source::Type source) {
switch (source) {
case Source::kDefault:
return webapps::WebappUninstallSource::kExternalPreinstalled;
case Source::kPolicy:
return webapps::WebappUninstallSource::kExternalPolicy;
case Source::kSync:
return webapps::WebappUninstallSource::kInternalPreinstalled;
case Source::kSystem:
return webapps::WebappUninstallSource::kSystemPreinstalled;
case Source::kWebAppStore:
return webapps::WebappUninstallSource::kArc;
}
}
} // namespace
WebAppInstallFinalizer::WebAppInstallFinalizer(Profile* profile,
WebAppIconManager* icon_manager)
: profile_(profile), icon_manager_(icon_manager) {}
WebAppInstallFinalizer::~WebAppInstallFinalizer() = default;
void WebAppInstallFinalizer::FinalizeInstall(
const WebApplicationInfo& 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(),
InstallResultCode::kWebAppProviderNotReady);
return;
}
// TODO(loyso): Expose Source argument as a field of AppTraits struct.
const auto source =
InferSourceFromMetricsInstallSource(options.install_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 (!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:
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);
} 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());
}
// 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_source) ||
!existing_web_app) {
web_app->SetUserDisplayMode(web_app_info.open_as_window
? DisplayMode::kStandalone
: DisplayMode::kBrowser);
}
// `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());
// `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->SetIsInSyncInstall(false);
web_app->SetStorageIsolated(web_app_info.is_storage_isolated);
UpdateIntWebAppPref(profile_->GetPrefs(), app_id, kLatestWebAppInstallSource,
static_cast<int>(options.install_source));
// TODO(crbug.com/897314): Store this as a display mode on WebApp to
// participate in the DB transactional model.
registry_controller().SetExperimentalTabbedWindowMode(
app_id, web_app_info.enable_experimental_tabbed_window,
/*is_user_action=*/false);
// This step is necessary in case this app shares an origin with another PWA
// which already asked for file handling permissions, and the new app asks to
// handle more file types.
MaybeResetFileHandlingPermission(web_app_info);
CommitCallback commit_callback = base::BindOnce(
&WebAppInstallFinalizer::OnDatabaseCommitCompletedForInstall,
weak_ptr_factory_.GetWeakPtr(), std::move(callback), app_id);
SetWebAppManifestFieldsAndWriteData(web_app_info, std::move(web_app),
std::move(commit_callback));
}
void WebAppInstallFinalizer::FinalizeUninstallAfterSync(
const AppId& app_id,
UninstallWebAppCallback callback) {
DCHECK(started_);
// WebAppSyncBridge::ApplySyncChangesToRegistrar does the actual
// NotifyWebAppWillBeUninstalled and unregistration of the app from the
// registry.
DCHECK(!GetWebAppRegistrar().GetAppById(app_id));
icon_manager_->DeleteData(
app_id,
base::BindOnce(
&WebAppInstallFinalizer::OnIconsDataDeletedAndWebAppUninstalled,
weak_ptr_factory_.GetWeakPtr(), app_id,
webapps::WebappUninstallSource::kSync, std::move(callback)));
}
void WebAppInstallFinalizer::UninstallExternalWebApp(
const AppId& app_id,
webapps::WebappUninstallSource webapp_uninstall_source,
UninstallWebAppCallback callback) {
DCHECK(started_);
DCHECK(webapp_uninstall_source ==
webapps::WebappUninstallSource::kInternalPreinstalled ||
webapp_uninstall_source ==
webapps::WebappUninstallSource::kExternalPreinstalled ||
webapp_uninstall_source ==
webapps::WebappUninstallSource::kExternalPolicy ||
webapp_uninstall_source ==
webapps::WebappUninstallSource::kSystemPreinstalled ||
webapp_uninstall_source ==
webapps::WebappUninstallSource::kPlaceholderReplacement ||
webapp_uninstall_source == webapps::WebappUninstallSource::kArc);
Source::Type source =
InferSourceFromWebAppUninstallSource(webapp_uninstall_source);
DCHECK_NE(source, Source::Type::kSync);
UninstallExternalWebAppOrRemoveSource(app_id, 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);
const WebApp* app = GetWebAppRegistrar().GetAppById(app_id);
DCHECK(app);
DCHECK(app->CanUserUninstallWebApp());
if (app->IsPreinstalledApp()) {
UpdateBoolWebAppPref(profile_->GetPrefs(), app_id,
kWasExternalAppUninstalledByUser, true);
}
// 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));
}
bool WebAppInstallFinalizer::WasPreinstalledWebAppUninstalled(
const AppId& app_id) const {
return GetBoolWebAppPref(profile_->GetPrefs(), app_id,
kWasExternalAppUninstalledByUser);
}
void WebAppInstallFinalizer::FinalizeUpdate(
const WebApplicationInfo& web_app_info,
content::WebContents* web_contents,
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_in_sync_install() ||
web_app_info.start_url != existing_web_app->start_url()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), AppId(),
InstallResultCode::kWebAppDisabled));
return;
}
bool should_update_os_hooks = ShouldUpdateOsHooks(app_id);
FileHandlerUpdateAction file_handlers_need_os_update =
DoFileHandlersNeedOsUpdate(app_id, web_app_info, web_contents);
// Grab the shortcut info before the app is removed from the database.
os_integration_manager().GetShortcutInfoForApp(
app_id,
base::BindOnce(&WebAppInstallFinalizer::FinalizeUpdateWithShortcutInfo,
weak_ptr_factory_.GetWeakPtr(), should_update_os_hooks,
file_handlers_need_os_update, std::move(callback), app_id,
web_app_info));
}
void WebAppInstallFinalizer::Start() {
DCHECK(!started_);
content_settings_observer_.Observe(
HostContentSettingsMapFactory::GetForProfile(profile_));
DetectAndCorrectFileHandlingPermissionBlocks();
started_ = true;
}
void WebAppInstallFinalizer::Shutdown() {
started_ = false;
}
bool WebAppInstallFinalizer::IsFileHandlerPermissionBlocked(const GURL& scope) {
return HostContentSettingsMapFactory::GetForProfile(profile_)
->GetContentSetting(scope, scope,
ContentSettingsType::FILE_HANDLING) ==
CONTENT_SETTING_BLOCK;
}
void WebAppInstallFinalizer::UpdateFileHandlerPermission(
const AppId& app_id,
bool permission_blocked) {
ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
WebApp* app_to_update = update->UpdateApp(app_id);
app_to_update->SetFileHandlerPermissionBlocked(permission_blocked);
FileHandlerUpdateAction file_handlers_need_os_update =
permission_blocked ? FileHandlerUpdateAction::kRemove
: FileHandlerUpdateAction::kUpdate;
os_integration_manager().UpdateFileHandlers(app_id,
file_handlers_need_os_update);
}
void WebAppInstallFinalizer::DetectAndCorrectFileHandlingPermissionBlocks() {
DCHECK(!started_);
for (const AppId& app_id : registrar().GetAppIds()) {
const WebApp* app = registrar().AsWebAppRegistrar()->GetAppById(app_id);
if (!app || !app->is_locally_installed()) {
continue;
}
const GURL url = app->scope();
bool permission_blocked = IsFileHandlerPermissionBlocked(app->scope());
if (permission_blocked != app->file_handler_permission_blocked()) {
UpdateFileHandlerPermission(app_id, permission_blocked);
}
}
}
void WebAppInstallFinalizer::OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type) {
if (!started_ || content_type != ContentSettingsType::FILE_HANDLING)
return;
auto* host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile_);
DCHECK(host_content_settings_map);
for (const AppId& app_id : registrar().GetAppIds()) {
const WebApp* app = registrar().AsWebAppRegistrar()->GetAppById(app_id);
if (!app || !app->is_locally_installed()) {
continue;
}
const GURL url = app->scope();
if (!primary_pattern.Matches(url))
continue;
ContentSetting setting = host_content_settings_map->GetContentSetting(
url, url, ContentSettingsType::FILE_HANDLING);
bool permission_blocked = setting == CONTENT_SETTING_BLOCK;
if (permission_blocked != app->file_handler_permission_blocked()) {
UpdateFileHandlerPermission(app_id, permission_blocked);
}
}
}
void WebAppInstallFinalizer::UninstallWebAppInternal(
const AppId& app_id,
webapps::WebappUninstallSource uninstall_source,
UninstallWebAppCallback callback) {
// If the app is already uninstalling then avoid triggering another uninstall.
{
ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
WebApp* app = update->UpdateApp(app_id);
if (!app || app->is_uninstalling()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
/*uninstalled=*/false));
return;
}
// Set uninstalling flag and continue with app uninstall.
app->SetIsUninstalling(true);
}
registrar().NotifyWebAppWillBeUninstalled(app_id);
os_integration_manager().UninstallAllOsHooks(
app_id, base::BindOnce(&WebAppInstallFinalizer::OnUninstallOsHooks,
weak_ptr_factory_.GetWeakPtr(), app_id,
uninstall_source, std::move(callback)));
}
void WebAppInstallFinalizer::OnUninstallOsHooks(
const AppId& app_id,
webapps::WebappUninstallSource uninstall_source,
UninstallWebAppCallback callback,
OsHooksResults os_hooks_info) {
WebAppRegistrar* web_app_registrar = registrar().AsWebAppRegistrar();
DCHECK(web_app_registrar);
const WebApp* web_app = web_app_registrar->GetAppById(app_id);
DCHECK(web_app);
RemoveAppIsolationState(profile_->GetPrefs(),
url::Origin::Create(web_app->scope()));
ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
update->DeleteApp(app_id);
icon_manager_->DeleteData(
app_id,
base::BindOnce(
&WebAppInstallFinalizer::OnIconsDataDeletedAndWebAppUninstalled,
weak_ptr_factory_.GetWeakPtr(), app_id, uninstall_source,
std::move(callback)));
}
void WebAppInstallFinalizer::UninstallExternalWebAppOrRemoveSource(
const AppId& app_id,
Source::Type source,
UninstallWebAppCallback callback) {
const WebApp* app = GetWebAppRegistrar().GetAppById(app_id);
if (!app) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
/*uninstalled=*/false));
return;
}
if (app->HasOnlySource(source)) {
webapps::WebappUninstallSource uninstall_source =
ConvertSourceTypeToWebAppUninstallSource(source);
UninstallWebAppInternal(app_id, uninstall_source, std::move(callback));
} else {
ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
WebApp* app_to_update = update->UpdateApp(app_id);
app_to_update->RemoveSource(source);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
/*uninstalled=*/true));
}
}
void WebAppInstallFinalizer::SetWebAppManifestFieldsAndWriteData(
const WebApplicationInfo& web_app_info,
std::unique_ptr<WebApp> web_app,
CommitCallback commit_callback) {
SetWebAppManifestFields(web_app_info, *web_app);
web_app->SetFileHandlerPermissionBlocked(
IsFileHandlerPermissionBlocked(web_app->scope()));
AppId app_id = web_app->app_id();
icon_manager_->WriteData(
std::move(app_id), web_app_info.icon_bitmaps,
base::BindOnce(&WebAppInstallFinalizer::OnIconsDataWritten,
weak_ptr_factory_.GetWeakPtr(), std::move(commit_callback),
std::move(web_app),
web_app_info.shortcuts_menu_icon_bitmaps));
}
void WebAppInstallFinalizer::OnIconsDataWritten(
CommitCallback commit_callback,
std::unique_ptr<WebApp> web_app,
const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(commit_callback).Run(success);
return;
}
if (shortcuts_menu_icon_bitmaps.empty()) {
OnShortcutsMenuIconsDataWritten(std::move(commit_callback),
std::move(web_app), success);
} else {
AppId app_id = web_app->app_id();
icon_manager_->WriteShortcutsMenuIconsData(
app_id, shortcuts_menu_icon_bitmaps,
base::BindOnce(&WebAppInstallFinalizer::OnShortcutsMenuIconsDataWritten,
weak_ptr_factory_.GetWeakPtr(),
std::move(commit_callback), std::move(web_app)));
}
}
void WebAppInstallFinalizer::OnShortcutsMenuIconsDataWritten(
CommitCallback commit_callback,
std::unique_ptr<WebApp> web_app,
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 =
registry_controller().AsWebAppSyncBridge()->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));
registry_controller().AsWebAppSyncBridge()->CommitUpdate(
std::move(update), std::move(commit_callback));
}
void WebAppInstallFinalizer::OnIconsDataDeletedAndWebAppUninstalled(
const AppId& app_id,
webapps::WebappUninstallSource uninstall_source,
UninstallWebAppCallback callback,
bool success) {
registrar().NotifyWebAppUninstalled(app_id);
webapps::InstallableMetrics::TrackUninstallEvent(uninstall_source);
std::move(callback).Run(success);
}
void WebAppInstallFinalizer::OnDatabaseCommitCompletedForInstall(
InstallFinalizedCallback callback,
AppId app_id,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(callback).Run(AppId(), InstallResultCode::kWriteDataFailed);
return;
}
registrar().NotifyWebAppInstalled(app_id);
std::move(callback).Run(app_id, InstallResultCode::kSuccessNewInstall);
}
void WebAppInstallFinalizer::FinalizeUpdateWithShortcutInfo(
bool should_update_os_hooks,
FileHandlerUpdateAction file_handlers_need_os_update,
InstallFinalizedCallback callback,
const AppId app_id,
const WebApplicationInfo& web_app_info,
std::unique_ptr<ShortcutInfo> old_shortcut) {
// Prepare copy-on-write to update existing app.
const WebApp* existing_web_app = GetWebAppRegistrar().GetAppById(app_id);
auto web_app = std::make_unique<WebApp>(*existing_web_app);
CommitCallback commit_callback = base::BindOnce(
&WebAppInstallFinalizer::OnDatabaseCommitCompletedForUpdate,
weak_ptr_factory_.GetWeakPtr(), std::move(callback), app_id,
existing_web_app->name(), std::move(old_shortcut), should_update_os_hooks,
file_handlers_need_os_update, web_app_info);
SetWebAppManifestFieldsAndWriteData(web_app_info, std::move(web_app),
std::move(commit_callback));
}
bool WebAppInstallFinalizer::ShouldUpdateOsHooks(const AppId& app_id) {
#if defined(OS_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.
WebAppRegistrar* web_app_registrar = registrar().AsWebAppRegistrar();
DCHECK(web_app_registrar);
return !web_app_registrar->WasInstalledByDefaultOnly(app_id);
#endif // defined(OS_CHROMEOS)
}
FileHandlerUpdateAction WebAppInstallFinalizer::DoFileHandlersNeedOsUpdate(
const AppId app_id,
const WebApplicationInfo& web_app_info,
content::WebContents* web_contents) {
if (!os_integration_manager().IsFileHandlingAPIAvailable(app_id))
return FileHandlerUpdateAction::kNoUpdate;
const GURL& url = web_app_info.scope;
// Keep in sync with chromeos::kChromeUIMediaAppURL.
const char kChromeUIMediaAppURL[] = "chrome://media-app/";
// Keep in sync with chromeos::kChromeUICameraAppURL.
const char kChromeUICameraAppURL[] = "chrome://camera-app/";
// Omit file handler removal and permission downgrade for the ChromeOS Media
// and Camera System Web Apps (SWAs), which have permissions granted by
// default.
// TODO(huangdarwin): Find a better architecture to structure this exception
// and check relevant only in ChromeOS (outside of LaCrOS).
if (url == kChromeUIMediaAppURL || url == kChromeUICameraAppURL) {
return FileHandlerUpdateAction::kUpdate;
}
// Downgrade file handlers permission before
// OsIntegrationManager::UpdateOsHooks(), as `web_contents` may no
// longer exist by the time we reach OsIntegrationManager.
//
// It's possible we'll downgrade the permission and then fail to update OS
// integrations (ex. if the disk or icon downloads fail), but this is ok
// because these failures should rarely occur.
ContentSetting content_setting =
MaybeResetFileHandlingPermission(web_app_info);
// If the permission is "BLOCK", leave it as is. When permission is
// "BLOCK", the `OnContentSettingChanged()` and
// `DetectAndCorrectFileHandlingPermissionBlocks()` should capture the
// permission change and make sure the OS and db state are in sync with the
// HostContentSettingsMap setting. Therefore, manifest update task should not
// update file handlers due to blocked permission state.
if (content_setting == CONTENT_SETTING_BLOCK)
return FileHandlerUpdateAction::kNoUpdate;
// TODO(https://crbug.com/1197013): Consider trying to re-use
// HaveFileHandlersChanged() results from the ManifestUpdateTask.
if (!HaveFileHandlersChanged(
/*old_handlers=*/registrar().GetAppFileHandlers(app_id),
/*new_handlers=*/web_app_info.file_handlers)) {
return FileHandlerUpdateAction::kNoUpdate;
}
return FileHandlerUpdateAction::kUpdate;
}
ContentSetting WebAppInstallFinalizer::MaybeResetFileHandlingPermission(
const WebApplicationInfo& web_app_info) {
const GURL& url = web_app_info.scope;
HostContentSettingsMap* settings_map =
HostContentSettingsMapFactory::GetForProfile(profile_);
ContentSetting status = settings_map->GetContentSetting(
url, url, ContentSettingsType::FILE_HANDLING);
// If file handling permission is "ALLOW", downgrade to "ASK" via reset, as
// the user may not want to allow newly added file handlers, which may include
// more dangerous extensions.
if (status == CONTENT_SETTING_ALLOW &&
!AreFileHandlersAlreadyRegistered(profile_, url,
web_app_info.file_handlers)) {
settings_map->SetContentSettingDefaultScope(
url, url, ContentSettingsType::FILE_HANDLING, CONTENT_SETTING_DEFAULT);
return CONTENT_SETTING_ASK;
}
return status;
}
void WebAppInstallFinalizer::OnDatabaseCommitCompletedForUpdate(
InstallFinalizedCallback callback,
AppId app_id,
std::string old_name,
std::unique_ptr<ShortcutInfo> old_shortcut,
bool should_update_os_hooks,
FileHandlerUpdateAction file_handlers_need_os_update,
const WebApplicationInfo& web_app_info,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success) {
std::move(callback).Run(AppId(), InstallResultCode::kWriteDataFailed);
return;
}
if (should_update_os_hooks) {
os_integration_manager().UpdateOsHooks(
app_id, old_name, std::move(old_shortcut), file_handlers_need_os_update,
web_app_info);
}
registrar().NotifyWebAppManifestUpdated(app_id, old_name);
std::move(callback).Run(app_id, InstallResultCode::kSuccessAlreadyInstalled);
}
WebAppRegistrar& WebAppInstallFinalizer::GetWebAppRegistrar() const {
WebAppRegistrar* web_app_registrar = registrar().AsWebAppRegistrar();
DCHECK(web_app_registrar);
return *web_app_registrar;
}
} // namespace web_app