blob: e6c99e5986ae36bb576f9183016368218a3fde06 [file] [log] [blame]
// Copyright 2020 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/components/os_integration_manager.h"
#include <memory>
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/browser/web_applications/components/web_app_id.h"
#include "chrome/browser/web_applications/components/web_app_shortcut.h"
#include "chrome/browser/web_applications/components/web_app_ui_manager.h"
#include "chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/browser_thread.h"
#if defined(OS_MAC)
#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
#endif
namespace {
// Used to disable os hooks globally when OsIntegrationManager::SuppressOsHooks
// can't be easily used.
bool g_suppress_os_hooks_for_testing_ = false;
} // namespace
namespace web_app {
// This barrier is designed to accumulate errors from calls to OS hook
// operations, and call the completion callback when all OS hook operations
// have completed. The |callback| is called when all copies of this object and
// all callbacks created using this object are destroyed.
class OsIntegrationManager::OsHooksBarrier
: public base::RefCounted<OsHooksBarrier> {
public:
explicit OsHooksBarrier(OsHooksResults results_default,
InstallOsHooksCallback callback)
: results_(results_default), callback_(std::move(callback)) {}
void OnError(OsHookType::Type type) { AddResult(type, false); }
base::OnceCallback<void(bool)> CreateBarrierCallbackForType(
OsHookType::Type type) {
return base::BindOnce(&OsHooksBarrier::AddResult, this, type);
}
private:
friend class base::RefCounted<OsHooksBarrier>;
~OsHooksBarrier() {
DCHECK(callback_);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), std::move(results_)));
}
void AddResult(OsHookType::Type type, bool result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
results_[type] = result;
}
OsHooksResults results_;
InstallOsHooksCallback callback_;
};
InstallOsHooksOptions::InstallOsHooksOptions() = default;
InstallOsHooksOptions::InstallOsHooksOptions(
const InstallOsHooksOptions& other) = default;
OsIntegrationManager::OsIntegrationManager(
Profile* profile,
std::unique_ptr<AppShortcutManager> shortcut_manager,
std::unique_ptr<FileHandlerManager> file_handler_manager,
std::unique_ptr<ProtocolHandlerManager> protocol_handler_manager)
: profile_(profile),
shortcut_manager_(std::move(shortcut_manager)),
file_handler_manager_(std::move(file_handler_manager)),
protocol_handler_manager_(std::move(protocol_handler_manager)) {}
OsIntegrationManager::~OsIntegrationManager() = default;
void OsIntegrationManager::SetSubsystems(AppRegistrar* registrar,
WebAppUiManager* ui_manager,
AppIconManager* icon_manager) {
registrar_ = registrar;
ui_manager_ = ui_manager;
file_handler_manager_->SetSubsystems(registrar);
shortcut_manager_->SetSubsystems(icon_manager, registrar);
if (protocol_handler_manager_)
protocol_handler_manager_->SetSubsystems(registrar);
}
void OsIntegrationManager::Start() {
DCHECK(registrar_);
DCHECK(file_handler_manager_);
#if defined(OS_MAC)
// Ensure that all installed apps are included in the AppShimRegistry when the
// profile is loaded. This is redundant, because apps are registered when they
// are installed. It is necessary, however, because app registration was added
// long after app installation launched. This should be removed after shipping
// for a few versions (whereupon it may be assumed that most applications have
// been registered).
std::vector<AppId> app_ids = registrar_->GetAppIds();
for (const auto& app_id : app_ids) {
AppShimRegistry::Get()->OnAppInstalledForProfile(app_id,
profile_->GetPath());
}
#endif
file_handler_manager_->Start();
if (protocol_handler_manager_)
protocol_handler_manager_->Start();
}
void OsIntegrationManager::InstallOsHooks(
const AppId& app_id,
InstallOsHooksCallback callback,
std::unique_ptr<WebApplicationInfo> web_app_info,
InstallOsHooksOptions options) {
if (g_suppress_os_hooks_for_testing_) {
OsHooksResults os_hooks_results{true};
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), os_hooks_results));
return;
}
MacAppShimOnAppInstalledForProfile(app_id);
scoped_refptr<OsHooksBarrier> barrier = base::MakeRefCounted<OsHooksBarrier>(
options.os_hooks, std::move(callback));
DCHECK(options.os_hooks[OsHookType::kShortcuts] ||
!options.os_hooks[OsHookType::kShortcutsMenu])
<< "Cannot install shortcuts menu without installing shortcuts.";
auto shortcuts_callback = base::BindOnce(
&OsIntegrationManager::OnShortcutsCreated, weak_ptr_factory_.GetWeakPtr(),
app_id, std::move(web_app_info), options, barrier);
// TODO(ortuno): Make adding a shortcut to the applications menu independent
// from adding a shortcut to desktop.
if (options.os_hooks[OsHookType::kShortcuts]) {
CreateShortcuts(app_id, options.add_to_desktop,
std::move(shortcuts_callback));
} else {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(shortcuts_callback),
/*shortcuts_created=*/false));
}
}
void OsIntegrationManager::UninstallAllOsHooks(
const AppId& app_id,
UninstallOsHooksCallback callback) {
OsHooksResults os_hooks;
os_hooks.set();
UninstallOsHooks(app_id, os_hooks, std::move(callback));
}
void OsIntegrationManager::UninstallOsHooks(const AppId& app_id,
const OsHooksResults& os_hooks,
UninstallOsHooksCallback callback) {
if (g_suppress_os_hooks_for_testing_) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), os_hooks));
return;
}
scoped_refptr<OsHooksBarrier> barrier =
base::MakeRefCounted<OsHooksBarrier>(os_hooks, std::move(callback));
if (os_hooks[OsHookType::kShortcutsMenu]) {
bool success = UnregisterShortcutsMenu(app_id);
if (!success)
barrier->OnError(OsHookType::kShortcutsMenu);
}
if (os_hooks[OsHookType::kShortcuts] || os_hooks[OsHookType::kRunOnOsLogin]) {
std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfo(app_id);
base::FilePath shortcut_data_dir =
internals::GetShortcutDataDir(*shortcut_info);
if (os_hooks[OsHookType::kRunOnOsLogin] &&
base::FeatureList::IsEnabled(features::kDesktopPWAsRunOnOsLogin)) {
UnregisterRunOnOsLogin(
app_id, shortcut_info->profile_path, shortcut_info->title,
barrier->CreateBarrierCallbackForType(OsHookType::kRunOnOsLogin));
}
if (os_hooks[OsHookType::kShortcuts]) {
DeleteShortcuts(
app_id, shortcut_data_dir, std::move(shortcut_info),
barrier->CreateBarrierCallbackForType(OsHookType::kShortcuts));
}
}
// TODO(https://crbug.com/1108109) we should return the result of file handler
// unregistration and record errors during unregistration.
if (os_hooks[OsHookType::kFileHandlers])
UnregisterFileHandlers(app_id);
// TODO(https://crbug.com/1108109) we should return the result of protocol
// handler unregistration and record errors during unregistration.
if (os_hooks[OsHookType::kProtocolHandlers])
UnregisterProtocolHandlers(app_id);
// There is a chance uninstallation point was created with feature flag
// enabled so we need to clean it up regardless of feature flag state.
if (os_hooks[OsHookType::kUninstallationViaOsSettings])
UnregisterWebAppOsUninstallation(app_id);
}
void OsIntegrationManager::UpdateOsHooks(
const AppId& app_id,
base::StringPiece old_name,
const WebApplicationInfo& web_app_info) {
DCHECK(shortcut_manager_);
if (g_suppress_os_hooks_for_testing_)
return;
// TODO(crbug.com/1079439): Update file handlers.
shortcut_manager_->UpdateShortcuts(app_id, old_name);
if (base::FeatureList::IsEnabled(
features::kDesktopPWAsAppIconShortcutsMenu) &&
!web_app_info.shortcuts_menu_item_infos.empty()) {
shortcut_manager_->RegisterShortcutsMenuWithOs(
app_id, web_app_info.shortcuts_menu_item_infos,
web_app_info.shortcuts_menu_icons_bitmaps);
} else {
// Unregister shortcuts menu when feature is disabled or
// shortcuts_menu_item_infos is empty.
shortcut_manager_->UnregisterShortcutsMenuWithOs(app_id);
}
}
void OsIntegrationManager::GetShortcutInfoForApp(
const AppId& app_id,
AppShortcutManager::GetShortcutInfoCallback callback) {
DCHECK(shortcut_manager_);
return shortcut_manager_->GetShortcutInfoForApp(app_id, std::move(callback));
}
bool OsIntegrationManager::IsFileHandlingAPIAvailable(const AppId& app_id) {
DCHECK(file_handler_manager_);
return file_handler_manager_->IsFileHandlingAPIAvailable(app_id);
}
const apps::FileHandlers* OsIntegrationManager::GetEnabledFileHandlers(
const AppId& app_id) {
DCHECK(file_handler_manager_);
return file_handler_manager_->GetEnabledFileHandlers(app_id);
}
const base::Optional<GURL> OsIntegrationManager::GetMatchingFileHandlerURL(
const AppId& app_id,
const std::vector<base::FilePath>& launch_files) {
DCHECK(file_handler_manager_);
return file_handler_manager_->GetMatchingFileHandlerURL(app_id, launch_files);
}
void OsIntegrationManager::MaybeUpdateFileHandlingOriginTrialExpiry(
content::WebContents* web_contents,
const AppId& app_id) {
DCHECK(file_handler_manager_);
return file_handler_manager_->MaybeUpdateFileHandlingOriginTrialExpiry(
web_contents, app_id);
}
void OsIntegrationManager::ForceEnableFileHandlingOriginTrial(
const AppId& app_id) {
DCHECK(file_handler_manager_);
return file_handler_manager_->ForceEnableFileHandlingOriginTrial(app_id);
}
void OsIntegrationManager::DisableForceEnabledFileHandlingOriginTrial(
const AppId& app_id) {
DCHECK(file_handler_manager_);
return file_handler_manager_->DisableForceEnabledFileHandlingOriginTrial(
app_id);
}
FileHandlerManager& OsIntegrationManager::file_handler_manager_for_testing() {
DCHECK(file_handler_manager_);
return *file_handler_manager_;
}
ScopedOsHooksSuppress OsIntegrationManager::ScopedSuppressOsHooksForTesting() {
// Creating OS hooks on ChromeOS doesn't write files to disk, so it's
// unnecessary to suppress and it provides better crash coverage.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
return std::make_unique<base::AutoReset<bool>>(
&g_suppress_os_hooks_for_testing_, true);
#else
return std::make_unique<base::AutoReset<bool>>(
&g_suppress_os_hooks_for_testing_, false);
#endif
}
TestOsIntegrationManager* OsIntegrationManager::AsTestOsIntegrationManager() {
return nullptr;
}
void OsIntegrationManager::CreateShortcuts(const AppId& app_id,
bool add_to_desktop,
CreateShortcutsCallback callback) {
if (shortcut_manager_->CanCreateShortcuts()) {
shortcut_manager_->CreateShortcuts(app_id, add_to_desktop,
std::move(callback));
} else {
std::move(callback).Run(false);
}
}
void OsIntegrationManager::RegisterFileHandlers(
const AppId& app_id,
base::OnceCallback<void(bool success)> callback) {
DCHECK(file_handler_manager_);
file_handler_manager_->EnableAndRegisterOsFileHandlers(app_id);
// TODO(crbug.com/1087219): callback should be run after all hooks are
// deployed, need to refactor filehandler to allow this.
std::move(callback).Run(true);
}
void OsIntegrationManager::RegisterProtocolHandlers(
const AppId& app_id,
base::OnceCallback<void(bool success)> callback) {
if (!protocol_handler_manager_) {
std::move(callback).Run(true);
return;
}
// TODO(crbug.com/1019239): Call protocol_handler_manager_ implementation.
// TODO(crbug.com/1087219): callback should be run after all hooks are
// deployed, need to refactor protocol_handler_manager to allow this.
std::move(callback).Run(true);
}
void OsIntegrationManager::RegisterShortcutsMenu(
const AppId& app_id,
const std::vector<WebApplicationShortcutsMenuItemInfo>&
shortcuts_menu_item_infos,
const ShortcutsMenuIconsBitmaps& shortcuts_menu_icons_bitmaps,
base::OnceCallback<void(bool success)> callback) {
if (!ShouldRegisterShortcutsMenuWithOs()) {
std::move(callback).Run(true);
return;
}
DCHECK(shortcut_manager_);
shortcut_manager_->RegisterShortcutsMenuWithOs(
app_id, shortcuts_menu_item_infos, shortcuts_menu_icons_bitmaps);
// TODO(https://crbug.com/1098471): fix RegisterShortcutsMenuWithOs to
// take callback.
std::move(callback).Run(true);
}
void OsIntegrationManager::ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
const AppId& app_id,
base::OnceCallback<void(bool success)> callback) {
if (!ShouldRegisterShortcutsMenuWithOs()) {
std::move(callback).Run(true);
return;
}
shortcut_manager_->ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
app_id, std::move(callback));
}
void OsIntegrationManager::RegisterRunOnOsLogin(
const AppId& app_id,
RegisterRunOnOsLoginCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
shortcut_manager_->GetShortcutInfoForApp(
app_id,
base::BindOnce(
&OsIntegrationManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void OsIntegrationManager::MacAppShimOnAppInstalledForProfile(
const AppId& app_id) {
#if defined(OS_MAC)
AppShimRegistry::Get()->OnAppInstalledForProfile(app_id, profile_->GetPath());
#endif
}
void OsIntegrationManager::AddAppToQuickLaunchBar(const AppId& app_id) {
DCHECK(ui_manager_);
if (ui_manager_->CanAddAppToQuickLaunchBar()) {
ui_manager_->AddAppToQuickLaunchBar(app_id);
}
}
void OsIntegrationManager::RegisterWebAppOsUninstallation(
const AppId& app_id,
const std::string& name) {
if (ShouldRegisterUninstallationViaOsSettingsWithOs()) {
RegisterUninstallationViaOsSettingsWithOs(app_id, name, profile_);
}
}
bool OsIntegrationManager::UnregisterShortcutsMenu(const AppId& app_id) {
if (!ShouldRegisterShortcutsMenuWithOs())
return true;
return UnregisterShortcutsMenuWithOs(app_id, profile_->GetPath());
}
void OsIntegrationManager::UnregisterRunOnOsLogin(
const AppId& app_id,
const base::FilePath& profile_path,
const base::string16& shortcut_title,
UnregisterRunOnOsLoginCallback callback) {
ScheduleUnregisterRunOnOsLogin(app_id, profile_path, shortcut_title,
std::move(callback));
}
void OsIntegrationManager::DeleteShortcuts(
const AppId& app_id,
const base::FilePath& shortcuts_data_dir,
std::unique_ptr<ShortcutInfo> shortcut_info,
DeleteShortcutsCallback callback) {
if (shortcut_manager_->CanCreateShortcuts()) {
auto shortcuts_callback = base::BindOnce(
&OsIntegrationManager::OnShortcutsDeleted,
weak_ptr_factory_.GetWeakPtr(), app_id, std::move(callback));
shortcut_manager_->DeleteShortcuts(app_id, shortcuts_data_dir,
std::move(shortcut_info),
std::move(shortcuts_callback));
} else {
std::move(callback).Run(false);
}
}
void OsIntegrationManager::UnregisterFileHandlers(const AppId& app_id) {
file_handler_manager_->DisableAndUnregisterOsFileHandlers(app_id);
}
void OsIntegrationManager::UnregisterProtocolHandlers(const AppId& app_id) {
if (!protocol_handler_manager_)
return;
// TODO(crbug.com/1019239): Call protocol_handler_manager_ implementation.
}
void OsIntegrationManager::UnregisterWebAppOsUninstallation(
const AppId& app_id) {
if (ShouldRegisterUninstallationViaOsSettingsWithOs())
UnegisterUninstallationViaOsSettingsWithOs(app_id, profile_);
}
std::unique_ptr<ShortcutInfo> OsIntegrationManager::BuildShortcutInfo(
const AppId& app_id) {
DCHECK(shortcut_manager_);
return shortcut_manager_->BuildShortcutInfo(app_id);
}
void OsIntegrationManager::OnShortcutsCreated(
const AppId& app_id,
std::unique_ptr<WebApplicationInfo> web_app_info,
InstallOsHooksOptions options,
scoped_refptr<OsHooksBarrier> barrier,
bool shortcuts_created) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(barrier);
bool shortcut_creation_failure =
!shortcuts_created && options.os_hooks[OsHookType::kShortcuts];
if (shortcut_creation_failure)
barrier->OnError(OsHookType::kShortcuts);
if (options.os_hooks[OsHookType::kFileHandlers]) {
RegisterFileHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kFileHandlers));
}
if (options.os_hooks[OsHookType::kProtocolHandlers]) {
RegisterProtocolHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kProtocolHandlers));
}
if (options.os_hooks[OsHookType::kShortcuts] &&
options.add_to_quick_launch_bar) {
AddAppToQuickLaunchBar(app_id);
}
if (shortcuts_created && options.os_hooks[OsHookType::kShortcutsMenu] &&
base::FeatureList::IsEnabled(
features::kDesktopPWAsAppIconShortcutsMenu)) {
if (web_app_info) {
RegisterShortcutsMenu(
app_id, web_app_info->shortcuts_menu_item_infos,
web_app_info->shortcuts_menu_icons_bitmaps,
barrier->CreateBarrierCallbackForType(OsHookType::kShortcutsMenu));
} else {
ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
app_id,
barrier->CreateBarrierCallbackForType(OsHookType::kShortcutsMenu));
}
}
if (options.os_hooks[OsHookType::kRunOnOsLogin] &&
base::FeatureList::IsEnabled(features::kDesktopPWAsRunOnOsLogin)) {
// TODO(crbug.com/897302): Implement Run on OS Login mode selection.
// Currently it is set to be the default: RunOnOsLoginMode::kWindowed
RegisterRunOnOsLogin(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kRunOnOsLogin));
}
if (options.os_hooks[OsHookType::kUninstallationViaOsSettings] &&
base::FeatureList::IsEnabled(
features::kEnableWebAppUninstallFromOsSettings)) {
RegisterWebAppOsUninstallation(app_id, registrar_->GetAppShortName(app_id));
}
}
void OsIntegrationManager::OnShortcutsDeleted(const AppId& app_id,
DeleteShortcutsCallback callback,
bool shortcuts_deleted) {
#if defined(OS_MAC)
bool delete_multi_profile_shortcuts =
AppShimRegistry::Get()->OnAppUninstalledForProfile(app_id,
profile_->GetPath());
if (delete_multi_profile_shortcuts) {
internals::ScheduleDeleteMultiProfileShortcutsForApp(app_id,
std::move(callback));
}
#else
std::move(callback).Run(shortcuts_deleted);
#endif
}
void OsIntegrationManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin(
RegisterRunOnOsLoginCallback callback,
std::unique_ptr<ShortcutInfo> info) {
ScheduleRegisterRunOnOsLogin(std::move(info), std::move(callback));
}
} // namespace web_app