blob: 49c946703aa4e2adc834c27fc3e325095963f8e3 [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 <optional>
#include <string_view>
#include <utility>
#include <vector>
#include "base/atomic_ref_count.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_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/concurrent_closures.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.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/os_integration_test_override.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_install_info.h"
#include "chrome/browser/web_applications/web_app_profile_deletion_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_ui_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/gfx/image/image_skia_rep_default.h"
#if BUILDFLAG(IS_MAC)
#include "base/system/sys_info.h"
#include "chrome/common/mac/app_mode_common.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/web_applications/os_integration/mac/app_shim_registry.h"
#endif
namespace web_app {
namespace {
base::AtomicRefCount& GetSuppressCount() {
static base::AtomicRefCount g_ref_count;
return g_ref_count;
}
#if BUILDFLAG(IS_MAC)
// This version number is stored in local prefs to check whether app shortcuts
// need to be recreated. This might happen when we change various aspects of app
// shortcuts like command-line flags or associated icons, binaries, etc.
const int kCurrentAppShortcutsVersion = APP_SHIM_VERSION_NUMBER;
// The architecture that was last used to create app shortcuts for this user
// directory.
std::string CurrentAppShortcutsArch() {
return base::SysInfo::OperatingSystemArchitecture();
}
#else
std::string CurrentAppShortcutsArch() {
return "";
}
#if BUILDFLAG(IS_WIN)
const int kCurrentAppShortcutsVersion = 1;
#else
// Non-mac/win platforms do not update shortcuts.
const int kCurrentAppShortcutsVersion = 0;
#endif // BUILDFLAG(IS_WIN)
#endif // BUILDFLAG(IS_MAC)
// Delay in seconds before running UpdateShortcutsForAllApps.
const int kUpdateShortcutsForAllAppsDelay = 10;
OsIntegrationManager::UpdateShortcutsForAllAppsCallback&
GetUpdateShortcutsForAllAppsCallback() {
static base::NoDestructor<
OsIntegrationManager::UpdateShortcutsForAllAppsCallback>
callback;
return *callback;
}
} // namespace
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)
GetSuppressCount().Increment();
#endif
}
OsIntegrationManager::ScopedSuppressForTesting::~ScopedSuppressForTesting() {
#if !BUILDFLAG(IS_CHROMEOS)
CHECK(!GetSuppressCount().IsZero());
GetSuppressCount().Decrement();
#endif
}
// static
bool OsIntegrationManager::AreOsHooksSuppressedForTesting() {
return !GetSuppressCount().IsZero();
}
// static
void OsIntegrationManager::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
// Indicates whether app shortcuts have been created.
registry->RegisterIntegerPref(prefs::kAppShortcutsVersion,
kCurrentAppShortcutsVersion);
registry->RegisterStringPref(prefs::kAppShortcutsArch,
CurrentAppShortcutsArch());
}
// static
void OsIntegrationManager::SetUpdateShortcutsForAllAppsCallback(
UpdateShortcutsForAllAppsCallback callback) {
GetUpdateShortcutsForAllAppsCallback() = std::move(callback);
}
// static
base::OnceClosure&
OsIntegrationManager::OnSetCurrentAppShortcutsVersionCallbackForTesting() {
static base::NoDestructor<base::OnceClosure> callback;
return *callback;
}
OsIntegrationManager::OsIntegrationManager(
Profile* profile,
std::unique_ptr<WebAppFileHandlerManager> file_handler_manager,
std::unique_ptr<WebAppProtocolHandlerManager> protocol_handler_manager)
: profile_(profile),
file_handler_manager_(std::move(file_handler_manager)),
protocol_handler_manager_(std::move(protocol_handler_manager)) {}
OsIntegrationManager::~OsIntegrationManager() = default;
void OsIntegrationManager::SetProvider(base::PassKey<WebAppProvider>,
WebAppProvider& provider) {
CHECK(!first_synchronize_called_);
provider_ = &provider;
base::PassKey<OsIntegrationManager> pass_key;
file_handler_manager_->SetProvider(pass_key, provider);
if (protocol_handler_manager_)
protocol_handler_manager_->SetProvider(pass_key, provider);
sub_managers_.clear();
sub_managers_.push_back(
std::make_unique<ShortcutSubManager>(*profile_, provider));
sub_managers_.push_back(
std::make_unique<FileHandlingSubManager>(profile_->GetPath(), provider));
sub_managers_.push_back(std::make_unique<ProtocolHandlingSubManager>(
profile_->GetPath(), provider));
sub_managers_.push_back(std::make_unique<ShortcutMenuHandlingSubManager>(
profile_->GetPath(), provider));
sub_managers_.push_back(
std::make_unique<RunOnOsLoginSubManager>(*profile_, provider));
sub_managers_.push_back(
std::make_unique<UninstallationViaOsSettingsSubManager>(
profile_->GetPath(), provider));
set_provider_called_ = true;
}
void OsIntegrationManager::Start() {
CHECK(provider_);
CHECK(file_handler_manager_);
file_handler_manager_->Start();
if (protocol_handler_manager_) {
protocol_handler_manager_->Start();
}
UpdateShortcutsForAllAppsIfNeeded();
}
void OsIntegrationManager::Synchronize(
const webapps::AppId& app_id,
base::OnceClosure callback,
std::optional<SynchronizeOsOptions> options) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
first_synchronize_called_ = true;
// This is usually called to clean up OS integration states on the OS,
// regardless of whether there are apps existing in the app registry or not.
if (options.has_value() && options.value().force_unregister_os_integration) {
CHECK_OS_INTEGRATION_ALLOWED();
ForceUnregisterOsIntegrationOnSubManager(
app_id, /*index=*/0,
std::move(callback).Then(
base::BindOnce(force_unregister_callback_for_testing_, app_id)));
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(provider_->registrar_unsafe().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_os_integration flag inside "
"SynchronizeOsOptions";
CHECK(set_provider_called_);
if (sub_managers_.empty()) {
std::move(callback).Run();
return;
}
#if !BUILDFLAG(IS_CHROMEOS)
if (KeepAliveRegistry::GetInstance()->IsShuttingDown()) {
LOG(ERROR)
<< "Can't perform OS integration while the browser is shutting down.";
std::move(callback).Run();
return;
}
std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive =
std::make_unique<ScopedProfileKeepAlive>(
profile_, ProfileKeepAliveOrigin::kWebAppUpdate);
std::unique_ptr<ScopedKeepAlive> browser_keep_alive =
std::make_unique<ScopedKeepAlive>(KeepAliveOrigin::WEB_APP_INSTALL,
KeepAliveRestartOption::DISABLED);
auto end_keep_alive_then_run_callback =
base::OnceClosure(
base::DoNothingWithBoundArgs(std::move(profile_keep_alive),
std::move(browser_keep_alive)))
.Then(std::move(callback));
#else
// TODO(crbug.com/394384898): Do this for ChromeOS too once it
// doesn't break browser tests using InstallSystemAppsForTesting.
auto end_keep_alive_then_run_callback = std::move(callback);
#endif
std::unique_ptr<proto::os_state::WebAppOsIntegration> desired_states =
std::make_unique<proto::os_state::WebAppOsIntegration>();
proto::os_state::WebAppOsIntegration* 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(end_keep_alive_then_run_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::GetAppExistingShortCutLocation(
ShortcutLocationCallback callback,
std::unique_ptr<ShortcutInfo> shortcut_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Ownership of `shortcut_info` moves to the Reply, which is guaranteed to
// outlive the const reference.
const ShortcutInfo& shortcut_info_ref = *shortcut_info;
internals::GetShortcutIOTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&internals::GetAppExistingShortCutLocationImpl,
std::cref(shortcut_info_ref)),
base::BindOnce(
[](ShortcutLocationCallback callback, ShortcutLocations locations) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::move(callback).Run(locations);
},
std::move(callback).Then(base::OnceClosure(
base::DoNothingWithBoundArgs(std::move(shortcut_info))))));
}
void OsIntegrationManager::GetShortcutInfoForAppFromRegistrar(
const webapps::AppId& app_id,
GetShortcutInfoCallback callback) {
const WebApp* app = provider_->registrar_unsafe().GetAppById(app_id);
// app could be nullptr if registry profile is being deleted or the app is not
// in the registry.
if (!app) {
std::move(callback).Run(nullptr);
return;
}
// Build a common intersection between desired and downloaded icons.
auto icon_sizes_in_px = base::STLSetIntersection<std::vector<SquareSizePx>>(
app->downloaded_icon_sizes(IconPurpose::ANY),
GetDesiredIconSizesForShortcut());
if (!icon_sizes_in_px.empty()) {
provider_->icon_manager().ReadTrustedIconsWithFallbackToManifestIcons(
app_id, icon_sizes_in_px, IconPurpose::ANY,
base::BindOnce(&OsIntegrationManager::OnIconsRead,
weak_ptr_factory_.GetWeakPtr(), app_id,
std::move(callback)));
return;
}
// If there is no single icon at the desired sizes, we will resize what we can
// get.
SquareSizePx desired_icon_size = GetDesiredIconSizesForShortcut().back();
provider_->icon_manager().ReadIconAndResize(
app_id, IconPurpose::ANY, desired_icon_size,
base::BindOnce(&OsIntegrationManager::OnIconsRead,
weak_ptr_factory_.GetWeakPtr(), app_id,
std::move(callback)));
}
bool OsIntegrationManager::IsFileHandlingAPIAvailable(
const webapps::AppId& app_id) {
return true;
}
const apps::FileHandlers* OsIntegrationManager::GetEnabledFileHandlers(
const webapps::AppId& app_id) const {
CHECK(file_handler_manager_);
return file_handler_manager_->GetEnabledFileHandlers(app_id);
}
std::optional<GURL> OsIntegrationManager::TranslateProtocolUrl(
const webapps::AppId& app_id,
const GURL& protocol_url) {
if (!protocol_handler_manager_)
return std::optional<GURL>();
return protocol_handler_manager_->TranslateProtocolUrl(app_id, protocol_url);
}
std::vector<custom_handlers::ProtocolHandler>
OsIntegrationManager::GetAppProtocolHandlers(
const webapps::AppId& app_id) const {
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);
}
WebAppProtocolHandlerManager&
OsIntegrationManager::protocol_handler_manager_for_testing() {
CHECK(protocol_handler_manager_);
return *protocol_handler_manager_;
}
FakeOsIntegrationManager* OsIntegrationManager::AsTestOsIntegrationManager() {
return nullptr;
}
void OsIntegrationManager::UnregisterOsIntegrationOnProfileMarkedForDeletion(
base::PassKey<WebAppProfileDeletionManager>,
const webapps::AppId& app_id) {
CHECK_OS_INTEGRATION_ALLOWED();
// This is used to keep the profile from being deleted while doing a
// ForceUnregister when profile deletion is started.
auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
profile_, ProfileKeepAliveOrigin::kOsIntegrationForceUnregistration);
ForceUnregisterOsIntegrationOnSubManager(
app_id, 0,
base::BindOnce(&OsIntegrationManager::SubManagersUnregistered,
weak_ptr_factory_.GetWeakPtr(), app_id,
std::move(profile_keep_alive)));
}
void OsIntegrationManager::SetForceUnregisterCalledForTesting(
base::RepeatingCallback<void(const webapps::AppId&)> on_force_unregister) {
force_unregister_callback_for_testing_ = on_force_unregister;
}
void OsIntegrationManager::StartSubManagerExecutionIfRequired(
const webapps::AppId& app_id,
std::optional<SynchronizeOsOptions> options,
std::unique_ptr<proto::os_state::WebAppOsIntegration> desired_states,
base::OnceClosure on_all_execution_done) {
// 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.
const WebApp* web_app = provider_->registrar_unsafe().GetAppById(app_id);
if (!web_app) {
std::move(on_all_execution_done).Run();
return;
}
proto::os_state::WebAppOsIntegration* 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 (AreOsHooksSuppressedForTesting()) {
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 webapps::AppId& app_id,
std::optional<SynchronizeOsOptions> options,
proto::os_state::WebAppOsIntegration* desired_state,
const proto::os_state::WebAppOsIntegration 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 webapps::AppId& app_id,
std::unique_ptr<proto::os_state::WebAppOsIntegration> desired_states,
base::OnceClosure callback) {
// Exit early if the app is already uninstalled. We still need to write the
// desired_states to the web_app DB during the uninstallation process since
// that helps make decisions on whether the uninstallation went successfully
// or not inside the RemoveWebAppJob.
const WebApp* existing_app = provider_->registrar_unsafe().GetAppById(app_id);
if (!existing_app) {
std::move(callback).Run();
return;
}
{
ScopedRegistryUpdate update = provider_->sync_bridge_unsafe().BeginUpdate();
WebApp* web_app = update->UpdateApp(app_id);
CHECK(web_app);
web_app->SetCurrentOsIntegrationStates(*desired_states.get());
}
std::move(callback).Run();
}
void OsIntegrationManager::SubManagersUnregistered(
const webapps::AppId& app_id,
std::unique_ptr<ScopedProfileKeepAlive> keep_alive) {
force_unregister_callback_for_testing_.Run(app_id);
keep_alive.reset();
}
void OsIntegrationManager::ForceUnregisterOsIntegrationOnSubManager(
const webapps::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::UpdateShortcutsForAllAppsIfNeeded() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Updating shortcuts writes to user home folders, which can not be done in
// tests without exploding disk space usage on the bots.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
return;
}
int last_version =
profile_->GetPrefs()->GetInteger(prefs::kAppShortcutsVersion);
std::string last_arch =
profile_->GetPrefs()->GetString(prefs::kAppShortcutsArch);
if (last_version == kCurrentAppShortcutsVersion &&
last_arch == CurrentAppShortcutsArch()) {
// This either means this is a profile where installed shortcuts already
// match the expected version and arch, or this could be a fresh profile.
// For the latter, make sure to actually store version and arch in prefs,
// as otherwise this code would always just read the defaults for these
// prefs, and not actually ever detect a version change.
SetCurrentAppShortcutsVersion();
return;
}
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OsIntegrationManager::UpdateShortcutsForAllAppsNow,
weak_ptr_factory_.GetWeakPtr()),
base::Seconds(kUpdateShortcutsForAllAppsDelay));
}
void OsIntegrationManager::UpdateShortcutsForAllAppsNow() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::ConcurrentClosures concurrent;
SynchronizeOsOptions options;
options.force_update_shortcuts = true;
for (const auto& app_id : provider_->registrar_unsafe().GetAppIds()) {
Synchronize(app_id, concurrent.CreateClosure(), options);
}
UpdateShortcutsForAllAppsCallback update_callback =
GetUpdateShortcutsForAllAppsCallback();
if (update_callback) {
update_callback.Run(profile_, concurrent.CreateClosure());
} else {
concurrent.CreateClosure().Run();
}
std::move(concurrent)
.Done(base::BindOnce(&OsIntegrationManager::SetCurrentAppShortcutsVersion,
weak_ptr_factory_.GetWeakPtr()));
}
void OsIntegrationManager::SetCurrentAppShortcutsVersion() {
profile_->GetPrefs()->SetInteger(prefs::kAppShortcutsVersion,
kCurrentAppShortcutsVersion);
profile_->GetPrefs()->SetString(prefs::kAppShortcutsArch,
CurrentAppShortcutsArch());
if (base::OnceClosure& callback =
OnSetCurrentAppShortcutsVersionCallbackForTesting()) {
std::move(callback).Run();
}
}
void OsIntegrationManager::OnIconsRead(
const webapps::AppId& app_id,
GetShortcutInfoCallback callback,
std::map<SquareSizePx, SkBitmap> icon_bitmaps) {
const WebApp* app = provider_->registrar_unsafe().GetAppById(app_id);
if (!app) {
std::move(callback).Run(nullptr);
return;
}
gfx::ImageFamily image_family;
for (auto& size_and_bitmap : icon_bitmaps) {
image_family.Add(gfx::ImageSkia(
gfx::ImageSkiaRep(size_and_bitmap.second, /*scale=*/0.0f)));
}
// If the image failed to load, use the standard application icon.
if (image_family.empty()) {
SquareSizePx icon_size_in_px = GetDesiredIconSizesForShortcut().back();
gfx::ImageSkia image_skia = CreateDefaultApplicationIcon(icon_size_in_px);
image_family.Add(gfx::Image(image_skia));
}
std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfoForWebApp(app);
shortcut_info->favicon = std::move(image_family);
std::move(callback).Run(std::move(shortcut_info));
}
// TODO(crbug.com/40250591): Merge into BuildShortcutInfoWithoutFavicon() for
// web_app_shortcut.cc.
std::unique_ptr<ShortcutInfo> OsIntegrationManager::BuildShortcutInfoForWebApp(
const WebApp* app) {
auto shortcut_info = std::make_unique<ShortcutInfo>();
shortcut_info->app_id = app->app_id();
shortcut_info->url = app->start_url();
shortcut_info->is_diy_app = app->is_diy_app();
shortcut_info->title = base::UTF8ToUTF16(
provider_->registrar_unsafe().GetAppShortName(app->app_id()));
shortcut_info->description = base::UTF8ToUTF16(
provider_->registrar_unsafe().GetAppDescription(app->app_id()));
shortcut_info->profile_path = profile_->GetPath();
shortcut_info->profile_name =
profile_->GetPrefs()->GetString(prefs::kProfileName);
shortcut_info->is_multi_profile = true;
if (const apps::FileHandlers* file_handlers =
file_handler_manager_->GetEnabledFileHandlers(app->app_id())) {
shortcut_info->file_handler_extensions =
GetFileExtensionsFromFileHandlers(*file_handlers);
shortcut_info->file_handler_mime_types =
GetMimeTypesFromFileHandlers(*file_handlers);
}
std::vector<apps::ProtocolHandlerInfo> protocol_handlers =
protocol_handler_manager_->GetAppProtocolHandlerInfos(app->app_id());
for (const auto& protocol_handler : protocol_handlers) {
if (!protocol_handler.protocol.empty()) {
shortcut_info->protocol_handlers.emplace(protocol_handler.protocol);
}
}
#if BUILDFLAG(IS_LINUX)
const std::vector<WebAppShortcutsMenuItemInfo>& shortcuts_menu_item_infos =
app->shortcuts_menu_item_infos();
DCHECK_LE(shortcuts_menu_item_infos.size(), kMaxApplicationDockMenuItems);
for (const auto& shortcuts_menu_item_info : shortcuts_menu_item_infos) {
if (!shortcuts_menu_item_info.name.empty() &&
!shortcuts_menu_item_info.url.is_empty()) {
// Generates ID from the name by replacing all characters that are not
// numbers, letters, or '-' with '-'.
std::string id = base::UTF16ToUTF8(shortcuts_menu_item_info.name);
RE2::GlobalReplace(&id, "[^a-zA-Z0-9\\-]", "-");
shortcut_info->actions.emplace(
id, base::UTF16ToUTF8(shortcuts_menu_item_info.name),
shortcuts_menu_item_info.url);
}
}
#endif // BUILDFLAG(IS_LINUX)
#if BUILDFLAG(IS_MAC)
shortcut_info->handlers_per_profile =
AppShimRegistry::Get()->GetHandlersForApp(app->app_id());
#endif
return shortcut_info;
}
} // namespace web_app