blob: d13f90cef0eb152cfc0c7f445949ec72af452e28 [file] [log] [blame]
// 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>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/app_registry_cache_waiter.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/intent_helper/preferred_apps_test_util.h"
#include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/theme_change_waiter.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/blink/public/mojom/input/input_event.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "url/gurl.h"
#include "url/url_constants.h"
static_assert(BUILDFLAG(IS_CHROMEOS), "For Chrome OS only");
namespace {
constexpr char kMicrosoft365ManifestUrlsFinchParam[] = "m365-manifest-urls";
}
namespace web_app {
class ChromeOsWebAppExperimentsBrowserTest
: public WebAppNavigationBrowserTest,
public testing::WithParamInterface<
apps::test::LinkCapturingFeatureVersion> {
public:
ChromeOsWebAppExperimentsBrowserTest() {
std::vector<base::test::FeatureRefAndParams> enabled_features =
apps::test::GetFeaturesToEnableLinkCapturingUX(GetParam());
enabled_features.emplace_back(chromeos::features::kUploadOfficeToCloud,
base::FieldTrialParams());
scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
}
~ChromeOsWebAppExperimentsBrowserTest() override = default;
// WebAppNavigationBrowserTest:
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->Start());
WebAppNavigationBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
WebAppNavigationBrowserTest::SetUpOnMainThread();
// Override the experiment parameters before installing the app so that it
// gets published with the test extended scope.
extended_scope_ = embedded_test_server()->GetURL("/pwa/");
extended_scope_page_ = extended_scope_.Resolve("app2.html");
ChromeOsWebAppExperiments::SetAlwaysEnabledForTesting();
ChromeOsWebAppExperiments::SetScopeExtensionsForTesting(
{extended_scope_.spec().c_str()});
app_id_ = InstallWebAppFromPageAndCloseAppBrowser(
browser(), embedded_test_server()->GetURL(
"/web_apps/get_manifest.html?theme_color.json"));
apps::AppReadinessWaiter(profile(), app_id_).Await();
}
void TearDownOnMainThread() override {
WebAppNavigationBrowserTest::TearDownOnMainThread();
ChromeOsWebAppExperiments::ClearOverridesForTesting();
}
protected:
webapps::AppId app_id_;
GURL extended_scope_;
GURL extended_scope_page_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsBrowserTest,
OutOfScopeBarRemoval) {
// Check that the out of scope banner doesn't show after navigating to the
// different scope in the web app window.
Browser* app_browser = LaunchWebAppBrowser(app_id_);
NavigateViaLinkClickToURLAndWait(app_browser, extended_scope_page_);
EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar());
}
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsBrowserTest,
LinkCaptureScopeExtension) {
// Turn on link capturing for the web app.
apps_util::SetSupportedLinksPreferenceAndWait(profile(), app_id_);
// Navigate the main browser to the different scope.
ClickLinkAndWait(browser()->tab_strip_model()->GetActiveWebContents(),
extended_scope_page_, LinkTarget::SELF, "");
// The navigation should get link captured into the web app.
Browser* app_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser, app_id_));
EXPECT_EQ(
app_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
extended_scope_page_);
}
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsBrowserTest,
IgnoreManifestColor) {
Browser* app_browser = LaunchWebAppBrowserAndWait(app_id_);
EXPECT_FALSE(app_browser->app_controller()->GetThemeColor().has_value());
// If the page starts setting its own theme-color it should not be ignored.
content::WebContents* web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
content::ThemeChangeWaiter waiter(web_contents);
const char* script = R"(
const meta = document.createElement('meta');
meta.name = 'theme-color';
meta.content = 'lime';
document.head.append(meta);
)";
ASSERT_TRUE(EvalJs(web_contents, script).error.empty());
waiter.Wait();
EXPECT_EQ(app_browser->app_controller()->GetThemeColor(),
SkColorSetARGB(0xFF, 0x0, 0xFF, 0x0));
}
INSTANTIATE_TEST_SUITE_P(
All,
ChromeOsWebAppExperimentsBrowserTest,
testing::Values(apps::test::LinkCapturingFeatureVersion::kV1DefaultOff,
apps::test::LinkCapturingFeatureVersion::kV2DefaultOff,
apps::test::LinkCapturingFeatureVersion::
kV2DefaultOffCaptureExistingFrames),
apps::test::LinkCapturingVersionToString);
class ChromeOsWebAppExperimentsNavigationBrowserTest
: public ChromeOsWebAppExperimentsBrowserTest {
protected:
ChromeOsWebAppExperimentsNavigationBrowserTest() = default;
~ChromeOsWebAppExperimentsNavigationBrowserTest() override = default;
void AddAndClickLinkWithCode(content::WebContents* web_contents,
const std::string& on_click_code) {
const std::string script = base::StringPrintf(
R"(
(() => {
const link = document.createElement('a');
link.href = '#';
link.onclick = function(e) {
e.preventDefault();
%s
};
// Make a click target that covers the whole viewport.
const clickTarget = document.createElement('textarea');
clickTarget.style.position = 'absolute';
clickTarget.style.top = 0;
clickTarget.style.left = 0;
clickTarget.style.height = '100vh';
clickTarget.style.width = '100vw';
link.appendChild(clickTarget);
document.body.appendChild(link);
})();
)",
on_click_code.c_str());
ASSERT_TRUE(content::ExecJs(web_contents, script));
content::SimulateMouseClick(web_contents,
blink::WebInputEvent::Modifiers::kNoModifiers,
blink::WebMouseEvent::Button::kLeft);
}
std::string GetFormBasedRedirectorCode(const GURL& target_url) const {
const GURL redirector_url = https_server().GetURL(
"redirector-host", CreateServerRedirect(target_url));
return base::StringPrintf(
R"(
const f = document.createElement('form');
f.setAttribute('method', 'post');
f.setAttribute('action', '%s');
document.body.appendChild(f);
f.submit();
)",
redirector_url.spec().c_str());
}
base::test::ScopedFeatureList scoped_feature_list_{
chromeos::features::kOfficeNavigationCapturingReimpl};
};
// Test that submitting a POST form in the app's window doesn't result in
// leaving that window.
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
PostForm) {
Browser* app_browser = LaunchWebAppBrowserAndWait(app_id_);
content::WebContents* app_web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
const std::string on_click_code = base::StringPrintf(
R"(
const f = document.createElement('form');
f.setAttribute('method', 'post');
f.setAttribute('action', '%s');
f.setAttribute('target', '_top');
const p = document.createElement('input');
p.setAttribute('name', 'foo');
p.setAttribute('value', 'bar');
f.appendChild(p);
document.body.appendChild(f);
f.submit();
)",
extended_scope_page_.spec().c_str());
auto observer = GetTestNavigationObserver(extended_scope_page_);
AddAndClickLinkWithCode(app_web_contents, on_click_code);
observer->Wait();
// The web app handles the navigation.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_TRUE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
extended_scope_page_);
}
// Test that submitting a POST form to an app-controlled URL, happening in a
// window opened via target=_blank, ends up in a new app window.
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
PostFormInBlankWindow) {
Browser* app_browser = LaunchWebAppBrowserAndWait(app_id_);
content::WebContents* app_web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
const std::string on_click_code = base::StringPrintf(
R"(
const w = window.open('', '_blank');
const f = w.document.createElement('form');
f.setAttribute('method', 'post');
f.setAttribute('action', '%s');
f.setAttribute('target', '_top');
const p = w.document.createElement('input');
p.setAttribute('name', 'foo');
p.setAttribute('value', 'bar');
f.appendChild(p);
w.document.body.appendChild(f);
f.submit();
)",
extended_scope_page_.spec().c_str());
auto observer = GetTestNavigationObserver(extended_scope_page_);
AddAndClickLinkWithCode(app_web_contents, on_click_code);
observer->Wait();
// The web app handles the navigation by opening a new app window.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_TRUE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
extended_scope_page_);
}
// Test that opening a target=_blank window with an app-controlled URL ends up
// in a new app window.
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
OpenAsBlankWindow) {
Browser* app_browser = LaunchWebAppBrowserAndWait(app_id_);
content::WebContents* app_web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
const std::string on_click_code = base::StringPrintf(
R"(
window.open('%s', '_blank');
)",
extended_scope_page_.spec().c_str());
auto observer = GetTestNavigationObserver(extended_scope_page_);
AddAndClickLinkWithCode(app_web_contents, on_click_code);
observer->Wait();
// The web app handles the navigation by opening a new app window.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_TRUE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
extended_scope_page_);
}
// Test that opening an empty target=_blank window and then navigating it as
// target=_top to an app-controlled URL ends up in a new app window.
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
OpenTopWindowInBlankWindow) {
Browser* app_browser = LaunchWebAppBrowserAndWait(app_id_);
content::WebContents* app_web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
const std::string on_click_code = base::StringPrintf(
R"(
const w = window.open('', '_blank');
w.open('%s', '_top');
)",
extended_scope_page_.spec().c_str());
auto observer = GetTestNavigationObserver(extended_scope_page_);
AddAndClickLinkWithCode(app_web_contents, on_click_code);
observer->Wait();
// The web app handles the navigation by opening a new app window.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_TRUE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
extended_scope_page_);
}
// Test that submitting a form that redirects to the app-controlled URL results
// in launching that app - if it's marked as "open supported links".
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
OutOfScopeFormAndRedirectToPreferred) {
ASSERT_TRUE(https_server().Start());
// Start from a blank page - the form below will be added to it.
EXPECT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
content::WebContents* page_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto observer = GetTestNavigationObserver(extended_scope_page_);
AddAndClickLinkWithCode(page_web_contents,
GetFormBasedRedirectorCode(extended_scope_page_));
observer->Wait();
// The web app handles the navigation by opening a new app window.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_TRUE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
extended_scope_page_);
}
// Opposite to the previous test, verifies that the app is NOT launched if it's
// not marked as "open supported links".
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
OutOfScopeFormAndRedirectToNotPreferred) {
ASSERT_TRUE(https_server().Start());
// The link capturing is turned on by default; simulate the user opt-out here.
apps_util::RemoveSupportedLinksPreferenceAndWait(profile(), app_id_);
// Start from a blank page - the form below will be added to it.
EXPECT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
content::WebContents* page_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto observer = GetTestNavigationObserver(extended_scope_page_);
AddAndClickLinkWithCode(page_web_contents,
GetFormBasedRedirectorCode(extended_scope_page_));
observer->Wait();
// The app window was not launched for the navigation.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_FALSE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
extended_scope_page_);
}
// Test that clicking a noreferrer noopener target=_blank link to an
// out-of-scope URL results in opening a browser tab.
IN_PROC_BROWSER_TEST_P(ChromeOsWebAppExperimentsNavigationBrowserTest,
NoopenerNoreferrerBlankLinkToOutOfScope) {
ASSERT_TRUE(https_server().Start());
Browser* app_browser = LaunchWebAppBrowserAndWait(app_id_);
content::WebContents* app_web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
const GURL target_url = https_server().GetURL("/empty.html");
auto observer = GetTestNavigationObserver(target_url);
ClickLink(app_web_contents, target_url, LinkTarget::BLANK,
/*rel=*/"noreferrer noopener");
observer->Wait();
// A browser tab is opened for the target URL.
Browser* active_browser = BrowserList::GetInstance()->GetLastActive();
EXPECT_FALSE(AppBrowserController::IsForWebApp(active_browser, app_id_));
EXPECT_EQ(active_browser->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL(),
target_url);
}
INSTANTIATE_TEST_SUITE_P(
All,
ChromeOsWebAppExperimentsNavigationBrowserTest,
testing::Values(apps::test::LinkCapturingFeatureVersion::kV1DefaultOff,
apps::test::LinkCapturingFeatureVersion::kV2DefaultOff,
apps::test::LinkCapturingFeatureVersion::
kV2DefaultOffCaptureExistingFrames),
apps::test::LinkCapturingVersionToString);
class ChromeOsWebAppExperimentsManifestOverrideBrowserTest
: public InProcessBrowserTest {
public:
ChromeOsWebAppExperimentsManifestOverrideBrowserTest() = default;
void TearDown() override {
InProcessBrowserTest::TearDown();
scoped_feature_list_.Reset();
}
Profile* profile() { return browser()->profile(); }
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::RenderFrameHost* RenderFrameHost() const {
return web_contents()->GetPrimaryMainFrame();
}
base::test::ScopedFeatureList scoped_feature_list_;
};
class ChromeOsWebAppExperimentsManifestOverrideDisabledBrowserTest
: public ChromeOsWebAppExperimentsManifestOverrideBrowserTest {
public:
ChromeOsWebAppExperimentsManifestOverrideDisabledBrowserTest() {
scoped_feature_list_.InitAndDisableFeature(
chromeos::features::kMicrosoft365ManifestOverride);
}
};
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideDisabledBrowserTest,
DontOverrideManifestWithFlagDisabled) {
const GURL m365PWAUrl = GURL("https://www.microsoft365.com/");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl;
manifest->start_url = m365PWAUrl;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(m365PWAUrl, manifest->id);
}
class ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest
: public ChromeOsWebAppExperimentsManifestOverrideBrowserTest {
public:
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest() {
EnableM365ManifestUrls(
"https://www.microsoft365.com/,https://www.example.com/");
}
void EnableM365ManifestUrls(const std::string& urls) {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
chromeos::features::kMicrosoft365ManifestOverride,
{{kMicrosoft365ManifestUrlsFinchParam, urls}});
}
};
// The manifest id should not be overridden if the start URL is not contained in
// the Url list of the corresponding finch parameter.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
DontOverrideManifestForNonMatchingUrl) {
const GURL m365PWAUrl = GURL("https://www.example2.com/");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl;
manifest->start_url = m365PWAUrl;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(m365PWAUrl, manifest->id);
}
// The manifest id should not be overridden if the start URL origin matches but
// the path does not.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
DontOverrideManifestForUrlWithPath) {
const GURL m365PWAUrlWithPath =
GURL("https://www.microsoft365.com/launch/word/");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrlWithPath;
manifest->start_url = m365PWAUrlWithPath;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(m365PWAUrlWithPath, manifest->id);
}
// The manifest id should be overridden if the start URL matches a URL in the
// corresponding finch flag.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
OverrideManifestIdForMatchingUrl) {
// Override manifest for plain Url.
const GURL m365PWAUrl = GURL("https://www.microsoft365.com/");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl;
manifest->start_url = m365PWAUrl;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(GURL("https://www.microsoft365.com/?from=Homescreen"),
manifest->id);
}
// The manifest id should be overridden if the start URL matches a URL in the
// corresponding finch flag except for query parameters.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
OverrideManifestIdForMatchingUrlWithQueryParams) {
const GURL m365PWAUrl = GURL("https://www.microsoft365.com/?auth=1");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl;
manifest->start_url = m365PWAUrl;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(GURL("https://www.microsoft365.com/?from=Homescreen"),
manifest->id);
}
// The manifest id should be overridden if the start URL matches a URL in the
// corresponding finch flag except for a file name.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
OverrideManifestIdForMatchingUrlWithFileName) {
const GURL m365PWAUrl = GURL("https://www.microsoft365.com/index.html");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl;
manifest->start_url = m365PWAUrl;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(GURL("https://www.microsoft365.com/?from=Homescreen"),
manifest->id);
}
// The manifest id should be overridden if the start URL matches a URL in the
// corresponding finch flag except for a fragment.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
OverrideManifestIdForMatchingUrlWithFragment) {
const GURL m365PWAUrl = GURL("https://www.microsoft365.com/#test");
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl;
manifest->start_url = m365PWAUrl;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(), manifest);
EXPECT_EQ(GURL("https://www.microsoft365.com/?from=Homescreen"),
manifest->id);
}
// The manifest id should be overridden if the start URL matches one of multiple
// URLs in the corresponding finch flag.
IN_PROC_BROWSER_TEST_F(
ChromeOsWebAppExperimentsManifestOverrideEnabledBrowserTest,
OverrideManifestIdForMultipleUrls) {
const GURL m365PWAUrl1 = GURL("https://www.microsoft365.com/");
const GURL m365PWAUrl2 = GURL("https://www.example.com/?test=a");
{
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl1;
manifest->start_url = m365PWAUrl1;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(),
manifest);
EXPECT_EQ(GURL("https://www.microsoft365.com/?from=Homescreen"),
manifest->id);
}
{
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->id = m365PWAUrl2;
manifest->start_url = m365PWAUrl2;
ChromeOsWebAppExperiments::MaybeOverrideManifest(RenderFrameHost(),
manifest);
EXPECT_EQ(GURL("https://www.example.com/?from=Homescreen"), manifest->id);
}
}
} // namespace web_app