blob: 8eab2867db3a4985150bfc4e365267cab19872ca [file] [log] [blame]
// Copyright 2018 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/web_app_registrar.h"
#include <algorithm>
#include <bitset>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/enum_set.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/to_string.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/proto/proto_helpers.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_proto_package.pb.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar_observer.h"
#include "chrome/browser/web_applications/web_app_translation_manager.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/isolated_web_apps_policy.h"
#include "content/public/browser/storage_partition_config.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/web_applications/chromeos_web_app_experiments.h"
#include "chromeos/constants/chromeos_features.h"
#endif
namespace web_app {
namespace {
using InstallStateSet = base::EnumSet<proto::InstallState,
proto::InstallState_MIN,
proto::InstallState_MAX>;
// With Lacros, only system web apps are exposed using the Ash browser.
bool WebAppSourceSupported(const WebApp& web_app) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (IsWebAppsCrosapiEnabled() && !web_app.IsSystemApp()) {
return false;
}
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
if (web_app.IsSystemApp())
return false;
#endif
return true;
}
bool IsLinkCapturingDisabledByDefaultBasedOnFlagState() {
return features::kNavigationCapturingDefaultState.Get() ==
features::CapturingState::kDefaultOff ||
features::kNavigationCapturingDefaultState.Get() ==
features::CapturingState::kReimplDefaultOff;
}
} // namespace
WebAppRegistrar::WebAppRegistrar(Profile* profile) : profile_(profile) {}
WebAppRegistrar::~WebAppRegistrar() {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnAppRegistrarDestroyed();
}
}
bool WebAppRegistrar::IsLocallyInstalled(const GURL& start_url) const {
return IsLocallyInstalled(
GenerateAppId(/*manifest_id=*/std::nullopt, start_url));
}
blink::ParsedPermissionsPolicy WebAppRegistrar::GetPermissionsPolicy(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->permissions_policy()
: blink::ParsedPermissionsPolicy();
}
bool WebAppRegistrar::IsPlaceholderApp(
const webapps::AppId& app_id,
const WebAppManagement::Type source_type) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app)
return false;
const WebApp::ExternalConfigMap& config_map =
web_app->management_to_external_config_map();
auto it = config_map.find(source_type);
if (it == config_map.end()) {
return false;
}
// Only kiosk and policy sources currently have placeholder apps.
CHECK(!it->second.is_placeholder ||
(source_type == WebAppManagement::kPolicy ||
source_type == WebAppManagement::kKiosk));
return it->second.is_placeholder;
}
// TODO(crbug.com/40264854): Revert changes back to old code
// once the system starts enforcing a single install URL per
// app_id.
std::optional<webapps::AppId> WebAppRegistrar::LookupPlaceholderAppId(
const GURL& install_url,
const WebAppManagement::Type source_type) const {
for (const WebApp& web_app : GetApps()) {
const WebApp::ExternalConfigMap& config_map =
web_app.management_to_external_config_map();
auto it = config_map.find(source_type);
if (it == config_map.end()) {
continue;
}
if (base::Contains(it->second.install_urls, install_url) &&
it->second.is_placeholder) {
return web_app.app_id();
}
}
return std::nullopt;
}
void WebAppRegistrar::AddObserver(WebAppRegistrarObserver* observer) {
observers_.AddObserver(observer);
}
void WebAppRegistrar::RemoveObserver(WebAppRegistrarObserver* observer) {
observers_.RemoveObserver(observer);
}
void WebAppRegistrar::NotifyWebAppProtocolSettingsChanged() {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppProtocolSettingsChanged();
}
}
void WebAppRegistrar::NotifyWebAppFileHandlerApprovalStateChanged(
const webapps::AppId& app_id) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppFileHandlerApprovalStateChanged(app_id);
}
}
void WebAppRegistrar::NotifyWebAppsWillBeUpdatedFromSync(
const std::vector<const WebApp*>& new_apps_state) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppsWillBeUpdatedFromSync(new_apps_state);
}
}
void WebAppRegistrar::NotifyWebAppDisabledStateChanged(
const webapps::AppId& app_id,
bool is_disabled) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppDisabledStateChanged(app_id, is_disabled);
}
}
void WebAppRegistrar::NotifyWebAppsDisabledModeChanged() {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppsDisabledModeChanged();
}
}
void WebAppRegistrar::NotifyWebAppLastBadgingTimeChanged(
const webapps::AppId& app_id,
const base::Time& time) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppLastBadgingTimeChanged(app_id, time);
}
}
void WebAppRegistrar::NotifyWebAppLastLaunchTimeChanged(
const webapps::AppId& app_id,
const base::Time& time) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppLastLaunchTimeChanged(app_id, time);
}
}
void WebAppRegistrar::NotifyWebAppFirstInstallTimeChanged(
const webapps::AppId& app_id,
const base::Time& time) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppFirstInstallTimeChanged(app_id, time);
}
}
void WebAppRegistrar::NotifyWebAppUserDisplayModeChanged(
const webapps::AppId& app_id,
mojom::UserDisplayMode user_display_mode) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppUserDisplayModeChanged(app_id, user_display_mode);
}
}
void WebAppRegistrar::NotifyWebAppRunOnOsLoginModeChanged(
const webapps::AppId& app_id,
RunOnOsLoginMode run_on_os_login_mode) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppRunOnOsLoginModeChanged(app_id, run_on_os_login_mode);
}
}
void WebAppRegistrar::NotifyWebAppSettingsPolicyChanged() {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppSettingsPolicyChanged();
}
}
#if !BUILDFLAG(IS_CHROMEOS)
void WebAppRegistrar::NotifyWebAppUserLinkCapturingPreferencesChanged(
const webapps::AppId& app_id,
bool is_preferred) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnWebAppUserLinkCapturingPreferencesChanged(app_id, is_preferred);
}
}
#endif // !BUILDFLAG(IS_CHROMEOS)
base::flat_map<webapps::AppId, base::flat_set<GURL>>
WebAppRegistrar::GetExternallyInstalledApps(
ExternalInstallSource install_source) const {
base::flat_map<webapps::AppId, base::flat_set<GURL>> installed_apps;
WebAppManagement::Type management_source =
ConvertExternalInstallSourceToSource(install_source);
for (const WebApp& web_app : GetApps()) {
const WebApp::ExternalConfigMap& config_map =
web_app.management_to_external_config_map();
auto it = config_map.find(management_source);
if (it != config_map.end() && !it->second.install_urls.empty())
installed_apps[web_app.app_id()] = it->second.install_urls;
}
return installed_apps;
}
std::optional<webapps::AppId> WebAppRegistrar::LookupExternalAppId(
const GURL& install_url) const {
std::optional<webapps::AppId> app_id = LookUpAppIdByInstallUrl(install_url);
if (app_id.has_value())
return app_id;
return std::nullopt;
}
bool WebAppRegistrar::HasExternalApp(const webapps::AppId& app_id) const {
if (!IsInstalled(app_id))
return false;
const WebApp* web_app = GetAppById(app_id);
// If the external config map is filled, then the app was
// externally installed.
return web_app && web_app->management_to_external_config_map().size() > 0;
}
bool WebAppRegistrar::HasExternalAppWithInstallSource(
const webapps::AppId& app_id,
ExternalInstallSource install_source) const {
if (!IsInstalled(app_id))
return false;
const WebApp* web_app = GetAppById(app_id);
return web_app &&
base::Contains(web_app->management_to_external_config_map(),
ConvertExternalInstallSourceToSource(install_source));
}
GURL WebAppRegistrar::GetAppLaunchUrl(const webapps::AppId& app_id) const {
const GURL& start_url = GetAppStartUrl(app_id);
const std::string* launch_query_params = GetAppLaunchQueryParams(app_id);
if (!start_url.is_valid() || !launch_query_params)
return start_url;
GURL::Replacements replacements;
if (start_url.query_piece().empty()) {
replacements.SetQueryStr(*launch_query_params);
return start_url.ReplaceComponents(replacements);
}
if (start_url.query_piece().find(*launch_query_params) !=
std::string_view::npos) {
return start_url;
}
std::string query_params = start_url.query() + "&" + *launch_query_params;
replacements.SetQueryStr(query_params);
return start_url.ReplaceComponents(replacements);
}
GURL WebAppRegistrar::GetAppScope(const webapps::AppId& app_id) const {
std::optional<GURL> scope = GetAppScopeInternal(app_id);
if (scope)
return *scope;
return GetAppStartUrl(app_id).GetWithoutFilename();
}
int WebAppRegistrar::GetAppExtendedScopeScore(
const GURL& url,
const webapps::AppId& app_id) const {
if (!url.is_valid()) {
return 0;
}
int app_scope = GetUrlInAppScopeScore(url.spec(), app_id);
if (app_scope > 0) {
return app_scope;
}
const WebApp* app = GetAppById(app_id);
if (!app || app->validated_scope_extensions().empty()) {
return 0;
}
url::Origin origin = url::Origin::Create(url);
if (origin.opaque() || origin.scheme() != url::kHttpsScheme) {
return 0;
}
std::optional<std::string> origin_str;
for (const auto& scope_extension : GetValidatedScopeExtensions(app_id)) {
if (origin.IsSameOriginWith(scope_extension.origin)) {
return origin.host().size();
}
// Origins with wildcard e.g. *.foo are saved as https://foo.
// Ensure while matching that the origin ends with '.foo' and not 'foo'.
if (scope_extension.has_origin_wildcard) {
if (!origin_str.has_value()) {
origin_str = origin.Serialize();
}
if (base::EndsWith(origin_str.value(), scope_extension.origin.host(),
base::CompareCase::SENSITIVE) &&
origin_str.value().size() > scope_extension.origin.host().size() &&
origin_str.value()[origin_str.value().size() -
scope_extension.origin.host().size() - 1] == '.') {
return scope_extension.origin.host().size();
}
}
}
return 0;
}
bool WebAppRegistrar::IsUrlInAppScope(const GURL& url,
const webapps::AppId& app_id) const {
return GetUrlInAppScopeScore(url.spec(), app_id) > 0;
}
bool WebAppRegistrar::IsUrlInAppExtendedScope(
const GURL& url,
const webapps::AppId& app_id) const {
return GetAppExtendedScopeScore(url, app_id) > 0;
}
int WebAppRegistrar::GetUrlInAppScopeScore(const std::string& url_spec,
const webapps::AppId& app_id) const {
std::string app_scope = GetAppScope(app_id).spec();
// The app may have been uninstalled.
if (app_scope.empty())
return 0;
int score =
base::StartsWith(url_spec, app_scope, base::CompareCase::SENSITIVE)
? app_scope.size()
: 0;
#if BUILDFLAG(IS_CHROMEOS)
if (chromeos::features::IsUploadOfficeToCloudEnabled()) {
score = std::max(score, ChromeOsWebAppExperiments::GetExtendedScopeScore(
app_id, url_spec));
}
#endif // BUILDFLAG(IS_CHROMEOS)
return score;
}
std::optional<webapps::AppId> WebAppRegistrar::FindAppWithUrlInScope(
const GURL& url) const {
return FindBestAppWithUrlInScope(
url, {
proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE,
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
});
}
bool WebAppRegistrar::DoesScopeContainAnyApp(const GURL& scope) const {
return DoesScopeContainAnyApp(
scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION});
}
std::vector<webapps::AppId> WebAppRegistrar::FindAppsInScope(
const GURL& scope) const {
return FindAllAppsNestedInUrl(
scope, {
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
});
}
std::optional<webapps::AppId> WebAppRegistrar::FindInstalledAppWithUrlInScope(
const GURL& url,
bool window_only,
bool exclude_diy_apps) const {
return FindBestAppWithUrlInScope(
url,
{
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
},
{.include_open_in_browser_tab = !window_only,
.include_diy = !exclude_diy_apps});
}
bool WebAppRegistrar::IsNonLocallyInstalledAppWithUrlInScope(
const GURL& url) const {
return FindBestAppWithUrlInScope(
url, {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE})
.has_value();
}
bool WebAppRegistrar::IsShortcutApp(const webapps::AppId& app_id) const {
if (!GetAppById(app_id)) {
return false;
}
// TODO(crbug.com/40277513): Record shortcut distinction explicitly instead of
// using scope.
return !GetAppScopeInternal(app_id).has_value();
}
bool WebAppRegistrar::IsSystemApp(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->IsSystemApp();
}
DisplayMode WebAppRegistrar::GetAppEffectiveDisplayMode(
const webapps::AppId& app_id) const {
if (!IsInstallState(app_id,
{proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION})) {
return DisplayMode::kBrowser;
}
auto app_display_mode = GetAppDisplayMode(app_id);
std::optional<mojom::UserDisplayMode> user_display_mode =
GetAppUserDisplayMode(app_id);
if (app_display_mode == DisplayMode::kUndefined ||
!user_display_mode.has_value()) {
return DisplayMode::kUndefined;
}
std::vector<DisplayMode> display_mode_overrides =
GetAppDisplayModeOverride(app_id);
return ResolveEffectiveDisplayMode(app_display_mode, display_mode_overrides,
*user_display_mode, IsIsolated(app_id));
}
DisplayMode WebAppRegistrar::GetEffectiveDisplayModeFromManifest(
const webapps::AppId& app_id) const {
std::vector<DisplayMode> display_mode_overrides =
GetAppDisplayModeOverride(app_id);
if (!display_mode_overrides.empty())
return display_mode_overrides[0];
return GetAppDisplayMode(app_id);
}
GURL WebAppRegistrar::GetComputedManifestId(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->manifest_id() : GURL();
}
bool WebAppRegistrar::IsTabbedWindowModeEnabled(
const webapps::AppId& app_id) const {
if (!base::FeatureList::IsEnabled(blink::features::kDesktopPWAsTabStrip)) {
return false;
}
return GetAppEffectiveDisplayMode(app_id) == DisplayMode::kTabbed;
}
GURL WebAppRegistrar::GetAppNewTabUrl(const webapps::AppId& app_id) const {
if (IsTabbedWindowModeEnabled(app_id)) {
auto* web_app = GetAppById(app_id);
if (!web_app) {
return GURL();
}
if (web_app->tab_strip()) {
std::optional<GURL> url = web_app->tab_strip().value().new_tab_button.url;
if (url.has_value())
return url.value();
}
}
// Apps that don't set a new_tab_button.url will use the start URL.
return GetAppStartUrl(app_id);
}
std::optional<GURL> WebAppRegistrar::GetAppPinnedHomeTabUrl(
const webapps::AppId& app_id) const {
if (IsTabbedWindowModeEnabled(app_id)) {
const WebApp* web_app = GetAppById(app_id);
if (!web_app)
return std::nullopt;
if (web_app->tab_strip() &&
absl::holds_alternative<blink::Manifest::HomeTabParams>(
web_app->tab_strip().value().home_tab)) {
return GetAppStartUrl(app_id);
}
}
// Apps with home_tab set to 'auto' will not have a home tab.
return std::nullopt;
}
std::optional<proto::WebAppOsIntegrationState>
WebAppRegistrar::GetAppCurrentOsIntegrationState(
const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app)
return std::nullopt;
return web_app->current_os_integration_states();
}
#if BUILDFLAG(IS_MAC)
bool WebAppRegistrar::AlwaysShowToolbarInFullscreen(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->always_show_toolbar_in_fullscreen() : true;
}
void WebAppRegistrar::NotifyAlwaysShowToolbarInFullscreenChanged(
const webapps::AppId& app_id,
bool show) {
for (WebAppRegistrarObserver& observer : observers_) {
observer.OnAlwaysShowToolbarInFullscreenChanged(app_id, show);
}
}
#endif
const WebApp* WebAppRegistrar::GetAppById(const webapps::AppId& app_id) const {
auto it = registry_.find(app_id);
if (it != registry_.end() && WebAppSourceSupported(*it->second))
return it->second.get();
return nullptr;
}
const WebApp* WebAppRegistrar::GetAppByStartUrl(const GURL& start_url) const {
for (auto const& it : registry_) {
if (WebAppSourceSupported(*it.second) &&
it.second->start_url() == start_url)
return it.second.get();
}
return nullptr;
}
std::vector<webapps::AppId>
WebAppRegistrar::GetAppsFromSyncAndPendingInstallation() const {
AppSet apps_in_sync_install =
AppSet(this, [](const WebApp& web_app) {
return WebAppSourceSupported(web_app) &&
web_app.is_from_sync_and_pending_installation();
});
std::vector<webapps::AppId> app_ids;
for (const WebApp& app : apps_in_sync_install)
app_ids.push_back(app.app_id());
return app_ids;
}
std::vector<webapps::AppId> WebAppRegistrar::GetAppsPendingUninstall() const {
AppSet apps_in_sync_uninstall =
AppSet(this, [](const WebApp& web_app) {
return WebAppSourceSupported(web_app) &&
!web_app.is_from_sync_and_pending_installation() &&
web_app.is_uninstalling();
});
std::vector<webapps::AppId> app_ids;
for (const WebApp& app : apps_in_sync_uninstall)
app_ids.push_back(app.app_id());
return app_ids;
}
bool WebAppRegistrar::AppsExistWithExternalConfigData() const {
for (const WebApp& web_app : GetApps()) {
if (web_app.management_to_external_config_map().size() > 0)
return true;
}
return false;
}
void WebAppRegistrar::SetProvider(base::PassKey<WebAppProvider>,
WebAppProvider& provider) {
provider_ = &provider;
}
void WebAppRegistrar::Start() {
int num_user_installed_apps =
std::get<InstallableAppCount>(CountTotalUserInstalledAppsIncludingDiy())
.value();
int num_user_installed_diy_apps =
std::get<DiyAppCount>(CountTotalUserInstalledAppsIncludingDiy()).value();
int num_non_locally_installed = CountUserInstalledNotLocallyInstalledApps();
base::UmaHistogramCounts1000("WebApp.InstalledCount.ByUser",
num_user_installed_apps);
base::UmaHistogramCounts1000(
"WebApp.InstalledCount.ByUserNotLocallyInstalled",
num_non_locally_installed);
base::UmaHistogramCounts1000("WebApp.DiyAppsInstalledCount.ByUser",
num_user_installed_diy_apps);
#if BUILDFLAG(IS_MAC)
auto multi_profile_app_ids =
AppShimRegistry::Get()->GetAppsInstalledInMultipleProfiles();
int num_multi_profile_apps = 0;
for (const auto& app_id : multi_profile_app_ids) {
const WebApp* app = GetAppById(app_id);
if (app && app->install_state() == proto::INSTALLED_WITH_OS_INTEGRATION &&
app->WasInstalledByUser()) {
num_multi_profile_apps++;
}
}
base::UmaHistogramCounts1000("WebApp.InstalledCount.ByUserInMultipleProfiles",
num_multi_profile_apps);
#endif
}
base::WeakPtr<WebAppRegistrar> WebAppRegistrar::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
std::optional<webapps::AppId> WebAppRegistrar::LookUpAppIdByInstallUrl(
const GURL& install_url) const {
for (const WebApp& web_app : GetApps()) {
for (auto it : web_app.management_to_external_config_map()) {
if (base::Contains(it.second.install_urls, install_url)) {
return web_app.app_id();
}
}
}
return std::nullopt;
}
const WebApp* WebAppRegistrar::LookUpAppByInstallSourceInstallUrl(
WebAppManagement::Type install_source,
const GURL& install_url) const {
for (const WebApp& app : GetApps()) {
const WebApp::ExternalConfigMap& config_map =
app.management_to_external_config_map();
auto it = config_map.find(install_source);
if (it != config_map.end()) {
if (base::Contains(it->second.install_urls, install_url)) {
return &app;
}
}
}
return nullptr;
}
bool WebAppRegistrar::IsNotInRegistrar(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app || web_app->is_uninstalling()) {
return true;
}
// `is_from_sync_and_pending_installation()` should be treated as 'not
// installed' only if there are no other sources that have installed the web
// app.
WebAppManagementTypes sources_except_sync = web_app->GetSources();
sources_except_sync.Remove(WebAppManagement::kSync);
if (web_app->is_from_sync_and_pending_installation() &&
sources_except_sync.empty()) {
return true;
}
return false;
}
std::optional<proto::InstallState> WebAppRegistrar::GetInstallState(
const webapps::AppId& app_id) const {
if (IsNotInRegistrar(app_id)) {
return std::nullopt;
}
const WebApp* web_app = GetAppById(app_id);
CHECK(web_app);
return web_app->install_state();
}
bool WebAppRegistrar::IsInstallState(
const webapps::AppId& app_id,
std::initializer_list<proto::InstallState> states) const {
CHECK_NE(states.size(), 0ul);
std::optional<proto::InstallState> install_state = GetInstallState(app_id);
if (!install_state) {
return false;
}
InstallStateSet state_set(states);
return state_set.Has(install_state.value());
}
std::optional<webapps::AppId> WebAppRegistrar::FindBestAppWithUrlInScope(
const GURL& url,
std::initializer_list<proto::InstallState> states) const {
return FindBestAppWithUrlInScope(url, states, AppFilterOptions());
}
std::optional<webapps::AppId> WebAppRegistrar::FindBestAppWithUrlInScope(
const GURL& url,
std::initializer_list<proto::InstallState> states,
AppFilterOptions options) const {
CHECK_NE(states.size(), 0ul);
if (!url.is_valid()) {
return std::nullopt;
}
const std::string url_spec = url.spec();
std::optional<webapps::AppId> best_app_id;
int best_score = 0;
bool best_app_is_shortcut = true;
for (const webapps::AppId& app_id :
GetAppIdsForAppSet(GetAppsIncludingStubs())) {
if (!IsInstallState(app_id, states)) {
continue;
}
if (!options.include_open_in_browser_tab &&
GetAppEffectiveDisplayMode(app_id) == DisplayMode::kBrowser) {
continue;
}
if (!options.include_diy && IsDiyApp(app_id)) {
continue;
}
// TODO(crbug.com/40277513): Consider treating shortcuts differently to
// PWAs.
// TODO(crbug.com/341316725): Remove shortcut apps.
bool app_is_shortcut = IsShortcutApp(app_id);
if (app_is_shortcut && !best_app_is_shortcut) {
continue;
}
int score;
// TODO(crbug.com/341337420): Audit call sites and ideally have scope
// extensions be considered by default.
if (options.include_extended_scope) {
score = GetAppExtendedScopeScore(url, app_id);
} else {
score = GetUrlInAppScopeScore(url_spec, app_id);
}
if (score > 0 &&
(score > best_score || (best_app_is_shortcut && !app_is_shortcut))) {
best_app_id = app_id;
best_score = score;
best_app_is_shortcut = app_is_shortcut;
}
}
return best_app_id;
}
// Returns all apps that have the given `url` in scope and are in one of the
// given `states`.
std::vector<webapps::AppId> WebAppRegistrar::FindAllAppsWithUrlInScope(
const GURL& url,
std::initializer_list<proto::InstallState> states) const {
CHECK_NE(states.size(), 0ul);
if (!url.is_valid()) {
return {};
}
std::string url_spec = url.spec();
std::vector<webapps::AppId> apps_with_url_in_scope;
for (const auto& app_id : GetAppIdsForAppSet(GetAppsIncludingStubs())) {
if (!IsInstallState(app_id, states)) {
continue;
}
std::string app_scope = GetAppScope(app_id).spec();
DCHECK(!app_scope.empty());
if (!base::StartsWith(app_scope, url_spec, base::CompareCase::SENSITIVE)) {
continue;
}
apps_with_url_in_scope.push_back(app_id);
}
return apps_with_url_in_scope;
}
// Returns all apps that have the given `url` in scope and are in one of the
// given `states`.
std::vector<webapps::AppId> WebAppRegistrar::FindAllAppsNestedInUrl(
const GURL& outer_scope,
std::initializer_list<proto::InstallState> states) const {
CHECK_NE(states.size(), 0ul);
if (!outer_scope.is_valid()) {
return {};
}
std::string outer_scope_spec = outer_scope.spec();
std::vector<webapps::AppId> apps_in_outer_scope;
for (const auto& app_id : GetAppIdsForAppSet(GetAppsIncludingStubs())) {
if (!IsInstallState(app_id, states)) {
continue;
}
std::string app_scope = GetAppScope(app_id).spec();
DCHECK(!app_scope.empty());
if (!base::StartsWith(app_scope, outer_scope_spec,
base::CompareCase::SENSITIVE)) {
continue;
}
apps_in_outer_scope.push_back(app_id);
}
return apps_in_outer_scope;
}
bool WebAppRegistrar::DoesScopeContainAnyApp(
const GURL& scope,
std::initializer_list<proto::InstallState> allowed_states) const {
std::string scope_str = scope.spec();
for (const auto& app_id : GetAppIdsForAppSet(GetAppsIncludingStubs())) {
if (!IsInstallState(app_id, allowed_states)) {
continue;
}
std::string app_scope = GetAppScope(app_id).spec();
CHECK(!app_scope.empty());
if (base::StartsWith(app_scope, scope_str, base::CompareCase::SENSITIVE)) {
return true;
}
// TODO(crbug.com/341337420): Support scope extensions.
}
return false;
}
bool WebAppRegistrar::IsInstalled(const webapps::AppId& app_id) const {
return IsInstallState(
app_id, {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE,
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION});
}
bool WebAppRegistrar::IsUninstalling(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->is_uninstalling();
}
bool WebAppRegistrar::IsLocallyInstalled(const webapps::AppId& app_id) const {
return IsInstallState(
app_id, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION});
}
bool WebAppRegistrar::IsActivelyInstalled(const webapps::AppId& app_id) const {
return IsInstallState(app_id,
{proto::InstallState::INSTALLED_WITH_OS_INTEGRATION});
}
bool WebAppRegistrar::IsIsolated(const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app && web_app->isolation_data().has_value();
}
bool WebAppRegistrar::IsInstalledByDefaultManagement(
const webapps::AppId& app_id) const {
if (!IsInstalled(app_id))
return false;
const WebApp* web_app = GetAppById(app_id);
DCHECK(web_app);
return web_app->GetSources().Has(WebAppManagement::kDefault);
}
bool WebAppRegistrar::IsInstalledByPolicy(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app) {
return false;
}
WebAppManagementTypes sources = web_app->GetSources();
if (web_app->isolation_data().has_value()) {
return sources.Has(WebAppManagement::Type::kIwaPolicy);
}
return sources.Has(WebAppManagement::Type::kPolicy);
}
bool WebAppRegistrar::WasInstalledByDefaultOnly(
const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->HasOnlySource(WebAppManagement::Type::kDefault);
}
bool WebAppRegistrar::WasInstalledByUser(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->WasInstalledByUser();
}
bool WebAppRegistrar::WasInstalledByOem(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->chromeos_data().has_value() &&
web_app->chromeos_data()->oem_installed;
}
bool WebAppRegistrar::WasInstalledBySubApp(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->IsSubAppInstalledApp();
}
bool WebAppRegistrar::CanUserUninstallWebApp(
const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->CanUserUninstallWebApp();
}
bool WebAppRegistrar::IsPreventCloseEnabled(
const webapps::AppId& app_id) const {
return provider_->policy_manager().IsPreventCloseEnabled(app_id);
}
bool WebAppRegistrar::IsAllowedLaunchProtocol(
const webapps::AppId& app_id,
const std::string& protocol_scheme) const {
const WebApp* web_app = GetAppById(app_id);
return web_app &&
base::Contains(web_app->allowed_launch_protocols(), protocol_scheme);
}
bool WebAppRegistrar::IsDisallowedLaunchProtocol(
const webapps::AppId& app_id,
const std::string& protocol_scheme) const {
const WebApp* web_app = GetAppById(app_id);
return web_app && base::Contains(web_app->disallowed_launch_protocols(),
protocol_scheme);
}
bool WebAppRegistrar::IsRegisteredLaunchProtocol(
const webapps::AppId& app_id,
const std::string& protocol_scheme) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app)
return false;
return base::Contains(web_app->protocol_handlers(), protocol_scheme,
[](const auto& info) { return info.protocol; });
}
base::flat_set<std::string> WebAppRegistrar::GetAllAllowedLaunchProtocols()
const {
base::flat_set<std::string> protocols;
for (const WebApp& web_app : GetApps()) {
protocols.insert(web_app.allowed_launch_protocols().begin(),
web_app.allowed_launch_protocols().end());
}
return protocols;
}
base::flat_set<std::string> WebAppRegistrar::GetAllDisallowedLaunchProtocols()
const {
base::flat_set<std::string> protocols;
for (const WebApp& web_app : GetApps()) {
protocols.insert(web_app.disallowed_launch_protocols().begin(),
web_app.disallowed_launch_protocols().end());
}
return protocols;
}
int WebAppRegistrar::CountUserInstalledApps() const {
return std::get<InstallableAppCount>(
CountTotalUserInstalledAppsIncludingDiy())
.value();
}
int WebAppRegistrar::CountUserInstalledDiyApps() const {
return std::get<DiyAppCount>(CountTotalUserInstalledAppsIncludingDiy())
.value();
}
std::vector<content::StoragePartitionConfig>
WebAppRegistrar::GetIsolatedWebAppStoragePartitionConfigs(
const webapps::AppId& isolated_web_app_id) const {
if (!content::IsolatedWebAppsPolicy::AreIsolatedWebAppsEnabled(profile_)) {
return {};
}
const WebApp* isolated_web_app = GetAppById(isolated_web_app_id);
if (!isolated_web_app) {
return {};
}
// Note: This function is called after is_installed is set to true, so regular
// helper functions cannot be used.
if (isolated_web_app->install_state() !=
proto::INSTALLED_WITH_OS_INTEGRATION &&
isolated_web_app->install_state() !=
proto::INSTALLED_WITHOUT_OS_INTEGRATION) {
return {};
}
if (!isolated_web_app->isolation_data()) {
return {};
}
base::expected<IsolatedWebAppUrlInfo, std::string> url_info =
IsolatedWebAppUrlInfo::Create(isolated_web_app->scope());
if (!url_info.has_value()) {
LOG(ERROR) << "Invalid Isolated Web App: " << isolated_web_app->app_id()
<< ", " << url_info.error();
return {};
}
// Start with IWA's base on-disk partition.
std::vector<content::StoragePartitionConfig> partitions = {
url_info->storage_partition_config(profile_)};
// Get all on-disk Controlled Frame partitions.
for (const std::string& partition :
isolated_web_app->isolation_data()->controlled_frame_partitions) {
partitions.push_back(url_info->GetStoragePartitionConfigForControlledFrame(
profile_, partition, /*in_memory=*/false));
}
// Get all in-memory Controlled Frame partitions.
auto it = isolated_web_app_in_memory_controlled_frame_partitions_.find(
isolated_web_app_id);
if (it != isolated_web_app_in_memory_controlled_frame_partitions_.end()) {
for (const std::string& partition : it->second) {
partitions.push_back(
url_info->GetStoragePartitionConfigForControlledFrame(
profile_, partition, /*in_memory=*/true));
}
}
return partitions;
}
std::optional<content::StoragePartitionConfig>
WebAppRegistrar::SaveAndGetInMemoryControlledFramePartitionConfig(
const IsolatedWebAppUrlInfo& url_info,
const std::string& partition_name) {
if (!IsInstalled(url_info.app_id())) {
return std::nullopt;
}
isolated_web_app_in_memory_controlled_frame_partitions_[url_info.app_id()]
.insert(partition_name);
return url_info.GetStoragePartitionConfigForControlledFrame(
profile_, partition_name, true);
}
bool WebAppRegistrar::CanCaptureLinksInScope(
const webapps::AppId& app_id) const {
if (!base::FeatureList::IsEnabled(features::kPwaNavigationCapturing)) {
return false;
}
if (!IsInstallState(app_id,
{proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION}) ||
IsShortcutApp(app_id)) {
return false;
}
return true;
}
bool WebAppRegistrar::CapturesLinksInScope(const webapps::AppId& app_id) const {
if (!CanCaptureLinksInScope(app_id)) {
return false;
}
const WebApp* web_app = GetAppById(app_id);
CHECK(web_app);
switch (web_app->user_link_capturing_preference()) {
case proto::LinkCapturingUserPreference::LINK_CAPTURING_PREFERENCE_DEFAULT:
if (IsLinkCapturingDisabledByDefaultBasedOnFlagState()) {
return false;
}
break;
case proto::LinkCapturingUserPreference::CAPTURE_SUPPORTED_LINKS:
return true;
case proto::LinkCapturingUserPreference::DO_NOT_CAPTURE_SUPPORTED_LINKS:
return false;
}
// Reaching here means that the default link capturing behavior is 'on' and
// the current app is 'default'. To resolve,
// - If any other app shares the scope and has link capturing enabled, return
// false.
// - If there are more than one apps set to 'default', then return one app
// deterministically (the earliest installed).
// Technically, this violates some of the locking practices we have, as this
// views all apps instead of the one app. However, given the rarity of hitting
// this, and the difficulty of actually hitting an edge case here, this seems
// OK.
std::vector<std::pair<webapps::AppId, base::Time>> app_and_install_time = {
{app_id, web_app->first_install_time()}};
for (const webapps::AppId& other_app_id : GetAppIds()) {
if (!CanCaptureLinksInScope(other_app_id) || other_app_id == app_id) {
continue;
}
if (!AppScopesMatchForUserLinkCapturing(app_id, other_app_id)) {
continue;
}
const WebApp* other_app = GetAppById(other_app_id);
switch (other_app->user_link_capturing_preference()) {
case proto::LinkCapturingUserPreference::
LINK_CAPTURING_PREFERENCE_DEFAULT:
app_and_install_time.emplace_back(other_app_id,
other_app->first_install_time());
break;
case proto::LinkCapturingUserPreference::CAPTURE_SUPPORTED_LINKS:
return false;
case proto::LinkCapturingUserPreference::DO_NOT_CAPTURE_SUPPORTED_LINKS:
continue;
}
}
// Sort by install time so the first installation wins.
std::sort(app_and_install_time.begin(), app_and_install_time.end(),
[](auto& left, auto& right) { return left.second < right.second; });
return app_and_install_time.front().first == app_id;
}
std::optional<webapps::AppId> WebAppRegistrar::FindAppThatCapturesLinksInScope(
const GURL& url) const {
// Nested apps remove that URL space from the parent app, so links from a
// nested app cannot be captured by a parent app. Even so, there can be
// multiple apps with the same score, but the only one that matters is the
// first one that also captures links.
int top_score = 0;
std::vector<webapps::AppId> top_apps;
for (const webapps::AppId& app_id : GetAppIds()) {
if (!CanCaptureLinksInScope(app_id)) {
continue;
}
int score;
if (base::FeatureList::IsEnabled(
features::kPwaNavigationCapturingWithScopeExtensions)) {
score = GetAppExtendedScopeScore(url, app_id);
} else {
score = GetUrlInAppScopeScore(url.spec(), app_id);
}
// A score of 0 means it doesn't apply at all.
if (score == 0 || score < top_score) {
continue;
}
if (score == top_score) {
top_apps.push_back(app_id);
continue;
}
top_score = score;
top_apps = {app_id};
}
if (top_apps.empty()) {
return std::nullopt;
}
for (const webapps::AppId& app_id : top_apps) {
if (CapturesLinksInScope(app_id)) {
return app_id;
}
}
return std::nullopt;
}
bool WebAppRegistrar::IsLinkCapturableByApp(const webapps::AppId& app,
const GURL& url) const {
CHECK(url.is_valid());
int app_score;
if (base::FeatureList::IsEnabled(
features::kPwaNavigationCapturingWithScopeExtensions)) {
app_score = GetAppExtendedScopeScore(url, app);
} else {
app_score = GetUrlInAppScopeScore(url.spec(), app);
}
if (app_score == 0) {
return false;
}
return base::ranges::none_of(GetAppIds(), [&](const webapps::AppId& app_id) {
int other_score;
if (base::FeatureList::IsEnabled(
features::kPwaNavigationCapturingWithScopeExtensions)) {
other_score = GetAppExtendedScopeScore(url, app_id);
} else {
other_score = GetUrlInAppScopeScore(url.spec(), app_id);
}
return IsInstallState(
app_id, {proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION}) &&
!IsShortcutApp(app_id) && other_score > app_score;
});
}
std::vector<webapps::AppId> WebAppRegistrar::GetOverlappingAppsMatchingScope(
const webapps::AppId& app_id) const {
std::vector<webapps::AppId> all_apps_with_supported_links;
const GURL& required_scope = GetAppScope(app_id);
if (!IsValidScopeForLinkCapturing(required_scope)) {
return all_apps_with_supported_links;
}
for (const auto& id : GetAppIds()) {
if (id == app_id) {
continue;
}
if (!CanCaptureLinksInScope(id)) {
continue;
}
if (!AppScopesMatchForUserLinkCapturing(id, app_id)) {
continue;
}
if (!CapturesLinksInScope(id)) {
continue;
}
all_apps_with_supported_links.push_back(id);
}
return all_apps_with_supported_links;
}
bool WebAppRegistrar::AppScopesMatchForUserLinkCapturing(
const webapps::AppId& app_id1,
const webapps::AppId& app_id2) const {
if (!IsInstallState(app_id1, {proto::INSTALLED_WITH_OS_INTEGRATION,
proto::INSTALLED_WITHOUT_OS_INTEGRATION}) ||
!IsInstallState(app_id2, {proto::INSTALLED_WITH_OS_INTEGRATION,
proto::INSTALLED_WITHOUT_OS_INTEGRATION})) {
return false;
}
const GURL& app_scope1 = GetAppScope(app_id1);
const GURL& app_scope2 = GetAppScope(app_id2);
if (!IsValidScopeForLinkCapturing(app_scope1) ||
!IsValidScopeForLinkCapturing(app_scope2)) {
return false;
}
return app_scope1 == app_scope2;
}
base::flat_map<webapps::AppId, std::string>
WebAppRegistrar::GetAllAppsControllingUrl(const GURL& url) const {
base::flat_map<webapps::AppId, std::string> all_controlling_apps;
for (const webapps::AppId& app_id : GetAppIds()) {
if (!IsInstallState(app_id,
{proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION})) {
continue;
}
if (GetAppUserDisplayMode(app_id) == mojom::UserDisplayMode::kBrowser) {
continue;
}
const GURL scope = GetAppScope(app_id);
if (base::StartsWith(url.spec(), scope.spec(),
base::CompareCase::SENSITIVE)) {
all_controlling_apps.insert_or_assign(app_id, GetAppShortName(app_id));
}
}
return all_controlling_apps;
}
bool WebAppRegistrar::IsPreferredAppForCapturingUrl(
const GURL& url,
const webapps::AppId& app_id) {
const GURL app_scope = GetAppScope(app_id);
return base::StartsWith(url.spec(), app_scope.spec(),
base::CompareCase::SENSITIVE) &&
CapturesLinksInScope(app_id);
}
bool WebAppRegistrar::IsDiyApp(const webapps::AppId& app_id) const {
if (!IsInstalled(app_id)) {
return false;
}
const WebApp* web_app = GetAppById(app_id);
return web_app && web_app->is_diy_app();
}
std::string WebAppRegistrar::GetAppShortName(
const webapps::AppId& app_id) const {
if (base::FeatureList::IsEnabled(
blink::features::kWebAppEnableTranslations)) {
std::string translated_name =
provider_->translation_manager().GetTranslatedName(app_id);
if (!translated_name.empty()) {
return translated_name;
}
}
auto* web_app = GetAppById(app_id);
return web_app ? web_app->untranslated_name() : std::string();
}
std::string WebAppRegistrar::GetAppDescription(
const webapps::AppId& app_id) const {
if (base::FeatureList::IsEnabled(
blink::features::kWebAppEnableTranslations)) {
std::string translated_description =
provider_->translation_manager().GetTranslatedDescription(app_id);
if (!translated_description.empty()) {
return translated_description;
}
}
auto* web_app = GetAppById(app_id);
return web_app ? web_app->untranslated_description() : std::string();
}
std::optional<SkColor> WebAppRegistrar::GetAppThemeColor(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->theme_color() : std::nullopt;
}
std::optional<SkColor> WebAppRegistrar::GetAppDarkModeThemeColor(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->dark_mode_theme_color() : std::nullopt;
}
std::optional<SkColor> WebAppRegistrar::GetAppBackgroundColor(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->background_color() : std::nullopt;
}
std::optional<SkColor> WebAppRegistrar::GetAppDarkModeBackgroundColor(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->dark_mode_background_color() : std::nullopt;
}
const GURL& WebAppRegistrar::GetAppStartUrl(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->start_url() : GURL::EmptyGURL();
}
webapps::ManifestId WebAppRegistrar::GetAppManifestId(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->manifest_id() : webapps::ManifestId();
}
const std::string* WebAppRegistrar::GetAppLaunchQueryParams(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->launch_query_params() : nullptr;
}
const apps::ShareTarget* WebAppRegistrar::GetAppShareTarget(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return (web_app && web_app->share_target().has_value())
? &web_app->share_target().value()
: nullptr;
}
const apps::FileHandlers* WebAppRegistrar::GetAppFileHandlers(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? &web_app->file_handlers() : nullptr;
}
bool WebAppRegistrar::IsAppFileHandlerPermissionBlocked(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
if (!web_app)
return false;
return web_app->file_handler_approval_state() ==
ApiApprovalState::kDisallowed;
}
ApiApprovalState WebAppRegistrar::GetAppFileHandlerApprovalState(
const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app)
return ApiApprovalState::kDisallowed;
if (web_app->IsSystemApp())
return ApiApprovalState::kAllowed;
// TODO(estade): also consult the policy manager when File Handler policies
// exist.
return web_app->file_handler_approval_state();
}
bool WebAppRegistrar::ExpectThatFileHandlersAreRegisteredWithOs(
const webapps::AppId& app_id) const {
auto state = GetAppCurrentOsIntegrationState(app_id);
if (!state.has_value()) {
return false;
}
return state->has_file_handling();
}
std::optional<GURL> WebAppRegistrar::GetAppScopeInternal(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
if (!web_app)
return std::nullopt;
// TODO(crbug.com/40277513): Record shortcut distinction explicitly instead of
// using scope.
// Shortcuts on the WebApp system have empty scopes, while the implementation
// of IsShortcutApp just checks if the scope is |std::nullopt|, so make sure
// we return |std::nullopt| rather than an empty scope.
// TODO(crbug.com/341316725): Remove shortcut apps.
if (web_app->scope().is_empty())
return std::nullopt;
return web_app->scope();
}
DisplayMode WebAppRegistrar::GetAppDisplayMode(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->display_mode() : DisplayMode::kUndefined;
}
std::optional<mojom::UserDisplayMode> WebAppRegistrar::GetAppUserDisplayMode(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
if (web_app == nullptr) {
return std::nullopt;
}
return web_app->user_display_mode();
}
std::vector<DisplayMode> WebAppRegistrar::GetAppDisplayModeOverride(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->display_mode_override()
: std::vector<DisplayMode>();
}
apps::UrlHandlers WebAppRegistrar::GetAppUrlHandlers(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->url_handlers()
: std::vector<apps::UrlHandlerInfo>();
}
base::flat_set<ScopeExtensionInfo> WebAppRegistrar::GetScopeExtensions(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->scope_extensions()
: base::flat_set<ScopeExtensionInfo>();
}
base::flat_set<ScopeExtensionInfo> WebAppRegistrar::GetValidatedScopeExtensions(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->validated_scope_extensions()
: base::flat_set<ScopeExtensionInfo>();
}
GURL WebAppRegistrar::GetAppManifestUrl(const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->manifest_url() : GURL();
}
base::Time WebAppRegistrar::GetAppLastBadgingTime(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->last_badging_time() : base::Time();
}
base::Time WebAppRegistrar::GetAppLastLaunchTime(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->last_launch_time() : base::Time();
}
base::Time WebAppRegistrar::GetAppFirstInstallTime(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->first_install_time() : base::Time();
}
std::optional<webapps::WebappInstallSource>
WebAppRegistrar::GetLatestAppInstallSource(const webapps::AppId& app_id) const {
const WebApp* web_app = GetAppById(app_id);
if (!web_app)
return std::nullopt;
return web_app->latest_install_source();
}
std::vector<apps::IconInfo> WebAppRegistrar::GetAppIconInfos(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->manifest_icons() : std::vector<apps::IconInfo>();
}
SortedSizesPx WebAppRegistrar::GetAppDownloadedIconSizesAny(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->downloaded_icon_sizes(IconPurpose::ANY)
: SortedSizesPx();
}
std::vector<WebAppShortcutsMenuItemInfo>
WebAppRegistrar::GetAppShortcutsMenuItemInfos(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->shortcuts_menu_item_infos()
: std::vector<WebAppShortcutsMenuItemInfo>();
}
std::vector<webapps::AppId> WebAppRegistrar::GetAppIds() const {
return GetAppIdsForAppSet(GetApps());
}
std::vector<webapps::AppId> WebAppRegistrar::GetAllSubAppIds(
const webapps::AppId& parent_app_id) const {
std::vector<webapps::AppId> sub_app_ids;
for (const WebApp& app : GetApps()) {
if (app.parent_app_id().has_value() &&
*app.parent_app_id() == parent_app_id) {
sub_app_ids.push_back(app.app_id());
}
}
return sub_app_ids;
}
base::flat_map<webapps::AppId, webapps::AppId>
WebAppRegistrar::GetSubAppToParentMap() const {
base::flat_map<webapps::AppId, webapps::AppId> parent_app_ids;
for (const WebApp& app : GetApps()) {
if (app.parent_app_id().has_value()) {
parent_app_ids[app.app_id()] = *app.parent_app_id();
}
}
return parent_app_ids;
}
ValueWithPolicy<RunOnOsLoginMode> WebAppRegistrar::GetAppRunOnOsLoginMode(
const webapps::AppId& app_id) const {
RunOnOsLoginPolicy login_policy =
provider_->policy_manager().GetUrlRunOnOsLoginPolicy(app_id);
switch (login_policy) {
case RunOnOsLoginPolicy::kAllowed: {
auto* web_app = GetAppById(app_id);
return {
web_app ? web_app->run_on_os_login_mode() : RunOnOsLoginMode::kNotRun,
true};
}
case RunOnOsLoginPolicy::kBlocked:
return {RunOnOsLoginMode::kNotRun, false};
case RunOnOsLoginPolicy::kRunWindowed:
return {RunOnOsLoginMode::kWindowed, false};
}
}
bool WebAppRegistrar::GetWindowControlsOverlayEnabled(
const webapps::AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->window_controls_overlay_enabled() : false;
}
WebAppRegistrar::AppSet::AppSet(const WebAppRegistrar* registrar, Filter filter)
: registrar_(registrar),
filter_(filter)
#if DCHECK_IS_ON()
,
mutations_count_(registrar->mutations_count_)
#endif
{
DCHECK(filter);
}
WebAppRegistrar::AppSet::~AppSet() {
#if DCHECK_IS_ON()
DCHECK_EQ(mutations_count_, registrar_->mutations_count_);
#endif
}
WebAppRegistrar::AppSet::iterator WebAppRegistrar::AppSet::begin() {
return iterator(registrar_->registry_.begin(), registrar_->registry_.end(),
filter_);
}
WebAppRegistrar::AppSet::iterator WebAppRegistrar::AppSet::end() {
return iterator(registrar_->registry_.end(), registrar_->registry_.end(),
filter_);
}
WebAppRegistrar::AppSet::const_iterator WebAppRegistrar::AppSet::begin() const {
return const_iterator(registrar_->registry_.begin(),
registrar_->registry_.end(), filter_);
}
WebAppRegistrar::AppSet::const_iterator WebAppRegistrar::AppSet::end() const {
return const_iterator(registrar_->registry_.end(),
registrar_->registry_.end(), filter_);
}
WebAppRegistrar::AppSet WebAppRegistrar::GetAppsIncludingStubs() const {
return AppSet(this, [](const WebApp& web_app) {
return WebAppSourceSupported(web_app);
});
}
WebAppRegistrar::AppSet WebAppRegistrar::GetApps() const {
return AppSet(
this, [](const WebApp& web_app) {
return WebAppSourceSupported(web_app) &&
!web_app.is_from_sync_and_pending_installation() &&
!web_app.is_uninstalling();
});
}
base::Value WebAppRegistrar::AsDebugValue() const {
base::Value::Dict root;
std::vector<const web_app::WebApp*> web_apps;
for (const web_app::WebApp& web_app : GetAppsIncludingStubs()) {
web_apps.push_back(&web_app);
}
base::ranges::sort(web_apps, {}, &web_app::WebApp::untranslated_name);
// Prefix with a ! so this appears at the top when serialized.
base::Value::Dict& index = *root.EnsureDict("!Index");
for (const web_app::WebApp* web_app : web_apps) {
const std::string& key = web_app->untranslated_name();
base::Value* existing_entry = index.Find(key);
if (!existing_entry) {
index.Set(key, web_app->app_id());
continue;
}
// If any web apps share identical names then collect a list of app IDs.
const std::string* existing_id = existing_entry->GetIfString();
if (existing_id) {
base::Value::List id_list;
id_list.Append(*existing_id);
index.Set(key, std::move(id_list));
}
index.FindList(key)->Append(web_app->app_id());
}
base::Value::List& web_app_details = *root.EnsureList("Details");
for (const web_app::WebApp* web_app : web_apps) {
auto app_id = web_app->app_id();
base::Value app_debug_value = web_app->AsDebugValue();
auto& app_debug_dict = app_debug_value.GetDict();
base::Value::Dict& effective_fields =
*app_debug_dict.EnsureDict("registrar_evaluated_fields");
effective_fields.Set(
"display_mode",
blink::DisplayModeToString(GetAppEffectiveDisplayMode(app_id)));
effective_fields.Set("launch_url", base::ToString(GetAppLaunchUrl(app_id)));
effective_fields.Set("scope", base::ToString(GetAppScope(app_id)));
base::Value::Dict& run_on_os_login_fields =
*effective_fields.EnsureDict("run_on_os_login_mode");
web_app::ValueWithPolicy<web_app::RunOnOsLoginMode> run_on_os_login_mode =
GetAppRunOnOsLoginMode(app_id);
run_on_os_login_fields.Set("value",
base::ToString(run_on_os_login_mode.value));
run_on_os_login_fields.Set("user_controllable",
run_on_os_login_mode.user_controllable);
base::Value::List* in_mem_controlled_frame_partitions =
app_debug_dict.EnsureDict("isolated_data_in_memory")
->EnsureList("controlled_frame_partitions (in-memory)");
auto it = isolated_web_app_in_memory_controlled_frame_partitions_.find(
web_app->app_id());
if (it != isolated_web_app_in_memory_controlled_frame_partitions_.end()) {
for (const std::string& partition : it->second) {
in_mem_controlled_frame_partitions->Append(partition);
}
}
web_app_details.Append(std::move(app_debug_value));
}
return base::Value(std::move(root));
}
void WebAppRegistrar::SetRegistry(Registry&& registry) {
registry_ = std::move(registry);
}
void WebAppRegistrar::CountMutation() {
#if DCHECK_IS_ON()
++mutations_count_;
#endif
}
WebAppRegistrarMutable::WebAppRegistrarMutable(Profile* profile)
: WebAppRegistrar(profile) {}
WebAppRegistrarMutable::~WebAppRegistrarMutable() = default;
void WebAppRegistrarMutable::InitRegistry(Registry&& registry) {
DCHECK(is_empty());
SetRegistry(std::move(registry));
}
WebApp* WebAppRegistrarMutable::GetAppByIdMutable(
const webapps::AppId& app_id) {
return const_cast<WebApp*>(GetAppById(app_id));
}
WebAppRegistrar::AppSet WebAppRegistrarMutable::GetAppsMutable() {
return AppSet(
this, [](const WebApp& web_app) {
return WebAppSourceSupported(web_app) &&
!web_app.is_from_sync_and_pending_installation() &&
!web_app.is_uninstalling();
});
}
bool IsRegistryEqual(const Registry& registry,
const Registry& registry2,
bool exclude_current_os_integration) {
if (registry.size() != registry2.size()) {
LOG(ERROR) << registry.size() << " != " << registry2.size();
return false;
}
for (auto& kv : registry) {
// Copy to allow clearing the os integration.
WebApp web_app = WebApp(*kv.second);
if (!registry2.contains(web_app.app_id())) {
LOG(ERROR) << "Registry does not contain app: " << web_app;
return false;
}
WebApp web_app2 = WebApp(*registry2.at(web_app.app_id()));
if (exclude_current_os_integration) {
web_app.SetCurrentOsIntegrationStates(proto::WebAppOsIntegrationState());
web_app2.SetCurrentOsIntegrationStates(proto::WebAppOsIntegrationState());
}
if (web_app != web_app2) {
LOG(ERROR) << "Web apps are not equal:\n" << web_app << "\n" << web_app2;
return false;
}
}
return true;
}
std::vector<webapps::AppId> WebAppRegistrar::GetAppIdsForAppSet(
const AppSet& app_set) const {
std::vector<webapps::AppId> app_ids;
for (const WebApp& app : app_set)
app_ids.push_back(app.app_id());
return app_ids;
}
int WebAppRegistrar::CountUserInstalledNotLocallyInstalledApps() const {
int num_non_locally_installed = 0;
for (const WebApp& app : GetApps()) {
if (app.install_state() ==
proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE &&
app.WasInstalledByUser()) {
++num_non_locally_installed;
}
}
return num_non_locally_installed;
}
std::tuple<DiyAppCount, InstallableAppCount>
WebAppRegistrar::CountTotalUserInstalledAppsIncludingDiy() const {
InstallableAppCount num_user_installed(0);
DiyAppCount num_diy_apps_user_installed(0);
for (const WebApp& app : GetApps()) {
if ((app.install_state() == proto::INSTALLED_WITH_OS_INTEGRATION ||
app.install_state() == proto::INSTALLED_WITHOUT_OS_INTEGRATION) &&
app.WasInstalledByUser()) {
if (app.is_diy_app()) {
++num_diy_apps_user_installed.value();
}
++num_user_installed.value();
}
}
return std::make_tuple(num_diy_apps_user_installed, num_user_installed);
}
} // namespace web_app