| // 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->IsLoggedInAsKioskChromeApp()) { |
| 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 |