| // Copyright 2022 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/chromeos_web_app_experiments.h" |
| |
| #include <string_view> |
| |
| #include "ash/constants/web_app_id_constants.h" |
| #include "base/containers/contains.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/chromeos/upload_office_to_cloud/upload_office_to_cloud.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| constexpr const char* kMicrosoft365ManifestId = "?from=Homescreen"; |
| |
| bool g_always_enabled_for_testing = false; |
| |
| bool IsExperimentEnabled(const webapps::AppId& app_id) { |
| return g_always_enabled_for_testing || app_id == ash::kMicrosoft365AppId; |
| } |
| |
| // IsValidScopeExtenion returns whether a url can be successfully turned into |
| // a scope extension or not. |
| bool IsValidScopeExtension(const GURL& url) { |
| return url.is_valid() && url.IsStandard() && url.has_host() && |
| !base::StartsWith(url.host(), "."); |
| } |
| |
| std::vector<std::string> GetListFromFinchParam(const std::string& finch_param) { |
| return base::SplitString(finch_param, ",", |
| base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_NONEMPTY); |
| } |
| |
| std::optional<std::vector<const char*>>& |
| GetScopeExtensionsOverrideForTesting() { |
| static base::NoDestructor<std::optional<std::vector<const char*>>> |
| scope_extensions; |
| return *scope_extensions; |
| } |
| |
| } // namespace |
| |
| ScopeExtensions ChromeOsWebAppExperiments::GetScopeExtensions( |
| const webapps::AppId& app_id) { |
| DCHECK(chromeos::features::IsUploadOfficeToCloudEnabled()); |
| |
| ScopeExtensions extensions; |
| if (!IsExperimentEnabled(app_id)) { |
| return extensions; |
| } |
| |
| if (GetScopeExtensionsOverrideForTesting()) { |
| for (const auto* origin : *GetScopeExtensionsOverrideForTesting()) { |
| extensions.insert(ScopeExtensionInfo::CreateForOrigin( |
| url::Origin::Create(GURL(origin)))); |
| } |
| return extensions; |
| } |
| |
| const auto microsoft365_scope_extension_urls = GetListFromFinchParam( |
| chromeos::features::kMicrosoft365ScopeExtensionsURLs.Get()); |
| for (const auto& url_string : microsoft365_scope_extension_urls) { |
| const GURL url = GURL(url_string); |
| if (!IsValidScopeExtension(url)) { |
| LOG(WARNING) << "Skipping invalid M365 scope extension URL from Finch: " |
| << url_string; |
| continue; |
| } |
| extensions.insert( |
| ScopeExtensionInfo::CreateForOrigin(url::Origin::Create(GURL(url)))); |
| } |
| const auto microsoft365_scope_extension_domains = GetListFromFinchParam( |
| chromeos::features::kMicrosoft365ScopeExtensionsDomains.Get()); |
| for (const auto& url_string : microsoft365_scope_extension_domains) { |
| const GURL url = GURL(url_string); |
| if (!IsValidScopeExtension(url)) { |
| LOG(WARNING) |
| << "Skipping invalid M365 scope extension domain from Finch: " |
| << url_string; |
| continue; |
| } |
| extensions.insert(ScopeExtensionInfo::CreateForOrigin( |
| url::Origin::Create(GURL(url)), /*has_origin_wildcard*/ true)); |
| } |
| return extensions; |
| } |
| |
| bool ChromeOsWebAppExperiments::ShouldAddLinkPreference( |
| const webapps::AppId& app_id, |
| Profile* profile) { |
| return IsExperimentEnabled(app_id) && |
| chromeos::cloud_upload::IsMicrosoftOfficeOneDriveIntegrationAutomated( |
| profile); |
| } |
| |
| int ChromeOsWebAppExperiments::GetExtendedScopeScore( |
| const webapps::AppId& app_id, |
| std::string_view url_spec) { |
| DCHECK(chromeos::features::IsUploadOfficeToCloudEnabled()); |
| |
| const GURL url = GURL(url_spec); |
| const auto extensions = GetScopeExtensions(app_id); |
| int best_score = 0; |
| for (const ScopeExtensionInfo& scope : extensions) { |
| const GURL scope_origin = scope.origin.GetURL(); |
| int score; |
| if (scope.has_origin_wildcard) { |
| score = |
| url.DomainIs(scope_origin.host()) ? scope_origin.spec().length() : 0; |
| } else { |
| score = base::StartsWith(url_spec, scope_origin.spec(), |
| base::CompareCase::SENSITIVE) |
| ? scope_origin.spec().length() |
| : 0; |
| } |
| best_score = std::max(best_score, score); |
| } |
| return best_score; |
| } |
| |
| bool ChromeOsWebAppExperiments::IgnoreManifestColor( |
| const webapps::AppId& app_id) { |
| DCHECK(chromeos::features::IsUploadOfficeToCloudEnabled()); |
| return IsExperimentEnabled(app_id); |
| } |
| |
| bool ChromeOsWebAppExperiments::IsNavigationCapturingReimplEnabledForTargetApp( |
| const webapps::AppId& target_app_id) { |
| return ::chromeos::features::IsOfficeNavigationCapturingReimplEnabled() && |
| IsExperimentEnabled(target_app_id); |
| } |
| |
| bool ChromeOsWebAppExperiments::IsNavigationCapturingReimplEnabledForSourceApp( |
| const webapps::AppId& source_app_id, |
| const GURL& url) { |
| // Until Navigation Capturing Reimplementation is fully enabled, hardcode |
| // specific destination URLs for the typical scenarios in which we want the |
| // user to stay inside the Office PWA (note that URLs that are already within |
| // the PWA's scope are covered by |
| // `IsNavigationCapturingReimplEnabledForTargetApp()`). |
| return ::chromeos::features::IsOfficeNavigationCapturingReimplEnabled() && |
| IsExperimentEnabled(source_app_id) && url == url::kAboutBlankURL; |
| } |
| |
| bool ChromeOsWebAppExperiments::ShouldLaunchForRedirectedNavigation( |
| const webapps::AppId& target_app_id) { |
| return IsExperimentEnabled(target_app_id); |
| } |
| |
| void ChromeOsWebAppExperiments::MaybeOverrideManifest( |
| content::RenderFrameHost* frame_host, |
| blink::mojom::ManifestPtr& manifest) { |
| if (!::chromeos::features::IsMicrosoft365ManifestOverrideEnabled()) { |
| return; |
| } |
| |
| const auto pwa_start_url_origin = url::Origin::Create(manifest->start_url); |
| std::string pwa_start_url_path = |
| manifest->start_url.GetWithoutFilename().path(); |
| |
| const auto microsoft365_manifest_urls = GetListFromFinchParam( |
| chromeos::features::kMicrosoft365ManifestUrls.Get()); |
| |
| for (const auto& url_string : microsoft365_manifest_urls) { |
| GURL microsoft365_manifest_url = GURL(url_string); |
| |
| if (pwa_start_url_origin.IsSameOriginWith(microsoft365_manifest_url) && |
| pwa_start_url_path == microsoft365_manifest_url.path()) { |
| manifest->id = |
| GURL(pwa_start_url_origin.GetURL().spec() + kMicrosoft365ManifestId); |
| } |
| } |
| } |
| |
| void ChromeOsWebAppExperiments::SetAlwaysEnabledForTesting() { |
| g_always_enabled_for_testing = true; |
| } |
| |
| void ChromeOsWebAppExperiments::SetScopeExtensionsForTesting( |
| std::vector<const char*> scope_extensions_override) { |
| GetScopeExtensionsOverrideForTesting() = std::move(scope_extensions_override); |
| } |
| |
| void ChromeOsWebAppExperiments::ClearOverridesForTesting() { |
| g_always_enabled_for_testing = false; |
| GetScopeExtensionsOverrideForTesting().reset(); |
| } |
| |
| } // namespace web_app |