blob: 2deca635b9e36eb169c901fd602fde6de9bb9504 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// 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/os_integration/os_integration_manager.h"
#include <memory>
#include <utility>
#include "base/auto_reset.h"
#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/os_integration/file_handling_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/os_integration_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/protocol_handling_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/shortcut_menu_handling_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/shortcut_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/uninstallation_via_os_settings_sub_manager.h"
#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
#include "chrome/browser/web_applications/os_integration/web_app_uninstallation_via_os_settings_registration.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_id.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_ui_manager.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/web_applications/app_shim_registry_mac.h"
#endif
namespace {
bool g_suppress_os_hooks_for_testing_ = false;
} // namespace
namespace web_app {
namespace {
OsHooksErrors GetFinalErrorBitsetFromCollection(
std::vector<OsHooksErrors> os_hooks_errors) {
OsHooksErrors final_errors;
for (const OsHooksErrors& error : os_hooks_errors) {
final_errors = final_errors | error;
}
return final_errors;
}
} // namespace
bool AreOsIntegrationSubManagersEnabled() {
return base::FeatureList::IsEnabled(features::kOsIntegrationSubManagers);
}
bool AreSubManagersExecuteEnabled() {
if (!AreOsIntegrationSubManagersEnabled())
return false;
return (features::kOsIntegrationSubManagersStageParam.Get() ==
features::OsIntegrationSubManagersStage::kExecuteAndWriteConfig);
}
OsIntegrationManager::ScopedSuppressForTesting::ScopedSuppressForTesting()
:
// 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)
scope_(&g_suppress_os_hooks_for_testing_, true)
#else
scope_(&g_suppress_os_hooks_for_testing_, false)
#endif
{
}
OsIntegrationManager::ScopedSuppressForTesting::~ScopedSuppressForTesting() =
default;
// 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(OsHooksErrors errors_default,
InstallOsHooksCallback callback)
: errors_(errors_default), callback_(std::move(callback)) {}
void OnError(OsHookType::Type type) { AddResult(type, Result::kError); }
ResultCallback CreateBarrierCallbackForType(OsHookType::Type type) {
return base::BindOnce(&OsHooksBarrier::AddResult, this, type);
}
private:
friend class base::RefCounted<OsHooksBarrier>;
~OsHooksBarrier() {
CHECK(callback_);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), std::move(errors_)));
}
void AddResult(OsHookType::Type type, Result result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
errors_[type] = result == Result::kError ? true : false;
}
OsHooksErrors errors_;
InstallOsHooksCallback callback_;
};
InstallOsHooksOptions::InstallOsHooksOptions() = default;
InstallOsHooksOptions::InstallOsHooksOptions(
const InstallOsHooksOptions& other) = default;
InstallOsHooksOptions& InstallOsHooksOptions::operator=(
const InstallOsHooksOptions& other) = default;
OsIntegrationManager::OsIntegrationManager(
Profile* profile,
std::unique_ptr<WebAppShortcutManager> shortcut_manager,
std::unique_ptr<WebAppFileHandlerManager> file_handler_manager,
std::unique_ptr<WebAppProtocolHandlerManager> protocol_handler_manager,
std::unique_ptr<UrlHandlerManager> url_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)),
url_handler_manager_(std::move(url_handler_manager)) {}
OsIntegrationManager::~OsIntegrationManager() = default;
// static
base::RepeatingCallback<void(OsHooksErrors)>
OsIntegrationManager::GetBarrierForSynchronize(
AnyOsHooksErrorCallback errors_callback) {
// There are always 2 barriers, one for the normal OS Hook call and one for
// Synchronize().
int num_barriers = 2;
auto barrier_callback_for_synchronize = base::BarrierCallback<OsHooksErrors>(
num_barriers,
base::BindOnce(
[](AnyOsHooksErrorCallback callback,
std::vector<OsHooksErrors> combined_errors) {
std::move(callback).Run(
GetFinalErrorBitsetFromCollection(combined_errors));
},
std::move(errors_callback)));
return barrier_callback_for_synchronize;
}
void OsIntegrationManager::SetSubsystems(WebAppSyncBridge* sync_bridge,
WebAppRegistrar* registrar,
WebAppUiManager* ui_manager,
WebAppIconManager* icon_manager) {
CHECK(!first_synchronize_called_);
// TODO(estade): fetch the registrar from `sync_bridge` instead of passing
// both as arguments.
registrar_ = registrar;
ui_manager_ = ui_manager;
sync_bridge_ = sync_bridge;
file_handler_manager_->SetSubsystems(sync_bridge);
shortcut_manager_->SetSubsystems(icon_manager, registrar);
if (protocol_handler_manager_)
protocol_handler_manager_->SetSubsystems(registrar);
if (url_handler_manager_)
url_handler_manager_->SetSubsystems(registrar);
sub_managers_.clear();
auto shortcut_sub_manager = std::make_unique<ShortcutSubManager>(
*profile_, *icon_manager, *registrar);
auto file_handling_sub_manager = std::make_unique<FileHandlingSubManager>(
profile_->GetPath(), *registrar, *sync_bridge);
auto protocol_handling_sub_manager =
std::make_unique<ProtocolHandlingSubManager>(profile_->GetPath(),
*registrar);
auto shortcut_menu_handling_sub_manager =
std::make_unique<ShortcutMenuHandlingSubManager>(
profile_->GetPath(), *icon_manager, *registrar);
auto run_on_os_login_sub_manager = std::make_unique<RunOnOsLoginSubManager>(
*profile_, *registrar, *sync_bridge, *icon_manager);
auto uninstallation_via_os_settings_sub_manager =
std::make_unique<UninstallationViaOsSettingsSubManager>(
profile_->GetPath(), *registrar);
sub_managers_.push_back(std::move(shortcut_sub_manager));
sub_managers_.push_back(std::move(file_handling_sub_manager));
sub_managers_.push_back(std::move(protocol_handling_sub_manager));
sub_managers_.push_back(std::move(shortcut_menu_handling_sub_manager));
sub_managers_.push_back(std::move(run_on_os_login_sub_manager));
sub_managers_.push_back(
std::move(uninstallation_via_os_settings_sub_manager));
set_subsystems_called_ = true;
}
void OsIntegrationManager::Start() {
CHECK(registrar_);
CHECK(file_handler_manager_);
registrar_observation_.Observe(registrar_.get());
shortcut_manager_->Start();
file_handler_manager_->Start();
if (protocol_handler_manager_)
protocol_handler_manager_->Start();
}
void OsIntegrationManager::Synchronize(
const AppId& app_id,
base::OnceClosure callback,
absl::optional<SynchronizeOsOptions> options) {
first_synchronize_called_ = true;
// This is usually called whenever the app is missing in the web app registry,
// to clean up left over OS integration states.
if (options.has_value() && options.value().force_unregister_on_app_missing) {
ForceUnregisterOsIntegrationOnSubManager(app_id, /*index=*/0,
std::move(callback));
return;
}
// If the app does not exist in the DB and an unregistration is required, it
// should have been done in the past Synchronize call.
CHECK(registrar_->GetAppById(app_id))
<< "Can't perform OS integration without the app existing in the "
"registrar. If the use-case requires an app to not be installed, "
"consider setting the force_unregister_on_app_missing flag inside "
"SynchronizeOsOptions";
CHECK(set_subsystems_called_);
if (!AreOsIntegrationSubManagersEnabled()) {
std::move(callback).Run();
return;
}
if (sub_managers_.empty()) {
std::move(callback).Run();
return;
}
std::unique_ptr<proto::WebAppOsIntegrationState> desired_states =
std::make_unique<proto::WebAppOsIntegrationState>();
proto::WebAppOsIntegrationState* desired_states_ptr = desired_states.get();
// Note: Sometimes the execute step is a no-op based on feature flags or if os
// integration is disabled for testing. This logic is in the
// StartSubManagerExecutionIfRequired method.
base::RepeatingClosure configure_barrier;
configure_barrier = base::BarrierClosure(
sub_managers_.size(),
base::BindOnce(&OsIntegrationManager::StartSubManagerExecutionIfRequired,
weak_ptr_factory_.GetWeakPtr(), app_id, options,
std::move(desired_states), std::move(callback)));
for (const auto& sub_manager : sub_managers_) {
// This dereference is safe because the barrier closure guarantees that it
// will not be called until `configure_barrier` is called from each sub-
// manager.
sub_manager->Configure(app_id, *desired_states_ptr, configure_barrier);
}
}
void OsIntegrationManager::InstallOsHooks(
const AppId& app_id,
InstallOsHooksCallback callback,
std::unique_ptr<WebAppInstallInfo> web_app_info,
InstallOsHooksOptions options) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (g_suppress_os_hooks_for_testing_ || AreSubManagersExecuteEnabled()) {
OsHooksErrors os_hooks_errors;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), os_hooks_errors));
return;
}
MacAppShimOnAppInstalledForProfile(app_id);
OsHooksErrors os_hooks_errors;
scoped_refptr<OsHooksBarrier> barrier = base::MakeRefCounted<OsHooksBarrier>(
os_hooks_errors, std::move(callback));
CHECK(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);
#if BUILDFLAG(IS_MAC)
// This has to happen before creating shortcuts on Mac because the shortcut
// creation step uses the file type associations which are marked for enabling
// by `RegisterFileHandlers()`.
if (options.os_hooks[OsHookType::kFileHandlers]) {
RegisterFileHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kFileHandlers));
}
#endif
// 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, options.reason,
std::move(shortcuts_callback));
} else {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(shortcuts_callback),
/*shortcuts_created=*/false));
}
}
void OsIntegrationManager::UninstallAllOsHooks(
const AppId& app_id,
UninstallOsHooksCallback callback) {
OsHooksOptions os_hooks;
os_hooks.set();
UninstallOsHooks(app_id, os_hooks, std::move(callback));
}
void OsIntegrationManager::UninstallOsHooks(const AppId& app_id,
const OsHooksOptions& os_hooks,
UninstallOsHooksCallback callback) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (g_suppress_os_hooks_for_testing_ || AreSubManagersExecuteEnabled()) {
OsHooksErrors os_hooks_errors;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), os_hooks_errors));
return;
}
OsHooksErrors os_hooks_errors;
scoped_refptr<OsHooksBarrier> barrier = base::MakeRefCounted<OsHooksBarrier>(
os_hooks_errors, std::move(callback));
if (os_hooks[OsHookType::kShortcutsMenu]) {
bool success = UnregisterShortcutsMenu(
app_id,
barrier->CreateBarrierCallbackForType(OsHookType::kShortcutsMenu));
if (!success)
barrier->OnError(OsHookType::kShortcutsMenu);
}
if (os_hooks[OsHookType::kRunOnOsLogin] &&
base::FeatureList::IsEnabled(features::kDesktopPWAsRunOnOsLogin)) {
UnregisterRunOnOsLogin(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kRunOnOsLogin));
}
if (os_hooks[OsHookType::kShortcuts]) {
std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfo(app_id);
base::FilePath shortcut_data_dir =
internals::GetShortcutDataDir(*shortcut_info);
DeleteShortcuts(
app_id, shortcut_data_dir, std::move(shortcut_info),
barrier->CreateBarrierCallbackForType(OsHookType::kShortcuts));
}
// unregistration and record errors during unregistration.
if (os_hooks[OsHookType::kFileHandlers]) {
UnregisterFileHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kFileHandlers));
}
if (os_hooks[OsHookType::kProtocolHandlers]) {
UnregisterProtocolHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kProtocolHandlers));
}
if (os_hooks[OsHookType::kUrlHandlers])
UnregisterUrlHandlers(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,
FileHandlerUpdateAction file_handlers_need_os_update,
const WebAppInstallInfo& web_app_info,
UpdateOsHooksCallback callback) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (g_suppress_os_hooks_for_testing_ || AreSubManagersExecuteEnabled()) {
OsHooksErrors os_hooks_errors;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), os_hooks_errors));
return;
}
OsHooksErrors os_hooks_errors;
scoped_refptr<OsHooksBarrier> barrier = base::MakeRefCounted<OsHooksBarrier>(
os_hooks_errors, std::move(callback));
UpdateFileHandlers(app_id, file_handlers_need_os_update,
base::BindOnce(barrier->CreateBarrierCallbackForType(
OsHookType::kFileHandlers)));
UpdateShortcuts(app_id, old_name,
base::BindOnce(barrier->CreateBarrierCallbackForType(
OsHookType::kShortcuts)));
UpdateShortcutsMenu(app_id, web_app_info,
base::BindOnce(barrier->CreateBarrierCallbackForType(
OsHookType::kShortcutsMenu)));
UpdateUrlHandlers(
app_id,
base::BindOnce(
[](ResultCallback callback, bool success) {
std::move(callback).Run(success ? Result::kOk : Result::kError);
},
barrier->CreateBarrierCallbackForType(OsHookType::kUrlHandlers)));
UpdateProtocolHandlers(app_id, /*force_shortcut_updates_if_needed=*/false,
base::BindOnce(barrier->CreateBarrierCallbackForType(
OsHookType::kProtocolHandlers),
Result::kOk));
}
void OsIntegrationManager::GetAppExistingShortCutLocation(
ShortcutLocationCallback callback,
std::unique_ptr<ShortcutInfo> shortcut_info) {
CHECK(shortcut_manager_);
shortcut_manager_->GetAppExistingShortCutLocation(std::move(callback),
std::move(shortcut_info));
}
void OsIntegrationManager::GetShortcutInfoForApp(
const AppId& app_id,
WebAppShortcutManager::GetShortcutInfoCallback callback) {
CHECK(shortcut_manager_);
return shortcut_manager_->GetShortcutInfoForApp(app_id, std::move(callback));
}
bool OsIntegrationManager::IsFileHandlingAPIAvailable(const AppId& app_id) {
return true;
}
const apps::FileHandlers* OsIntegrationManager::GetEnabledFileHandlers(
const AppId& app_id) const {
CHECK(file_handler_manager_);
return file_handler_manager_->GetEnabledFileHandlers(app_id);
}
absl::optional<GURL> OsIntegrationManager::TranslateProtocolUrl(
const AppId& app_id,
const GURL& protocol_url) {
if (!protocol_handler_manager_)
return absl::optional<GURL>();
return protocol_handler_manager_->TranslateProtocolUrl(app_id, protocol_url);
}
std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetAppProtocolHandlers(const AppId& app_id) {
if (!protocol_handler_manager_)
return std::vector<custom_handlers::ProtocolHandler>();
return protocol_handler_manager_->GetAppProtocolHandlers(app_id);
}
std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetAllowedHandlersForProtocol(
const std::string& protocol) {
if (!protocol_handler_manager_)
return std::vector<custom_handlers::ProtocolHandler>();
return protocol_handler_manager_->GetAllowedHandlersForProtocol(protocol);
}
std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetDisallowedHandlersForProtocol(
const std::string& protocol) {
if (!protocol_handler_manager_)
return std::vector<custom_handlers::ProtocolHandler>();
return protocol_handler_manager_->GetDisallowedHandlersForProtocol(protocol);
}
WebAppShortcutManager& OsIntegrationManager::shortcut_manager_for_testing() {
CHECK(shortcut_manager_);
return *shortcut_manager_;
}
UrlHandlerManager& OsIntegrationManager::url_handler_manager_for_testing() {
CHECK(url_handler_manager_);
return *url_handler_manager_;
}
WebAppProtocolHandlerManager&
OsIntegrationManager::protocol_handler_manager_for_testing() {
CHECK(protocol_handler_manager_);
return *protocol_handler_manager_;
}
FakeOsIntegrationManager* OsIntegrationManager::AsTestOsIntegrationManager() {
return nullptr;
}
void OsIntegrationManager::CreateShortcuts(const AppId& app_id,
bool add_to_desktop,
ShortcutCreationReason reason,
CreateShortcutsCallback callback) {
if (shortcut_manager_->CanCreateShortcuts()) {
shortcut_manager_->CreateShortcuts(app_id, add_to_desktop, reason,
std::move(callback));
} else {
std::move(callback).Run(false);
}
}
void OsIntegrationManager::RegisterFileHandlers(const AppId& app_id,
ResultCallback callback) {
CHECK(file_handler_manager_);
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.FileHandlersRegistration.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
file_handler_manager_->EnableAndRegisterOsFileHandlers(
app_id, std::move(metrics_callback));
}
void OsIntegrationManager::RegisterProtocolHandlers(const AppId& app_id,
ResultCallback callback) {
if (!protocol_handler_manager_) {
std::move(callback).Run(Result::kOk);
return;
}
protocol_handler_manager_->RegisterOsProtocolHandlers(app_id,
std::move(callback));
}
void OsIntegrationManager::RegisterUrlHandlers(const AppId& app_id,
ResultCallback callback) {
if (!url_handler_manager_) {
std::move(callback).Run(Result::kOk);
return;
}
url_handler_manager_->RegisterUrlHandlers(app_id, std::move(callback));
}
void OsIntegrationManager::RegisterShortcutsMenu(
const AppId& app_id,
const std::vector<WebAppShortcutsMenuItemInfo>& shortcuts_menu_item_infos,
const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps,
ResultCallback callback) {
if (!ShouldRegisterShortcutsMenuWithOs()) {
std::move(callback).Run(Result::kOk);
return;
}
// Exit early if shortcuts_menu_item_infos are not populated.
if (shortcuts_menu_item_infos.size() < 1) {
std::move(callback).Run(Result::kOk);
return;
}
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.ShortcutsMenuRegistration.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
CHECK(shortcut_manager_);
shortcut_manager_->RegisterShortcutsMenuWithOs(
app_id, shortcuts_menu_item_infos, shortcuts_menu_icon_bitmaps,
std::move(metrics_callback));
}
void OsIntegrationManager::ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
const AppId& app_id,
ResultCallback callback) {
if (!ShouldRegisterShortcutsMenuWithOs()) {
std::move(callback).Run(Result::kOk);
return;
}
std::vector<WebAppShortcutsMenuItemInfo> shortcuts_menu_item_infos =
registrar_->GetAppShortcutsMenuItemInfos(app_id);
// Exit early if shortcuts_menu_item_infos are not populated.
if (shortcuts_menu_item_infos.size() < 1) {
std::move(callback).Run(Result::kOk);
return;
}
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.ShortcutsMenuRegistration.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
shortcut_manager_->ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
app_id, shortcuts_menu_item_infos, std::move(metrics_callback));
}
void OsIntegrationManager::RegisterRunOnOsLogin(const AppId& app_id,
ResultCallback callback) {
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.RunOnOsLogin.Registration.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
GetShortcutInfoForApp(
app_id,
base::BindOnce(
&OsIntegrationManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin,
weak_ptr_factory_.GetWeakPtr(), std::move(metrics_callback)));
}
void OsIntegrationManager::MacAppShimOnAppInstalledForProfile(
const AppId& app_id) {
#if BUILDFLAG(IS_MAC)
AppShimRegistry::Get()->OnAppInstalledForProfile(app_id, profile_->GetPath());
#endif
}
void OsIntegrationManager::AddAppToQuickLaunchBar(const AppId& app_id) {
CHECK(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_->GetPath());
}
}
bool OsIntegrationManager::UnregisterShortcutsMenu(const AppId& app_id,
ResultCallback callback) {
if (!ShouldRegisterShortcutsMenuWithOs()) {
std::move(callback).Run(Result::kOk);
return true;
}
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.ShortcutsMenuUnregistered.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
return UnregisterShortcutsMenuWithOs(app_id, profile_->GetPath(),
std::move(metrics_callback));
}
void OsIntegrationManager::UnregisterRunOnOsLogin(const AppId& app_id,
ResultCallback callback) {
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.RunOnOsLogin.Unregistration.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
ScheduleUnregisterRunOnOsLogin(
sync_bridge_, app_id, profile_->GetPath(),
base::UTF8ToUTF16(registrar_->GetAppShortName(app_id)),
std::move(metrics_callback));
}
void OsIntegrationManager::DeleteShortcuts(
const AppId& app_id,
const base::FilePath& shortcuts_data_dir,
std::unique_ptr<ShortcutInfo> shortcut_info,
ResultCallback 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(Result::kOk);
}
}
void OsIntegrationManager::UnregisterFileHandlers(const AppId& app_id,
ResultCallback callback) {
CHECK(file_handler_manager_);
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.FileHandlersUnregistration.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
file_handler_manager_->DisableAndUnregisterOsFileHandlers(
app_id, std::move(metrics_callback));
}
void OsIntegrationManager::UnregisterProtocolHandlers(const AppId& app_id,
ResultCallback callback) {
if (!protocol_handler_manager_) {
std::move(callback).Run(Result::kOk);
return;
}
protocol_handler_manager_->UnregisterOsProtocolHandlers(app_id,
std::move(callback));
}
void OsIntegrationManager::UnregisterUrlHandlers(const AppId& app_id) {
if (!url_handler_manager_)
return;
url_handler_manager_->UnregisterUrlHandlers(app_id);
}
void OsIntegrationManager::UnregisterWebAppOsUninstallation(
const AppId& app_id) {
if (ShouldRegisterUninstallationViaOsSettingsWithOs()) {
UnregisterUninstallationViaOsSettingsWithOs(app_id, profile_->GetPath());
}
}
void OsIntegrationManager::UpdateShortcuts(const AppId& app_id,
base::StringPiece old_name,
ResultCallback callback) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (AreSubManagersExecuteEnabled()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), Result::kOk));
return;
}
CHECK(shortcut_manager_);
if (!shortcut_manager_->CanCreateShortcuts()) {
std::move(callback).Run(Result::kOk);
return;
}
ResultCallback metrics_callback =
base::BindOnce([](Result result) {
base::UmaHistogramBoolean("WebApp.Shortcuts.Update.Result",
(result == Result::kOk));
return result;
}).Then(std::move(callback));
shortcut_manager_->UpdateShortcuts(app_id, old_name,
std::move(metrics_callback));
}
void OsIntegrationManager::UpdateShortcutsMenu(
const AppId& app_id,
const WebAppInstallInfo& web_app_info,
ResultCallback callback) {
if (web_app_info.shortcuts_menu_item_infos.empty()) {
UnregisterShortcutsMenu(app_id, std::move(callback));
} else {
RegisterShortcutsMenu(app_id, web_app_info.shortcuts_menu_item_infos,
web_app_info.shortcuts_menu_icon_bitmaps,
std::move(callback));
}
}
void OsIntegrationManager::UpdateUrlHandlers(
const AppId& app_id,
base::OnceCallback<void(bool success)> callback) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (AreSubManagersExecuteEnabled()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true));
return;
}
if (!url_handler_manager_)
return;
url_handler_manager_->UpdateUrlHandlers(app_id, std::move(callback));
}
void OsIntegrationManager::UpdateFileHandlers(
const AppId& app_id,
FileHandlerUpdateAction file_handlers_need_os_update,
ResultCallback finished_callback) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (AreSubManagersExecuteEnabled()) {
// Due to the way UpdateFileHandlerCommand is currently written, this needs
// to be synchronously called on Mac.
#if BUILDFLAG(IS_MAC)
std::move(finished_callback).Run(Result::kOk);
return;
#else
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(finished_callback), Result::kOk));
return;
#endif
}
if (file_handlers_need_os_update == FileHandlerUpdateAction::kNoUpdate) {
std::move(finished_callback).Run(Result::kOk);
return;
}
ResultCallback callback_after_removal;
if (file_handlers_need_os_update == FileHandlerUpdateAction::kUpdate) {
callback_after_removal = base::BindOnce(
[](base::WeakPtr<OsIntegrationManager> os_integration_manager,
const AppId& app_id, ResultCallback finished_callback,
Result result) {
if (!os_integration_manager) {
std::move(finished_callback).Run(Result::kError);
return;
}
os_integration_manager->RegisterFileHandlers(
app_id, std::move(finished_callback));
},
weak_ptr_factory_.GetWeakPtr(), app_id, std::move(finished_callback));
} else {
CHECK_EQ(file_handlers_need_os_update, FileHandlerUpdateAction::kRemove);
callback_after_removal = std::move(finished_callback);
}
// Update file handlers via complete uninstallation, then potential
// reinstallation.
UnregisterFileHandlers(app_id, std::move(callback_after_removal));
}
void OsIntegrationManager::UpdateProtocolHandlers(
const AppId& app_id,
bool force_shortcut_updates_if_needed,
base::OnceClosure callback) {
// If the "Execute" step is enabled for sub-managers, then the 'old' os
// integration path needs to be turned off so that os integration doesn't get
// done twice.
if (AreSubManagersExecuteEnabled()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(callback));
return;
}
if (!protocol_handler_manager_) {
std::move(callback).Run();
return;
}
auto shortcuts_callback = base::BindOnce(
&OsIntegrationManager::OnShortcutsUpdatedForProtocolHandlers,
weak_ptr_factory_.GetWeakPtr(), app_id, std::move(callback));
#if !BUILDFLAG(IS_WIN)
// Windows handles protocol registration through the registry. For other
// OS's we also need to regenerate the shortcut file before we call into
// the OS. Since `UpdateProtocolHandlers` function is also called in
// `UpdateOSHooks`, which also recreates the shortcuts, only do it if
// required.
if (force_shortcut_updates_if_needed) {
UpdateShortcuts(app_id, "",
base::IgnoreArgs<Result>(std::move(shortcuts_callback)));
return;
}
#endif
std::move(shortcuts_callback).Run();
}
void OsIntegrationManager::OnShortcutsUpdatedForProtocolHandlers(
const AppId& app_id,
base::OnceClosure update_finished_callback) {
// Update protocol handlers via complete uninstallation, then reinstallation.
ResultCallback unregister_callback = base::BindOnce(
[](base::WeakPtr<OsIntegrationManager> os_integration_manager,
const AppId& app_id, base::OnceClosure update_finished_callback,
Result result) {
// Re-register protocol handlers regardless of `result`.
// TODO(https://crbug.com/1250728): Report a UMA metric when
// unregistering fails, either here, or at the point of failure. This
// might also mean we can remove `result`.
if (!os_integration_manager) {
std::move(update_finished_callback).Run();
return;
}
os_integration_manager->RegisterProtocolHandlers(
app_id,
base::BindOnce(
[](base::OnceClosure update_finished_callback, Result result) {
// TODO(https://crbug.com/1250728): Report
// |result| in an UMA metric.
std::move(update_finished_callback).Run();
},
std::move(update_finished_callback)));
},
weak_ptr_factory_.GetWeakPtr(), app_id,
std::move(update_finished_callback));
UnregisterProtocolHandlers(app_id, std::move(unregister_callback));
}
void OsIntegrationManager::OnWebAppProfileWillBeDeleted(const AppId& app_id) {
UninstallAllOsHooks(app_id, base::DoNothing());
}
void OsIntegrationManager::OnAppRegistrarDestroyed() {
registrar_observation_.Reset();
}
std::unique_ptr<ShortcutInfo> OsIntegrationManager::BuildShortcutInfo(
const AppId& app_id) {
CHECK(shortcut_manager_);
return shortcut_manager_->BuildShortcutInfo(app_id);
}
void OsIntegrationManager::StartSubManagerExecutionIfRequired(
const AppId& app_id,
absl::optional<SynchronizeOsOptions> options,
std::unique_ptr<proto::WebAppOsIntegrationState> desired_states,
base::OnceClosure on_all_execution_done) {
// This can never be a use-case where we execute OS integration registration/
// unregistration but do not update the WebAppOsIntegrationState proto in the
// web_app DB.
CHECK(AreOsIntegrationSubManagersEnabled());
// The "execute" step is skipped in the following cases:
// 1. The app is no longer in the registrar. The whole synchronize process is
// stopped here.
// 2. The `g_suppress_os_hooks_for_testing_` flag is set.
// 3. Execution has been disabled by the feature parameters (see
// `AreSubManagersExecuteEnabled()`).
const WebApp* web_app = registrar_->GetAppById(app_id);
if (!web_app) {
std::move(on_all_execution_done).Run();
return;
}
proto::WebAppOsIntegrationState* desired_states_ptr = desired_states.get();
auto write_state_to_db = base::BindOnce(
&OsIntegrationManager::WriteStateToDB, weak_ptr_factory_.GetWeakPtr(),
app_id, std::move(desired_states), std::move(on_all_execution_done));
if (g_suppress_os_hooks_for_testing_ || !AreSubManagersExecuteEnabled()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(write_state_to_db));
return;
}
ExecuteNextSubmanager(app_id, options, desired_states_ptr,
web_app->current_os_integration_states(), /*index=*/0,
std::move(write_state_to_db));
}
void OsIntegrationManager::ExecuteNextSubmanager(
const AppId& app_id,
absl::optional<SynchronizeOsOptions> options,
proto::WebAppOsIntegrationState* desired_state,
const proto::WebAppOsIntegrationState current_state,
size_t index,
base::OnceClosure on_all_execution_done_db_write) {
CHECK(index < sub_managers_.size());
base::OnceClosure next_callback = base::OnceClosure();
if (index == sub_managers_.size() - 1) {
next_callback = std::move(on_all_execution_done_db_write);
} else {
next_callback = base::BindOnce(
&OsIntegrationManager::ExecuteNextSubmanager,
weak_ptr_factory_.GetWeakPtr(), app_id, options, desired_state,
current_state, index + 1, std::move(on_all_execution_done_db_write));
}
sub_managers_[index]->Execute(app_id, options, *desired_state, current_state,
std::move(next_callback));
}
void OsIntegrationManager::WriteStateToDB(
const AppId& app_id,
std::unique_ptr<proto::WebAppOsIntegrationState> desired_states,
base::OnceClosure callback) {
// Exit early if the app is scheduled to be uninstalled or is already
// uninstalled.
const WebApp* existing_app = registrar_->GetAppById(app_id);
if (!existing_app || existing_app->is_uninstalling()) {
std::move(callback).Run();
return;
}
{
ScopedRegistryUpdate update(sync_bridge_);
WebApp* web_app = update->UpdateApp(app_id);
CHECK(web_app);
web_app->SetCurrentOsIntegrationStates(*desired_states.get());
}
std::move(callback).Run();
}
void OsIntegrationManager::ForceUnregisterOsIntegrationOnSubManager(
const AppId& app_id,
size_t index,
base::OnceClosure final_callback) {
CHECK(index < sub_managers_.size());
base::OnceClosure next_callback = base::OnceClosure();
if (index == sub_managers_.size() - 1) {
next_callback = std::move(final_callback);
} else {
next_callback = base::BindOnce(
&OsIntegrationManager::ForceUnregisterOsIntegrationOnSubManager,
weak_ptr_factory_.GetWeakPtr(), app_id, index + 1,
std::move(final_callback));
}
sub_managers_[index]->ForceUnregister(app_id, std::move(next_callback));
}
void OsIntegrationManager::OnShortcutsCreated(
const AppId& app_id,
std::unique_ptr<WebAppInstallInfo> web_app_info,
InstallOsHooksOptions options,
scoped_refptr<OsHooksBarrier> barrier,
bool shortcuts_created) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(barrier);
if (registrar_ && !registrar_->GetAppById(app_id)) {
return;
}
bool shortcut_creation_failure =
!shortcuts_created && options.os_hooks[OsHookType::kShortcuts];
if (shortcut_creation_failure)
barrier->OnError(OsHookType::kShortcuts);
#if !BUILDFLAG(IS_MAC)
// This step happens before shortcut creation on Mac.
if (options.os_hooks[OsHookType::kFileHandlers]) {
RegisterFileHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kFileHandlers));
}
#endif
if (options.os_hooks[OsHookType::kProtocolHandlers]) {
RegisterProtocolHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kProtocolHandlers));
}
if (options.os_hooks[OsHookType::kUrlHandlers]) {
RegisterUrlHandlers(app_id, barrier->CreateBarrierCallbackForType(
OsHookType::kUrlHandlers));
}
if (options.os_hooks[OsHookType::kShortcuts] &&
options.add_to_quick_launch_bar) {
AddAppToQuickLaunchBar(app_id);
}
if (shortcuts_created && options.os_hooks[OsHookType::kShortcutsMenu]) {
if (web_app_info) {
RegisterShortcutsMenu(
app_id, web_app_info->shortcuts_menu_item_infos,
web_app_info->shortcuts_menu_icon_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/1091964): 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]) {
RegisterWebAppOsUninstallation(
app_id, registrar_ ? registrar_->GetAppShortName(app_id) : "");
}
}
void OsIntegrationManager::OnShortcutsDeleted(const AppId& app_id,
ResultCallback callback,
Result result) {
#if BUILDFLAG(IS_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(result);
#endif
}
void OsIntegrationManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin(
ResultCallback callback,
std::unique_ptr<ShortcutInfo> info) {
ScheduleRegisterRunOnOsLogin(sync_bridge_, std::move(info),
std::move(callback));
}
} // namespace web_app