|  | // 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_utils.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iterator> | 
|  | #include <map> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <utility> | 
|  |  | 
|  | #include "ash/constants/web_app_id_constants.h" | 
|  | #include "base/base64.h" | 
|  | #include "base/check.h" | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/containers/enum_set.h" | 
|  | #include "base/containers/flat_set.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/notreached.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/values.h" | 
|  | #include "build/build_config.h" | 
|  | #include "build/buildflag.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h" | 
|  | #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" | 
|  | #include "chrome/browser/web_applications/web_app_constants.h" | 
|  | #include "chrome/browser/web_applications/web_app_icon_manager.h" | 
|  | #include "chrome/browser/web_applications/web_app_install_info.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.h" | 
|  | #include "chrome/common/chrome_constants.h" | 
|  | #include "chrome/common/chrome_features.h" | 
|  | #include "chrome/common/chrome_isolated_world_ids.h" | 
|  | #include "chrome/grit/generated_resources.h" | 
|  | #include "components/grit/components_resources.h" | 
|  | #include "components/services/app_service/public/cpp/app_launch_util.h" | 
|  | #include "components/services/app_service/public/cpp/run_on_os_login_types.h" | 
|  | #include "components/site_engagement/content/site_engagement_service.h" | 
|  | #include "content/public/browser/navigation_handle.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/browser/web_contents_observer.h" | 
|  | #include "content/public/common/alternative_error_page_override_info.mojom-forward.h" | 
|  | #include "content/public/common/alternative_error_page_override_info.mojom.h" | 
|  | #include "content/public/common/content_features.h" | 
|  | #include "mojo/public/cpp/bindings/struct_ptr.h" | 
|  | #include "third_party/blink/public/common/features.h" | 
|  | #include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h" | 
|  | #include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h" | 
|  | #include "third_party/skia/include/core/SkBitmap.h" | 
|  | #include "ui/base/l10n/l10n_util.h" | 
|  | #include "ui/gfx/codec/png_codec.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | #include "chrome/browser/ash/profiles/profile_helper.h" | 
|  | #include "chromeos/ash/components/browser_context_helper/browser_context_types.h" | 
|  | #include "components/user_manager/user_manager.h" | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | namespace web_app { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | GURL EncodeIconAsUrl(const SkBitmap& bitmap) { | 
|  | std::optional<std::vector<uint8_t>> output = | 
|  | gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, /*discard_transparency=*/false); | 
|  | std::string encoded = | 
|  | base::Base64Encode(output.value_or(std::vector<uint8_t>())); | 
|  | return GURL("data:image/png;base64," + encoded); | 
|  | } | 
|  |  | 
|  | // This class is responsible for fetching the app icon for a web app and for | 
|  | // providing it to the error page that's currently showing. The class | 
|  | // monitors the lifetime of the web_contents for the page and deletes itself | 
|  | // under these conditions: | 
|  | // | 
|  | // 1) It is unable to determine which icon to download. | 
|  | // 2) The error page being monitored (it's web_contents) is destroyed. | 
|  | // 3) The page starts loading something else. | 
|  | // 4) (Success case) The icon is successfully fetched and delivered to the web | 
|  | //    page. | 
|  | // | 
|  | // Note that this class can not rely on downloading the bits off the network | 
|  | // because it has to work even when the app is launched for the first time while | 
|  | // network is disconnected. | 
|  | class AppIconFetcherTask : public content::WebContentsObserver { | 
|  | public: | 
|  | // Starts the asynchronous fetching of a specific web app icon from disk using | 
|  | // the `web_app_provider` and supplies the icon to the web_page via jscript. | 
|  | static void FetchAndPopulateIcon(content::WebContents* web_contents, | 
|  | WebAppProvider* web_app_provider, | 
|  | const webapps::AppId& app_id) { | 
|  | new AppIconFetcherTask(web_contents, web_app_provider, app_id); | 
|  | } | 
|  |  | 
|  | AppIconFetcherTask() = delete; | 
|  |  | 
|  | private: | 
|  | AppIconFetcherTask(content::WebContents* web_contents, | 
|  | WebAppProvider* web_app_provider, | 
|  | const webapps::AppId& app_id) | 
|  | : WebContentsObserver(web_contents) { | 
|  | DCHECK(web_contents); | 
|  | // For best results, this should be of equal (or slightly higher) value than | 
|  | // the width and height of the presented icon on the default offline error | 
|  | // page (see webapp_default_offline.[html|css] for icon details). | 
|  | const int kDesiredSizeForIcon = 160; | 
|  | web_app_provider->icon_manager().ReadIconAndResize( | 
|  | app_id, IconPurpose::ANY, kDesiredSizeForIcon, | 
|  | base::BindOnce(&AppIconFetcherTask::OnIconFetched, | 
|  | weak_factory_.GetWeakPtr(), kDesiredSizeForIcon)); | 
|  | } | 
|  |  | 
|  | // WebContentsObserver: | 
|  | void WebContentsDestroyed() override { delete this; } | 
|  |  | 
|  | void DidStartNavigation( | 
|  | content::NavigationHandle* navigation_handle) override { | 
|  | // Loading will have started already when the error page is being | 
|  | // constructed, so if we receive this event, it means that a new navigation | 
|  | // is taking place (so we can drop any remaining work). | 
|  | if (navigation_handle->IsInPrimaryMainFrame()) { | 
|  | delete this; | 
|  | } | 
|  | } | 
|  |  | 
|  | void DocumentOnLoadCompletedInPrimaryMainFrame() override { | 
|  | document_ready_ = true; | 
|  | MaybeSendImageAndSelfDestruct(); | 
|  | } | 
|  |  | 
|  | void OnIconFetched(int fetched_size, | 
|  | std::map<SquareSizePx, SkBitmap> icon_bitmaps) { | 
|  | DCHECK(icon_bitmaps.size() == 1); | 
|  | DCHECK(icon_bitmaps.begin()->first == fetched_size); | 
|  | if (icon_bitmaps.size() == 0) { | 
|  | delete this; | 
|  | return; | 
|  | } | 
|  | icon_url_ = EncodeIconAsUrl(icon_bitmaps.begin()->second); | 
|  | MaybeSendImageAndSelfDestruct(); | 
|  | } | 
|  |  | 
|  | // This function does nothing until both of these conditions have been met: | 
|  | // 1) The app icon image has been fetched. | 
|  | // 2) The error page is ready to receive the image. | 
|  | // Once they are met, this function will send the icon to the web page and | 
|  | // delete itself. Callers should not assume it is safe to do more work after | 
|  | // calling this function. | 
|  | void MaybeSendImageAndSelfDestruct() { | 
|  | if (!document_ready_ || icon_url_.is_empty()) { | 
|  | return; | 
|  | } | 
|  | DCHECK(web_contents()); | 
|  | DCHECK(icon_url_.is_valid()); | 
|  |  | 
|  | std::u16string app_icon_inline = | 
|  | std::u16string(u"var icon = document.getElementById('icon');") + | 
|  | u"icon.src ='" + base::UTF8ToUTF16(icon_url_.spec()) + u"';"; | 
|  |  | 
|  | content::RenderFrameHost* host = web_contents()->GetPrimaryMainFrame(); | 
|  | host->ExecuteJavaScriptInIsolatedWorld(app_icon_inline, base::DoNothing(), | 
|  | ISOLATED_WORLD_ID_EXTENSIONS); | 
|  |  | 
|  | delete this; | 
|  | } | 
|  |  | 
|  | // This url will contain the fetched icon bits inlined as a data: url. | 
|  | GURL icon_url_; | 
|  |  | 
|  | // Whether the error page is ready to receive the icon. | 
|  | bool document_ready_ = false; | 
|  |  | 
|  | // A weak factory for this class, must be last in the member list. | 
|  | base::WeakPtrFactory<AppIconFetcherTask> weak_factory_{this}; | 
|  | }; | 
|  |  | 
|  | // 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::optional<DisplayMode> TryResolveUserDisplayMode( | 
|  | mojom::UserDisplayMode user_display_mode) { | 
|  | switch (user_display_mode) { | 
|  | case mojom::UserDisplayMode::kBrowser: | 
|  | return DisplayMode::kBrowser; | 
|  | case mojom::UserDisplayMode::kTabbed: | 
|  | if (base::FeatureList::IsEnabled( | 
|  | features::kDesktopPWAsTabStripSettings)) { | 
|  | return DisplayMode::kTabbed; | 
|  | } | 
|  | // Treat as standalone. | 
|  | [[fallthrough]]; | 
|  | case mojom::UserDisplayMode::kStandalone: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | std::optional<DisplayMode> TryResolveOverridesDisplayMode( | 
|  | const std::vector<DisplayMode>& display_mode_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; | 
|  | } | 
|  | } | 
|  |  | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | DisplayMode ResolveNonIsolatedEffectiveDisplayMode( | 
|  | DisplayMode app_display_mode, | 
|  | const std::vector<DisplayMode>& display_mode_overrides, | 
|  | mojom::UserDisplayMode user_display_mode) { | 
|  | const std::optional<DisplayMode> resolved_display_mode = | 
|  | TryResolveUserDisplayMode(user_display_mode); | 
|  |  | 
|  | if (resolved_display_mode.has_value()) { | 
|  | return *resolved_display_mode; | 
|  | } | 
|  |  | 
|  | const std::optional<DisplayMode> resolved_override_display_mode = | 
|  | TryResolveOverridesDisplayMode(display_mode_overrides); | 
|  | if (resolved_override_display_mode.has_value()) { | 
|  | return *resolved_override_display_mode; | 
|  | } | 
|  | return ResolveAppDisplayModeForStandaloneLaunchContainer(app_display_mode); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | constexpr base::FilePath::CharType kManifestResourcesDirectoryName[] = | 
|  | FILE_PATH_LITERAL("Manifest Resources"); | 
|  |  | 
|  | constexpr base::FilePath::CharType kTempDirectoryName[] = | 
|  | FILE_PATH_LITERAL("Temp"); | 
|  |  | 
|  | bool AreWebAppsEnabled(Profile* profile) { | 
|  | if (!profile || profile->IsSystemProfile()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Web Apps should not be installed to the ChromeOS system profiles except the | 
|  | // lock screen app profile. | 
|  | if (!ash::ProfileHelper::IsUserProfile(profile) && | 
|  | !ash::IsShimlessRmaAppBrowserContext(profile)) { | 
|  | return false; | 
|  | } | 
|  | auto* user_manager = user_manager::UserManager::Get(); | 
|  |  | 
|  | // Don't enable for Chrome App Kiosk sessions. | 
|  | if (user_manager && user_manager->IsLoggedInAsKioskApp()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Guest session forces OTR to be turned on. | 
|  | if (profile->IsGuestSession()) { | 
|  | return profile->IsOffTheRecord(); | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | return !profile->IsOffTheRecord(); | 
|  | } | 
|  |  | 
|  | bool AreWebAppsUserInstallable(Profile* profile) { | 
|  | return AreWebAppsEnabled(profile) && !profile->IsGuestSession() && | 
|  | !profile->IsOffTheRecord(); | 
|  | } | 
|  |  | 
|  | content::BrowserContext* GetBrowserContextForWebApps( | 
|  | content::BrowserContext* context) { | 
|  | // Use original profile to create only one KeyedService instance. | 
|  | Profile* profile = Profile::FromBrowserContext(context); | 
|  | if (!profile) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | if (AreWebAppsEnabled(profile)) { | 
|  | return profile; | 
|  | } | 
|  |  | 
|  | // On ChromeOS, the system web app implementation requires that incognito | 
|  | // profiles can be used to look up the WebAppProvider of their original | 
|  | // profile. | 
|  | // TODO(https://crbug.com/384063076): Stop returning for profiles on ChromeOS | 
|  | // where `AreWebAppsEnabled` returns `false`. | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | Profile* original_profile = profile->GetOriginalProfile(); | 
|  | CHECK(original_profile); | 
|  | if (AreWebAppsEnabled(original_profile)) { | 
|  | return original_profile; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | content::BrowserContext* GetBrowserContextForWebAppMetrics( | 
|  | content::BrowserContext* context) { | 
|  | Profile* profile = Profile::FromBrowserContext(context); | 
|  | if (!profile) { | 
|  | return nullptr; | 
|  | } | 
|  | if (!site_engagement::SiteEngagementService::IsEnabled()) { | 
|  | return nullptr; | 
|  | } | 
|  | if (profile->GetOriginalProfile()->IsGuestSession()) { | 
|  | return nullptr; | 
|  | } | 
|  | return GetBrowserContextForWebApps(context); | 
|  | } | 
|  |  | 
|  | base::FilePath GetWebAppsRootDirectory(Profile* profile) { | 
|  | return profile->GetPath().Append(chrome::kWebAppDirname); | 
|  | } | 
|  |  | 
|  | base::FilePath GetManifestResourcesDirectory( | 
|  | const base::FilePath& web_apps_root_directory) { | 
|  | return web_apps_root_directory.Append(kManifestResourcesDirectoryName); | 
|  | } | 
|  |  | 
|  | base::FilePath GetManifestResourcesDirectory(Profile* profile) { | 
|  | return GetManifestResourcesDirectory(GetWebAppsRootDirectory(profile)); | 
|  | } | 
|  |  | 
|  | base::FilePath GetManifestResourcesDirectoryForApp( | 
|  | const base::FilePath& web_apps_root_directory, | 
|  | const webapps::AppId& app_id) { | 
|  | return GetManifestResourcesDirectory(web_apps_root_directory) | 
|  | .AppendASCII(app_id); | 
|  | } | 
|  |  | 
|  | base::FilePath GetWebAppsTempDirectory( | 
|  | const base::FilePath& web_apps_root_directory) { | 
|  | return web_apps_root_directory.Append(kTempDirectoryName); | 
|  | } | 
|  |  | 
|  | std::string GetProfileCategoryForLogging(Profile* profile) { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | if (!ash::ProfileHelper::IsUserProfile(profile)) { | 
|  | return "SigninOrLockScreen"; | 
|  | } else if (user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp()) { | 
|  | return "Kiosk"; | 
|  | } else if (ash::ProfileHelper::IsEphemeralUserProfile(profile)) { | 
|  | return "Ephemeral"; | 
|  | } else if (ash::ProfileHelper::IsPrimaryProfile(profile)) { | 
|  | return "Primary"; | 
|  | } else { | 
|  | return "Other"; | 
|  | } | 
|  | #else | 
|  | // Chrome OS profiles are different from non-ChromeOS ones. Because System Web | 
|  | // Apps are not installed on non Chrome OS, "Other" is returned here. | 
|  | return "Other"; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool IsChromeOsDataMandatory() { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | return true; | 
|  | #else | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool AreAppsLocallyInstalledBySync() { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // On Chrome OS, sync always locally installs an app. | 
|  | return true; | 
|  | #else | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool AreNewFileHandlersASubsetOfOld(const apps::FileHandlers& old_handlers, | 
|  | const apps::FileHandlers& new_handlers) { | 
|  | if (new_handlers.empty()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const std::set<std::string> mime_types_set = | 
|  | apps::GetMimeTypesFromFileHandlers(old_handlers); | 
|  | const std::set<std::string> extensions_set = | 
|  | apps::GetFileExtensionsFromFileHandlers(old_handlers); | 
|  |  | 
|  | for (const apps::FileHandler& new_handler : new_handlers) { | 
|  | for (const auto& new_handler_accept : new_handler.accept) { | 
|  | if (!base::Contains(mime_types_set, new_handler_accept.mime_type)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (const auto& new_extension : new_handler_accept.file_extensions) { | 
|  | if (!base::Contains(extensions_set, new_extension)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::tuple<std::u16string, size_t> | 
|  | GetFileTypeAssociationsHandledByWebAppForDisplay(Profile* profile, | 
|  | const webapps::AppId& app_id) { | 
|  | auto* provider = WebAppProvider::GetForLocalAppsUnchecked(profile); | 
|  | if (!provider) { | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | const apps::FileHandlers* file_handlers = | 
|  | provider->registrar_unsafe().GetAppFileHandlers(app_id); | 
|  |  | 
|  | std::vector<std::u16string> extensions_for_display = | 
|  | TransformFileExtensionsForDisplay( | 
|  | apps::GetFileExtensionsFromFileHandlers(*file_handlers)); | 
|  |  | 
|  | return {base::JoinString(extensions_for_display, | 
|  | l10n_util::GetStringUTF16( | 
|  | IDS_WEB_APP_FILE_HANDLING_LIST_SEPARATOR)), | 
|  | extensions_for_display.size()}; | 
|  | } | 
|  |  | 
|  | std::vector<std::u16string> TransformFileExtensionsForDisplay( | 
|  | const std::set<std::string>& extensions) { | 
|  | std::vector<std::u16string> extensions_for_display; | 
|  | extensions_for_display.reserve(extensions.size()); | 
|  | std::ranges::transform( | 
|  | extensions, std::back_inserter(extensions_for_display), | 
|  | [](const std::string& extension) { | 
|  | return base::UTF8ToUTF16(base::ToUpperASCII(extension.substr(1))); | 
|  | }); | 
|  | return extensions_for_display; | 
|  | } | 
|  |  | 
|  | bool IsRunOnOsLoginModeEnabledForAutostart(RunOnOsLoginMode login_mode) { | 
|  | switch (login_mode) { | 
|  | case RunOnOsLoginMode::kWindowed: | 
|  | return true; | 
|  | case RunOnOsLoginMode::kMinimized: | 
|  | return true; | 
|  | case RunOnOsLoginMode::kNotRun: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool HasAnySpecifiedSourcesAndNoOtherSources( | 
|  | WebAppManagementTypes sources, | 
|  | WebAppManagementTypes specified_sources) { | 
|  | bool has_any_specified_sources = sources.HasAny(specified_sources); | 
|  | bool has_no_other_sources = | 
|  | base::Difference(sources, specified_sources).empty(); | 
|  | return has_any_specified_sources && has_no_other_sources; | 
|  | } | 
|  |  | 
|  | bool CanUserUninstallWebApp(const webapps::AppId& app_id, | 
|  | WebAppManagementTypes sources) { | 
|  | return !WillBeSystemWebApp(app_id, sources) && | 
|  | HasAnySpecifiedSourcesAndNoOtherSources(sources, | 
|  | kUserUninstallableSources); | 
|  | } | 
|  |  | 
|  | webapps::AppId GetAppIdFromAppSettingsUrl(const GURL& url) { | 
|  | // App Settings page is served under chrome://app-settings/<app-id>. | 
|  | // url.path() returns "/<app-id>" with a leading slash. | 
|  | std::string path = url.path(); | 
|  | if (path.size() <= 1) { | 
|  | return webapps::AppId(); | 
|  | } | 
|  | return path.substr(1); | 
|  | } | 
|  |  | 
|  | bool IsInScope(const GURL& url, const GURL& scope) { | 
|  | if (!scope.is_valid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return base::StartsWith(url.spec(), scope.spec(), | 
|  | base::CompareCase::SENSITIVE); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | apps::LaunchContainer ConvertDisplayModeToAppLaunchContainer( | 
|  | DisplayMode display_mode) { | 
|  | switch (display_mode) { | 
|  | case DisplayMode::kBrowser: | 
|  | return apps::LaunchContainer::kLaunchContainerTab; | 
|  | case DisplayMode::kMinimalUi: | 
|  | case DisplayMode::kStandalone: | 
|  | case DisplayMode::kFullscreen: | 
|  | case DisplayMode::kWindowControlsOverlay: | 
|  | case DisplayMode::kTabbed: | 
|  | case DisplayMode::kBorderless: | 
|  | case DisplayMode::kPictureInPicture: | 
|  | return apps::LaunchContainer::kLaunchContainerWindow; | 
|  | case DisplayMode::kUndefined: | 
|  | return apps::LaunchContainer::kLaunchContainerNone; | 
|  | } | 
|  | } | 
|  |  | 
|  | apps::RunOnOsLoginMode ConvertOsLoginMode(RunOnOsLoginMode login_mode) { | 
|  | switch (login_mode) { | 
|  | case RunOnOsLoginMode::kWindowed: | 
|  | return apps::RunOnOsLoginMode::kWindowed; | 
|  | case RunOnOsLoginMode::kNotRun: | 
|  | return apps::RunOnOsLoginMode::kNotRun; | 
|  | case RunOnOsLoginMode::kMinimized: | 
|  | return apps::RunOnOsLoginMode::kUnknown; | 
|  | } | 
|  | } | 
|  |  | 
|  | const char* IconsDownloadedResultToString(IconsDownloadedResult result) { | 
|  | switch (result) { | 
|  | case IconsDownloadedResult::kCompleted: | 
|  | return "Completed"; | 
|  | case IconsDownloadedResult::kPrimaryPageChanged: | 
|  | return "PrimaryPageChanged"; | 
|  | case IconsDownloadedResult::kAbortedDueToFailure: | 
|  | return "AbortedDueToFailure"; | 
|  | } | 
|  | } | 
|  |  | 
|  | content::mojom::AlternativeErrorPageOverrideInfoPtr ConstructWebAppErrorPage( | 
|  | const GURL& url, | 
|  | content::RenderFrameHost* render_frame_host, | 
|  | content::BrowserContext* browser_context, | 
|  | std::u16string message, | 
|  | std::u16string supplementary_icon) { | 
|  | Profile* profile = Profile::FromBrowserContext(browser_context); | 
|  | WebAppProvider* web_app_provider = WebAppProvider::GetForWebApps(profile); | 
|  | if (web_app_provider == nullptr) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | WebAppRegistrar& web_app_registrar = web_app_provider->registrar_unsafe(); | 
|  | const std::optional<webapps::AppId> app_id = | 
|  | web_app_registrar.FindBestAppWithUrlInScope( | 
|  | url, web_app::WebAppFilter::InstalledInChrome()); | 
|  | if (!app_id.has_value()) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Fetch the app icon asynchronously and provide it to the error page. The | 
|  | // web_contents check exists because not all unit tests set up a proper error | 
|  | // page. | 
|  | content::WebContents* web_contents = | 
|  | content::WebContents::FromRenderFrameHost(render_frame_host); | 
|  | if (web_contents) { | 
|  | AppIconFetcherTask::FetchAndPopulateIcon(web_contents, web_app_provider, | 
|  | app_id.value()); | 
|  | } | 
|  |  | 
|  | auto alternative_error_page_info = | 
|  | content::mojom::AlternativeErrorPageOverrideInfo::New(); | 
|  | base::Value::Dict dict; | 
|  | dict.Set(error_page::kAppShortName, | 
|  | web_app_registrar.GetAppShortName(*app_id)); | 
|  | dict.Set(error_page::kMessage, message); | 
|  | // Android uses kIconUrl to provide the icon url synchronously, because it | 
|  | // already available, but Desktop sends down a transparent 1x1 pixel instead | 
|  | // and then updates it asynchronously once it is available. | 
|  | dict.Set(error_page::kIconUrl, | 
|  | "data:image/" | 
|  | "png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklE" | 
|  | "QVR42mMAAQAABQABoIJXOQAAAABJRU5ErkJggg=="); | 
|  | dict.Set(error_page::kSupplementaryIcon, supplementary_icon); | 
|  | alternative_error_page_info->alternative_error_page_params = std::move(dict); | 
|  | alternative_error_page_info->resource_id = IDR_WEBAPP_ERROR_PAGE_HTML; | 
|  | return alternative_error_page_info; | 
|  | } | 
|  |  | 
|  | bool IsValidScopeForLinkCapturing(const GURL& scope) { | 
|  | return scope.is_valid() && scope.has_scheme() && scope.SchemeIsHTTPOrHTTPS(); | 
|  | } | 
|  |  | 
|  | // TODO(http://b/331208955): Remove after migration. | 
|  | bool WillBeSystemWebApp(const webapps::AppId& app_id, | 
|  | WebAppManagementTypes sources) { | 
|  | #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS) | 
|  | return app_id == ash::kGeminiAppId && sources.Has(WebAppManagement::kDefault); | 
|  | #else  // BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS) | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | }  // namespace web_app |