| // 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> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "ash/constants/web_app_id_constants.h" |
| #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/containers/map_util.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/strings/strcat.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/to_string.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/web_applications/commands/manifest_silent_update_command.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-data-view.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/web_app.pb.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/url_pattern_with_regex_matcher.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_install_info.h" |
| #include "chrome/browser/web_applications/web_app_install_utils.h" |
| #include "chrome/browser/web_applications/web_app_management_type.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_scope.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 "chrome/common/pref_names.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 "services/network/public/cpp/permissions_policy/permissions_policy_declaration.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" |
| #endif |
| |
| namespace web_app { |
| namespace { |
| using InstallStateSet = base::EnumSet<proto::InstallState, |
| proto::InstallState_MIN, |
| proto::InstallState_MAX>; |
| |
| // ChromeOS stores the per-app capturing setting in PreferredAppsImpl, not here. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| BASE_FEATURE(kDiyAppsDefaultCaptureForcedOff, |
| "capture_forced_off_diy_apps", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| #endif |
| |
| struct AppStateForNavigationCapturing { |
| bool is_diy_app = false; |
| bool is_preinstalled_browser_tab_app = false; |
| bool client_mode_valid_and_specified = false; |
| }; |
| |
| // ChromeOS stores the per-app capturing setting in PreferredAppsImpl, not here. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| bool IsNavigationCapturingSettingOffByDefault( |
| AppStateForNavigationCapturing app_state) { |
| // If the app is a DIY app, capture navigations by default unless enforced via |
| // flag. |
| if (app_state.is_diy_app && |
| base::FeatureList::IsEnabled(kDiyAppsDefaultCaptureForcedOff)) { |
| return true; |
| } |
| |
| // If the app is a preinstalled app that opens in a new browser tab, then |
| // prevent disabling capturing if the |
| // kPreinstalledBrowserTabWebAppsCaptureOnDefault flag is used. This is a |
| // stopgap in case we need to disable the setting by default, but want to keep |
| // the preinstalled apps having it on by default. |
| if (app_state.is_preinstalled_browser_tab_app && |
| base::FeatureList::IsEnabled( |
| kPreinstalledBrowserTabWebAppsCaptureOnDefault)) { |
| return false; |
| } |
| |
| switch (features::kNavigationCapturingDefaultState.Get()) { |
| case features::CapturingState::kDefaultOff: |
| case features::CapturingState::kReimplDefaultOff: |
| return true; |
| case features::CapturingState::kDefaultOn: |
| case features::CapturingState::kReimplDefaultOn: |
| return false; |
| case features::CapturingState::kReimplOnViaClientMode: |
| // Disallow navigation capturing if the client mode is invalid or not |
| // specified in the manifest. |
| return !app_state.client_mode_valid_and_specified; |
| } |
| } |
| #endif |
| |
| bool IsAppCapturingSettingForcedOff(const webapps::AppId& app_id) { |
| if (!features::kForcedOffCapturingAppsUserSetting.Get().empty()) { |
| std::vector<std::string> forced_capturing_off_user_app_ids = |
| base::SplitString(features::kForcedOffCapturingAppsUserSetting.Get(), |
| ",", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| for (const std::string& forced_capturing_off_user_app_id : |
| forced_capturing_off_user_app_ids) { |
| if (app_id == forced_capturing_off_user_app_id) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Note: This can never return kBrowser. This is because the user has |
| // specified that the web app should be displayed in a window, and thus |
| // the lowest fallback that we can go to is kMinimalUi. |
| DisplayMode ResolveAppDisplayModeForStandaloneLaunchContainer( |
| DisplayMode app_display_mode) { |
| switch (app_display_mode) { |
| case DisplayMode::kBrowser: |
| case DisplayMode::kMinimalUi: |
| return DisplayMode::kMinimalUi; |
| case DisplayMode::kUndefined: |
| case DisplayMode::kPictureInPicture: |
| NOTREACHED(); |
| case DisplayMode::kStandalone: |
| case DisplayMode::kFullscreen: |
| return DisplayMode::kStandalone; |
| case DisplayMode::kWindowControlsOverlay: |
| return DisplayMode::kWindowControlsOverlay; |
| case DisplayMode::kTabbed: |
| if (base::FeatureList::IsEnabled(blink::features::kDesktopPWAsTabStrip)) { |
| return DisplayMode::kTabbed; |
| } else { |
| return DisplayMode::kStandalone; |
| } |
| case DisplayMode::kBorderless: |
| return DisplayMode::kBorderless; |
| } |
| } |
| |
| // Resolve the final display mode based on the user display mode first, then the |
| // DisplayMode overrides if any. If that doesn't end up happening, fallback to |
| // using the manifest provided value to resolve the final display mode. |
| DisplayMode ResolveNonIsolatedEffectiveDisplayMode( |
| DisplayMode app_display_mode, |
| const std::vector<DisplayMode>& display_mode_overrides, |
| mojom::UserDisplayMode user_display_mode) { |
| // First, try to resolve the DisplayMode based on the user choice of opening |
| // in a tab. Fall through to the next logic if that does not work. At that |
| // point, it is guaranteed to open in a standalone container. |
| if (user_display_mode == mojom::UserDisplayMode::kBrowser) { |
| return DisplayMode::kBrowser; |
| } |
| |
| if (user_display_mode == mojom::UserDisplayMode::kTabbed && |
| base::FeatureList::IsEnabled(features::kDesktopPWAsTabStripSettings)) { |
| return DisplayMode::kTabbed; |
| } |
| |
| // Second, try to resolve the DisplayMode based on the `DisplayMode` |
| // overrides. |
| for (DisplayMode override_display_mode : display_mode_overrides) { |
| DisplayMode resolved_display_mode = |
| ResolveAppDisplayModeForStandaloneLaunchContainer( |
| override_display_mode); |
| if (override_display_mode == resolved_display_mode) { |
| return resolved_display_mode; |
| } |
| } |
| |
| // If the `DisplayMode` has still not been resolved, fallback to using the |
| // manifest provided display mode, except for certain use-cases. Please look |
| // at the comment above `ResolveAppDisplayModeForStandaloneLaunchContainer()` |
| // to understand more about it. |
| return ResolveAppDisplayModeForStandaloneLaunchContainer(app_display_mode); |
| } |
| |
| // When user_display_mode indicates a user preference for opening in |
| // a browser tab, we open in a browser tab. If the developer has specified |
| // the app should utilize more advanced display modes and/or fallback chain, |
| // attempt honor those preferences. Otherwise, we open in a standalone |
| // window (for app_display_mode 'standalone' or 'fullscreen'), or a minimal-ui |
| // window (for app_display_mode 'browser' or 'minimal-ui'). |
| // |
| // |is_isolated| overrides browser display mode for Isolated Web Apps because |
| // they can't be open as a tab. |
| DisplayMode ResolveEffectiveDisplayMode( |
| DisplayMode app_display_mode, |
| const std::vector<DisplayMode>& app_display_mode_overrides, |
| mojom::UserDisplayMode user_display_mode, |
| bool is_isolated) { |
| const DisplayMode resolved_display_mode = |
| ResolveNonIsolatedEffectiveDisplayMode( |
| app_display_mode, app_display_mode_overrides, user_display_mode); |
| // TODO(https://crbug.com/389919693): Remove this if display mode restrictions |
| // are added to the WebAppProvider system. |
| if (is_isolated && (resolved_display_mode == DisplayMode::kMinimalUi || |
| resolved_display_mode == DisplayMode::kTabbed)) { |
| return DisplayMode::kStandalone; |
| } |
| CHECK(!(is_isolated && resolved_display_mode == DisplayMode::kBrowser)); |
| |
| return resolved_display_mode; |
| } |
| |
| } // namespace |
| |
| BASE_FEATURE(kPreinstalledBrowserTabWebAppsCaptureOnDefault, |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| BASE_FEATURE(kPreinstalledBrowserTabWebAppsForcedDefaultCaptureOff, |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| // static |
| bool WebAppRegistrar::IsSupportedDisplayModeForNavigationCapture( |
| blink::mojom::DisplayMode display_mode) { |
| // Explicitly disable navigation capturing on display modes that aren't |
| // supported. |
| switch (display_mode) { |
| case blink::mojom::DisplayMode::kUndefined: |
| case blink::mojom::DisplayMode::kPictureInPicture: |
| return false; |
| case blink::mojom::DisplayMode::kBrowser: |
| case blink::mojom::DisplayMode::kFullscreen: |
| case blink::mojom::DisplayMode::kMinimalUi: |
| case blink::mojom::DisplayMode::kWindowControlsOverlay: |
| case blink::mojom::DisplayMode::kBorderless: |
| case blink::mojom::DisplayMode::kStandalone: |
| case blink::mojom::DisplayMode::kTabbed: |
| return true; |
| } |
| } |
| |
| WebAppRegistrar::WebAppRegistrar(Profile* profile) : profile_(profile) {} |
| |
| WebAppRegistrar::~WebAppRegistrar() { |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnAppRegistrarDestroyed(); |
| } |
| } |
| |
| network::ParsedPermissionsPolicy WebAppRegistrar::GetPermissionsPolicy( |
| const webapps::AppId& app_id) const { |
| auto* web_app = GetAppById(app_id); |
| return web_app ? web_app->permissions_policy() |
| : network::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( |
| const webapps::AppId& app_id) { |
| DVLOG(1) << "NotifyWebAppProtocolSettingsChanged"; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppProtocolSettingsChanged(app_id); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppFileHandlerApprovalStateChanged( |
| const webapps::AppId& app_id) { |
| DVLOG(1) << "NotifyWebAppFileHandlerApprovalStateChanged"; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppFileHandlerApprovalStateChanged(app_id); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppsWillBeUpdatedFromSync( |
| const std::vector<const WebApp*>& new_apps_state) { |
| DVLOG(1) << "NotifyWebAppsWillBeUpdatedFromSync"; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppsWillBeUpdatedFromSync(new_apps_state); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppDisabledStateChanged( |
| const webapps::AppId& app_id, |
| bool is_disabled) { |
| DVLOG(1) << "NotifyWebAppDisabledStateChanged " << app_id << ", " |
| << is_disabled; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppDisabledStateChanged(app_id, is_disabled); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppsDisabledModeChanged() { |
| DVLOG(1) << "NotifyWebAppsDisabledModeChanged"; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppsDisabledModeChanged(); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppLastBadgingTimeChanged( |
| const webapps::AppId& app_id, |
| const base::Time& time) { |
| DVLOG(1) << "NotifyWebAppLastBadgingTimeChanged " << app_id << ", " << time; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppLastBadgingTimeChanged(app_id, time); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppLastLaunchTimeChanged( |
| const webapps::AppId& app_id, |
| const base::Time& time) { |
| DVLOG(1) << "NotifyWebAppLastLaunchTimeChanged " << app_id << ", " << time; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppLastLaunchTimeChanged(app_id, time); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppFirstInstallTimeChanged( |
| const webapps::AppId& app_id, |
| const base::Time& time) { |
| DVLOG(1) << "NotifyWebAppFirstInstallTimeChanged " << app_id << ", " << time; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppFirstInstallTimeChanged(app_id, time); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppUserDisplayModeChanged( |
| const webapps::AppId& app_id, |
| mojom::UserDisplayMode user_display_mode) { |
| DVLOG(1) << "NotifyWebAppUserDisplayModeChanged " << app_id << ", " |
| << 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) { |
| DVLOG(1) << "NotifyWebAppRunOnOsLoginModeChanged " << app_id << ", " |
| << run_on_os_login_mode; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppRunOnOsLoginModeChanged(app_id, run_on_os_login_mode); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppSettingsPolicyChanged() { |
| DVLOG(1) << "NotifyWebAppSettingsPolicyChanged"; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppSettingsPolicyChanged(); |
| } |
| } |
| |
| void WebAppRegistrar::NotifyWebAppEffectiveScopeChanged( |
| const webapps::AppId& app_id) { |
| std::optional<WebAppScope> scope = GetEffectiveScope(app_id); |
| if (!scope) { |
| return; |
| } |
| |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppEffectiveScopeChanged(app_id, *scope); |
| } |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| void WebAppRegistrar::NotifyWebAppUserLinkCapturingPreferencesChanged( |
| const webapps::AppId& app_id, |
| bool is_preferred) { |
| DVLOG(1) << "NotifyWebAppUserLinkCapturingPreferencesChanged " << app_id |
| << ", " << is_preferred; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppUserLinkCapturingPreferencesChanged(app_id, is_preferred); |
| } |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| void WebAppRegistrar::NotifyPendingUpdateInfoChanged( |
| const webapps::AppId& app_id, |
| bool pending_update_available, |
| base::PassKey<ManifestSilentUpdateCommand>) { |
| DVLOG(1) << "NotifyPendingUpdateInfoChanged " << app_id << ", " |
| << pending_update_available; |
| for (WebAppRegistrarObserver& observer : observers_) { |
| observer.OnWebAppPendingUpdateChanged(app_id, pending_update_available); |
| } |
| } |
| |
| 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 (!IsInstallState(app_id, |
| {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE, |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION})) { |
| 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 (!IsInstallState(app_id, |
| {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE, |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION})) { |
| 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(); |
| } |
| |
| std::optional<WebAppScope> WebAppRegistrar::GetEffectiveScope( |
| const webapps::AppId& app_id) const { |
| const WebApp* web_app = GetAppById(app_id); |
| if (!web_app) { |
| return std::nullopt; |
| } |
| |
| return web_app->GetScope(); |
| } |
| |
| bool WebAppRegistrar::IsUrlInAppScope(const GURL& url, |
| const webapps::AppId& app_id) const { |
| return GetUrlInAppScopeScore(url, app_id) > 0; |
| } |
| |
| bool WebAppRegistrar::IsUrlInAppExtendedScope( |
| const GURL& url, |
| const webapps::AppId& app_id) const { |
| return GetAppExtendedScopeScore(url, app_id) > 0; |
| } |
| |
| int WebAppRegistrar::GetAppExtendedScopeScore( |
| const GURL& url, |
| const webapps::AppId& app_id) const { |
| std::optional<WebAppScope> scope = GetEffectiveScope(app_id); |
| if (!scope) { |
| return 0; |
| } |
| return scope->GetScopeScore(url); |
| } |
| |
| int WebAppRegistrar::GetUrlInAppScopeScore(const GURL& url, |
| const webapps::AppId& app_id) const { |
| std::optional<WebAppScope> scope = GetEffectiveScope(app_id); |
| if (!scope) { |
| return 0; |
| } |
| return scope->GetScopeScore(url, {.exclude_scope_extensions = true}); |
| } |
| |
| 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_WITH_OS_INTEGRATION})) { |
| return DisplayMode::kBrowser; |
| } |
| |
| bool is_isolated = IsIsolated(app_id); |
| |
| 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; |
| } |
| // TODO(https://crbug.com/389919693): Remove this if display mode restrictions |
| // are added to the WebAppProvider system. |
| if (is_isolated) { |
| user_display_mode = mojom::UserDisplayMode::kStandalone; |
| } |
| |
| std::vector<DisplayMode> display_mode_overrides = |
| GetAppDisplayModeOverride(app_id); |
| return ResolveEffectiveDisplayMode(app_display_mode, display_mode_overrides, |
| *user_display_mode, is_isolated); |
| } |
| |
| 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; |
| } |
| |
| const GURL& WebAppRegistrar::GetAppNewTabUrl( |
| const webapps::AppId& app_id) const { |
| if (IsTabbedWindowModeEnabled(app_id)) { |
| auto* web_app = GetAppById(app_id); |
| if (!web_app) { |
| return GURL::EmptyGURL(); |
| } |
| |
| if (web_app->tab_strip()) { |
| const 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() && |
| std::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; |
| } |
| |
| bool WebAppRegistrar::IsUrlInHomeTabScope(const GURL& url, |
| const webapps::AppId& app_id) const { |
| if (!IsTabbedWindowModeEnabled(app_id)) { |
| return false; |
| } |
| |
| if (!IsUrlInAppScope(url, app_id)) { |
| return false; |
| } |
| |
| // Retrieve the start URL for the app. Start URL is always in home tab scope. |
| // TODO(b/330640982): rename GetAppPinnedHomeTabUrl() to something more |
| // sensible. |
| std::optional<GURL> pinned_home_url = GetAppPinnedHomeTabUrl(app_id); |
| if (!pinned_home_url) { |
| return false; |
| } |
| |
| // We ignore hash ref when deciding what should be opened as the home tab. |
| GURL::Replacements replacements; |
| replacements.ClearRef(); |
| if (url.ReplaceComponents(replacements) == |
| pinned_home_url.value().ReplaceComponents(replacements)) { |
| return true; |
| } |
| |
| for (auto& matcher : GetAppById(app_id)->GetTabbedModeHomeScope()) { |
| if (matcher.Match(url)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::optional<proto::os_state::WebAppOsIntegration> |
| 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 |
| |
| std::vector<apps::IconInfo> WebAppRegistrar::GetTrustedAppIconsMetadata( |
| const webapps::AppId& app_id) const { |
| std::vector<apps::IconInfo> trusted_app_icons; |
| auto* web_app = GetAppById(app_id); |
| if (!web_app) { |
| return trusted_app_icons; |
| } |
| |
| return web_app->trusted_icons().empty() ? web_app->manifest_icons() |
| : web_app->trusted_icons(); |
| } |
| |
| std::optional<apps::IconInfo> |
| WebAppRegistrar::GetSingleTrustedAppIconForSecuritySurfaces( |
| const webapps::AppId& app_id, |
| const SquareSizePx input_size) { |
| auto trusted_app_icons = GetTrustedAppIconsMetadata(app_id); |
| if (trusted_app_icons.empty()) { |
| return std::nullopt; |
| } |
| |
| // This is the ideal case, where there is a single trusted icon to be used by |
| // the system, so return that information and exit early. |
| if (trusted_app_icons.size() == 1) { |
| return trusted_app_icons.at(0); |
| } |
| |
| // First, choose the icon that is closest to `input_size` but larger, to |
| // prefer downscaling a larger icon instead of upscaling a smaller one if |
| // required. |
| std::map<SquareSizePx, apps::IconInfo> size_to_info; |
| for (const auto& icon : trusted_app_icons) { |
| if (!icon.square_size_px.has_value()) { |
| continue; |
| } |
| size_to_info.emplace(icon.square_size_px.value(), icon); |
| } |
| auto icon_size_greater_or_equal = size_to_info.lower_bound(input_size); |
| if (icon_size_greater_or_equal != size_to_info.end()) { |
| return icon_size_greater_or_equal->second; |
| } |
| |
| // If no icons are found, choose the one that has a size closest to |
| // `input_size` but smaller. |
| CHECK(icon_size_greater_or_equal != size_to_info.begin()); |
| auto less_than_required_icon_size = icon_size_greater_or_equal; |
| --less_than_required_icon_size; |
| return less_than_required_icon_size->second; |
| } |
| |
| const WebApp* WebAppRegistrar::GetAppById(const webapps::AppId& app_id) const { |
| return base::FindPtrOrNull(registry_, app_id); |
| } |
| |
| const WebApp* WebAppRegistrar::GetAppByStartUrl(const GURL& start_url) const { |
| for (auto const& it : registry_) { |
| if (it.second->start_url() == start_url) { |
| return it.second.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| std::vector<webapps::AppId> |
| WebAppRegistrar::GetAppsFromSyncAndPendingInstallation() const { |
| AppSet apps_in_sync_install(this, [](const WebApp& web_app) { |
| return 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(this, [](const WebApp& web_app) { |
| return !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() { |
| auto user_installed_app_count = CountTotalUserInstalledAppsIncludingDiy(); |
| int num_user_installed_apps = |
| std::get<InstallableAppCount>(user_installed_app_count).value(); |
| int num_user_installed_diy_apps = |
| std::get<DiyAppCount>(user_installed_app_count).value(); |
| int num_non_syncing_apps = |
| std::get<NonSyncingAppCount>(user_installed_app_count).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 (IsSyncEnabledForApps(profile_)) { |
| base::UmaHistogramCounts1000("WebApp.InstalledCount.NotSyncing.SyncEnabled", |
| num_non_syncing_apps); |
| } else { |
| base::UmaHistogramCounts1000( |
| "WebApp.InstalledCount.ByUserNotLocallyInstalled.SyncDisabled", |
| num_non_locally_installed); |
| } |
| |
| auto app_icons_count = CountAppsHavingTrustedIcons(); |
| base::UmaHistogramCounts1000( |
| "WebApp.InstalledCount.HasTrustedIcons", |
| std::get<AppsHavingTrustedIconsCount>(app_icons_count).value()); |
| base::UmaHistogramCounts1000( |
| "WebApp.InstalledCount.HasNoTrustedIcons", |
| std::get<AppsHavingNoTrustedIconsCount>(app_icons_count).value()); |
| |
| #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 (const 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::IsInRegistrar(const webapps::AppId& app_id) const { |
| const WebApp* web_app = GetAppById(app_id); |
| if (!web_app || web_app->is_uninstalling()) { |
| return false; |
| } |
| |
| // `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 false; |
| } |
| return true; |
| } |
| |
| std::optional<proto::InstallState> WebAppRegistrar::GetInstallState( |
| const webapps::AppId& app_id) const { |
| if (!IsInRegistrar(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()); |
| } |
| |
| bool WebAppRegistrar::AppMatches(const webapps::AppId& app_id, |
| const WebAppFilter& filter) const { |
| std::optional<proto::InstallState> install_state = GetInstallState(app_id); |
| if (install_state == std::nullopt) { |
| return false; |
| } |
| |
| if (install_state == proto::SUGGESTED_FROM_ANOTHER_DEVICE) { |
| return filter.is_suggested_app_; |
| } |
| |
| if (filter.opens_in_browser_tab_) { |
| return GetAppEffectiveDisplayMode(app_id) == DisplayMode::kBrowser; |
| } |
| |
| if (filter.opens_in_dedicated_window_) { |
| return GetAppEffectiveDisplayMode(app_id) != DisplayMode::kBrowser; |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| if (filter.captures_links_in_scope_) { |
| return CapturesLinksInScope(app_id); |
| } |
| #endif |
| |
| if (filter.is_isolated_app_) { |
| return IsIsolated(app_id); |
| } |
| |
| if (filter.is_policy_installed_iwa) { |
| return IsIsolated(app_id) && IsInstalledByPolicy(app_id); |
| } |
| |
| if (filter.is_crafted_app_) { |
| return !IsDiyApp(app_id); |
| } |
| |
| if (filter.is_diy_with_os_shortcut_) { |
| const WebApp* app = GetAppById(app_id); |
| return app && app->is_diy_app() && |
| install_state == proto::INSTALLED_WITH_OS_INTEGRATION; |
| } |
| |
| if (filter.displays_badge_on_os_ || filter.supports_os_notifications_) { |
| return install_state == proto::INSTALLED_WITH_OS_INTEGRATION; |
| } |
| |
| if (filter.installed_in_chrome_) { |
| return install_state == proto::INSTALLED_WITH_OS_INTEGRATION || |
| install_state == proto::INSTALLED_WITHOUT_OS_INTEGRATION; |
| } |
| |
| if (filter.installed_in_os_) { |
| return install_state == proto::INSTALLED_WITH_OS_INTEGRATION; |
| } |
| |
| if (filter.launchable_from_install_api_) { |
| const WebApp* app = GetAppById(app_id); |
| return (app && app->WasInstalledByUser()) || |
| GetAppEffectiveDisplayMode(app_id) != DisplayMode::kBrowser; |
| } |
| |
| return false; |
| } |
| |
| std::optional<webapps::AppId> WebAppRegistrar::FindBestAppWithUrlInScope( |
| const GURL& url, |
| const WebAppFilter& filter) const { |
| if (!url.is_valid()) { |
| return std::nullopt; |
| } |
| |
| std::optional<webapps::AppId> best_app_id; |
| int best_score = 0; |
| |
| for (const webapps::AppId& app_id : |
| GetAppIdsForAppSet(GetAppsIncludingStubs())) { |
| if (!GetAppScope(app_id).is_valid()) { |
| continue; |
| } |
| |
| if (GetInstallState(app_id) == proto::SUGGESTED_FROM_ANOTHER_DEVICE && |
| !filter.is_suggested_app_) { |
| continue; |
| } |
| |
| int score = GetAppExtendedScopeScore(url, app_id); |
| if (score > 0 && score > best_score) { |
| best_app_id = app_id; |
| best_score = score; |
| } |
| } |
| |
| if (best_app_id.has_value() && AppMatches(best_app_id.value(), filter)) { |
| return best_app_id; |
| } |
| |
| return std::nullopt; |
| } |
| |
| // Returns all apps that have the given `url` in scope and match the filter. |
| std::vector<webapps::AppId> WebAppRegistrar::FindAllAppsNestedInUrl( |
| const GURL& outer_scope, |
| const WebAppFilter& filter) const { |
| 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 (!AppMatches(app_id, filter)) { |
| 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::IsUninstalling(const webapps::AppId& app_id) const { |
| const WebApp* web_app = GetAppById(app_id); |
| return web_app && web_app->is_uninstalling(); |
| } |
| |
| 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 (!IsInstallState( |
| app_id, {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION})) { |
| 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::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::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 (!IsInstallState( |
| url_info.app_id(), |
| {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION})) { |
| 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 (IsAppCapturingSettingForcedOff(app_id)) { |
| return false; |
| } |
| if (!base::FeatureList::IsEnabled(features::kPwaNavigationCapturing) |
| #if BUILDFLAG(IS_CHROMEOS) |
| && !ChromeOsWebAppExperiments:: |
| IsNavigationCapturingReimplEnabledForTargetApp(app_id) |
| #endif |
| ) { |
| return false; |
| } |
| if (!IsInstallState(app_id, |
| {proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION}) || |
| !IsSupportedDisplayModeForNavigationCapture( |
| GetAppEffectiveDisplayMode(app_id))) { |
| return false; |
| } |
| return true; |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| 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); |
| bool is_preinstalled_browser_tab_app = |
| (web_app->GetSources() == |
| WebAppManagementTypes({WebAppManagement::Type::kDefault}) && |
| web_app->user_display_mode() == mojom::UserDisplayMode::kBrowser); |
| |
| switch (web_app->user_link_capturing_preference()) { |
| case proto::NAVIGATION_CAPTURING_PREFERENCE_DEFAULT: |
| if (IsNavigationCapturingSettingOffByDefault( |
| {.is_diy_app = web_app->is_diy_app(), |
| .is_preinstalled_browser_tab_app = |
| is_preinstalled_browser_tab_app, |
| .client_mode_valid_and_specified = |
| web_app->launch_handler() |
| .value_or(LaunchHandler()) |
| .client_mode_valid_and_specified()})) { |
| return false; |
| } |
| break; |
| case proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE: |
| return true; |
| case proto::NAVIGATION_CAPTURING_PREFERENCE_DO_NOT_CAPTURE: |
| return false; |
| } |
| |
| // This is a stop gap in case there are issues concerning preinstalled apps |
| // automatically capturing links by default post navigation capturing launch. |
| if (is_preinstalled_browser_tab_app && |
| base::FeatureList::IsEnabled( |
| kPreinstalledBrowserTabWebAppsForcedDefaultCaptureOff)) { |
| 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::NAVIGATION_CAPTURING_PREFERENCE_DEFAULT: |
| app_and_install_time.emplace_back(other_app_id, |
| other_app->first_install_time()); |
| break; |
| case proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE: |
| return false; |
| case proto::NAVIGATION_CAPTURING_PREFERENCE_DO_NOT_CAPTURE: |
| 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, 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, app); |
| } |
| if (app_score == 0) { |
| return false; |
| } |
| return std::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, app_id); |
| } |
| return IsInstallState( |
| app_id, {proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION}) && |
| 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; |
| } |
| |
| 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); |
| } |
| #endif |
| |
| 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::IsDiyApp(const webapps::AppId& app_id) const { |
| if (!IsInstallState(app_id, |
| {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE, |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION})) { |
| return false; |
| } |
| const WebApp* web_app = GetAppById(app_id); |
| return web_app && web_app->is_diy_app(); |
| } |
| |
| std::vector<blink::Manifest::RelatedApplication> |
| WebAppRegistrar::GetRelatedApplications(const webapps::AppId& app_id) const { |
| const WebApp* web_app = GetAppById(app_id); |
| return web_app ? web_app->related_applications() |
| : std::vector<blink::Manifest::RelatedApplication>(); |
| } |
| |
| 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; |
| } |
| |
| const bool user_disallowed = |
| web_app->file_handler_approval_state() == ApiApprovalState::kDisallowed; |
| |
| // DefaultHandlersForFileExtensions policy can define file |
| // handlers for apps. Its value takes precedence over user |
| // choice. |
| const bool policy_forced = |
| provider_->registrar_unsafe() |
| .IsAppSetAsPolicyDefinedFileHandlerForAnyFileExtension(app_id); |
| return user_disallowed && !policy_forced; |
| } |
| |
| ApiApprovalState WebAppRegistrar::GetAppFileHandlerApprovalState( |
| const webapps::AppId& app_id, |
| std::optional<std::string> file_extension) const { |
| if (file_extension && IsAppPolicyDefinedHandlerForFileExtension( |
| app_id, file_extension.value())) { |
| return ApiApprovalState::kAllowed; |
| } |
| return GetAppFileHandlerUserApprovalState(app_id); |
| } |
| |
| bool WebAppRegistrar::IsAppPolicyDefinedHandlerForFileExtension( |
| const webapps::AppId& app_id, |
| const std::string file_extension) const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| const std::string* file_extension_policy_id = |
| profile_->GetPrefs() |
| ->GetDict(prefs::kDefaultHandlersForFileExtensions) |
| .FindString(file_extension); |
| if (!file_extension_policy_id) { |
| return false; |
| } |
| |
| const WebApp* web_app = GetAppById(app_id); |
| if (!web_app) { |
| return false; |
| } |
| |
| std::optional<std::vector<std::string>> app_policy_ids = |
| WebAppPolicyManager::GetPolicyIds(profile(), *web_app); |
| |
| if (!app_policy_ids->empty()) { |
| return base::Contains(app_policy_ids.value(), *file_extension_policy_id); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| return false; |
| } |
| |
| bool WebAppRegistrar::IsAppSetAsPolicyDefinedFileHandlerForAnyFileExtension( |
| const webapps::AppId& app_id) const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| const base::Value::Dict& default_handlers = |
| profile_->GetPrefs()->GetDict(prefs::kDefaultHandlersForFileExtensions); |
| |
| const WebApp* web_app = GetAppById(app_id); |
| if (!web_app) { |
| return false; |
| } |
| |
| std::optional<std::vector<std::string>> app_policy_ids = |
| WebAppPolicyManager::GetPolicyIds(profile(), *web_app); |
| |
| if (!app_policy_ids->empty()) { |
| return std::ranges::any_of(default_handlers, [&](const auto& handler) { |
| return base::Contains(*app_policy_ids, handler.second.GetString()); |
| }); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| return false; |
| } |
| |
| ApiApprovalState WebAppRegistrar::GetAppFileHandlerUserApprovalState( |
| 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; |
| } |
| |
| 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; |
| } |
| |
| if (!web_app->scope().is_valid()) { |
| 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; |
| } |
| |
| if (GetInstallState(app_id) != proto::INSTALLED_WITH_OS_INTEGRATION) { |
| return mojom::UserDisplayMode::kBrowser; |
| } |
| |
| 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>(); |
| } |
| |
| 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(); |
| } |
| |
| base::Time WebAppRegistrar::GetAppLatestInstallTime( |
| const webapps::AppId& app_id) const { |
| auto* web_app = GetAppById(app_id); |
| return web_app ? web_app->latest_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::GetAppTrustedIconSizesFallbackToUntrusted( |
| const webapps::AppId& app_id) const { |
| auto* web_app = GetAppById(app_id); |
| if (!web_app) { |
| return SortedSizesPx(); |
| } |
| |
| SortedSizesPx sorted_sizes; |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| if (!web_app->stored_trusted_icon_sizes(IconPurpose::MASKABLE).empty()) { |
| return web_app->stored_trusted_icon_sizes(IconPurpose::MASKABLE); |
| } |
| #endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| |
| if (!web_app->stored_trusted_icon_sizes(IconPurpose::ANY).empty()) { |
| return web_app->stored_trusted_icon_sizes(IconPurpose::ANY); |
| } |
| |
| return web_app->downloaded_icon_sizes(IconPurpose::ANY); |
| } |
| |
| 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 true; }); |
| } |
| |
| WebAppRegistrar::AppSet WebAppRegistrar::GetApps() const { |
| return AppSet(this, [](const WebApp& web_app) { |
| return !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); |
| } |
| std::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 !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::os_state::WebAppOsIntegration()); |
| web_app2.SetCurrentOsIntegrationStates( |
| proto::os_state::WebAppOsIntegration()); |
| // Tests that want to ignore current os integration state usually also |
| // want to ignore the presence/absece of the "user installed" source, as |
| // that is something else that is not synced across. |
| // TODO(https://crbug.com/372062068): Figure out a better way to handle |
| // differences in installed state. |
| web_app.RemoveSource(WebAppManagement::kUserInstalled); |
| web_app2.RemoveSource(WebAppManagement::kUserInstalled); |
| } |
| 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, NonSyncingAppCount> |
| WebAppRegistrar::CountTotalUserInstalledAppsIncludingDiy() const { |
| InstallableAppCount num_user_installed(0); |
| DiyAppCount num_diy_apps_user_installed(0); |
| NonSyncingAppCount num_non_syncing_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(); |
| } |
| if (!app.IsSynced()) { |
| ++num_non_syncing_user_installed.value(); |
| } |
| ++num_user_installed.value(); |
| } |
| } |
| return std::make_tuple(num_diy_apps_user_installed, num_user_installed, |
| num_non_syncing_user_installed); |
| } |
| |
| std::tuple<AppsHavingNoTrustedIconsCount, AppsHavingTrustedIconsCount> |
| WebAppRegistrar::CountAppsHavingTrustedIcons() const { |
| AppsHavingNoTrustedIconsCount num_apps_no_trusted_icons(0); |
| AppsHavingTrustedIconsCount num_apps_trusted_icons(0); |
| for (const WebApp& app : GetApps()) { |
| if (app.trusted_icons().empty()) { |
| ++num_apps_no_trusted_icons.value(); |
| } else { |
| ++num_apps_trusted_icons.value(); |
| } |
| } |
| return std::make_tuple(num_apps_no_trusted_icons, num_apps_trusted_icons); |
| } |
| |
| bool WebAppRegistrar::IsDiyAppIconsMarkedMaskedOnMac( |
| const webapps::AppId& app_id) const { |
| const WebApp* app = GetAppById(app_id); |
| return app && app->is_diy_app() && app->diy_app_icons_masked_on_mac(); |
| } |
| |
| } // namespace web_app |