blob: 9c674e67d3dec14e93e3efa6a63ba31f8493efb2 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webapps/browser/banners/app_banner_manager.h"
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/banners/app_banner_manager_browsertest_base.h"
#include "chrome/browser/profiles/profile.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/webapps/browser/banners/app_banner_metrics.h"
#include "components/webapps/browser/banners/app_banner_settings_helper.h"
#include "components/webapps/browser/banners/install_banner_config.h"
#include "components/webapps/browser/banners/installable_web_app_check_result.h"
#include "components/webapps/browser/banners/web_app_banner_data.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/installable/installable_data.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_manager.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#endif
namespace webapps {
using State = AppBannerManager::State;
// Browser tests for web app banners.
// NOTE: this test relies on service workers; failures and flakiness may be due
// to changes in SW code.
// TODO(http://crbug.com/329145718): Use AppBannerManagerNoFakeBrowserTest style
// instead of overriding like this.
// TODO(http://crbug.com/322342499): Completely remove this class.
class AppBannerManagerTest : public AppBannerManager {
public:
explicit AppBannerManagerTest(content::WebContents* web_contents)
: AppBannerManager(web_contents) {}
AppBannerManagerTest(const AppBannerManagerTest&) = delete;
AppBannerManagerTest& operator=(const AppBannerManagerTest&) = delete;
~AppBannerManagerTest() override = default;
bool TriggeringDisabledForTesting() const override { return false; }
void RequestAppBanner() override {
// Filter out about:blank navigations - we use these in testing to force
// Stop() to be called.
if (validated_url_ == GURL("about:blank")) {
return;
}
AppBannerManager::RequestAppBanner();
}
bool banner_shown() { return banner_shown_.get() && *banner_shown_; }
std::optional<WebappInstallSource> install_source() {
return install_source_;
}
void clear_will_show() { banner_shown_.reset(); }
State state() { return AppBannerManager::state(); }
// Configures a callback to be invoked when the app banner flow finishes.
void PrepareDone(base::OnceClosure on_done) { on_done_ = std::move(on_done); }
// Configures a callback to be invoked from OnBannerPromptReply.
void PrepareBannerPromptReply(base::OnceClosure on_banner_prompt_reply) {
on_banner_prompt_reply_ = std::move(on_banner_prompt_reply);
}
void OnMlInstallPrediction(base::PassKey<MLInstallabilityPromoter>,
std::string result_label) override {}
protected:
bool CanRequestAppBanner() const override { return true; }
InstallableParams ParamsToPerformInstallableWebAppCheck() override {
InstallableParams params;
params.valid_primary_icon = true;
params.installable_criteria =
InstallableCriteria::kImplicitManifestFieldsHTML;
params.fetch_screenshots = true;
return params;
}
bool ShouldDoNativeAppCheck(
const blink::mojom::Manifest& manifest) const override {
return false;
}
void DoNativeAppInstallableCheck(content::WebContents* web_contents,
const GURL& validated_url,
const blink::mojom::Manifest& manifest,
NativeCheckCallback callback) override {
NOTREACHED();
}
void OnWebAppInstallableCheckedNoErrors(
const ManifestId& manifest_id) const override {}
base::expected<void, InstallableStatusCode> CanRunWebAppInstallableChecks(
const blink::mojom::Manifest& manifest) override {
return base::ok();
}
void MaybeShowAmbientBadge(const InstallBannerConfig& config) override {
return;
}
void ResetCurrentPageData() override {}
// The overridden RequestAppBanner() can filter out about:blank calls
// to force Stop() to be called, however, the newly introduced
// AppBannerManagerBrowserTestWithChromeBFCache starts a server and navigates
// to a dynamic/installable banner link and then retriggers the pipeline by
// terminating an existing banner. As a result, there can exist banners in an
// intermediary state (on_done_ not initialized, banner still shown) that
// needs to be cleaned in these overridden functions for Stop() and
// UpdateState(State::PENDING).
//
// As a result, calls to RequestAppBanner should always terminate in
// ShowBannerUi(), but not necessarily in Stop() (not showing banner).
// Override these methods to capture test status.
void Stop(InstallableStatusCode code) override {
AppBannerManager::Stop(code);
if (banner_shown_)
clear_will_show();
ASSERT_FALSE(banner_shown_.get());
banner_shown_ = std::make_unique<bool>(false);
install_source_ = std::nullopt;
if (on_done_)
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_done_));
}
void ShowBannerUi(WebappInstallSource install_source,
const InstallBannerConfig& config) override {
// Fake the call to ReportStatus here - this is usually called in
// platform-specific code which is not exposed here.
ReportStatus(InstallableStatusCode::SHOWING_WEB_APP_BANNER);
ASSERT_FALSE(banner_shown_.get());
banner_shown_ = std::make_unique<bool>(true);
install_source_ = install_source;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_done_));
}
void UpdateState(AppBannerManager::State state) override {
AppBannerManager::UpdateState(state);
if (state == AppBannerManager::State::PENDING_PROMPT_CANCELED ||
state == AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED) {
if (on_done_)
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_done_));
}
}
void OnBannerPromptReply(
const InstallBannerConfig& install_config,
mojo::Remote<blink::mojom::AppBannerController> controller,
blink::mojom::AppBannerPromptReply reply) override {
AppBannerManager::OnBannerPromptReply(install_config, std::move(controller),
reply);
if (on_banner_prompt_reply_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_banner_prompt_reply_));
}
}
base::WeakPtr<AppBannerManager> GetWeakPtrForThisNavigation() override {
return weak_factory_.GetWeakPtr();
}
void InvalidateWeakPtrsForThisNavigation() override {
weak_factory_.InvalidateWeakPtrs();
}
bool IsSupportedNonWebAppPlatform(
const std::u16string& platform) const override {
return base::EqualsASCII(platform, "chrome_web_store");
}
bool IsRelatedNonWebAppInstalled(
const blink::Manifest::RelatedApplication& related_app) const override {
// Corresponds to the id listed in manifest_listing_related_chrome_app.json.
return base::EqualsASCII(related_app.platform.value_or(std::u16string()),
"chrome_web_store") &&
base::EqualsASCII(related_app.id.value_or(std::u16string()),
"installed-extension-id");
}
base::OnceClosure on_done_;
private:
// If non-null, |on_banner_prompt_reply_| will be invoked from
// OnBannerPromptReply.
base::OnceClosure on_banner_prompt_reply_;
std::unique_ptr<bool> banner_shown_;
std::optional<WebappInstallSource> install_source_;
base::WeakPtrFactory<AppBannerManagerTest> weak_factory_{this};
};
enum class CheckWebAppExistence { kAsync = 0, kSync = 1, kMaxValue = kSync };
class AppBannerManagerBrowserTest
: public AppBannerManagerBrowserTestBase,
public ::testing::WithParamInterface<CheckWebAppExistence> {
public:
AppBannerManagerBrowserTest()
: disable_banner_trigger_(&test::g_disable_banner_triggering_for_testing,
true) {}
AppBannerManagerBrowserTest(const AppBannerManagerBrowserTest&) = delete;
AppBannerManagerBrowserTest& operator=(const AppBannerManagerBrowserTest&) =
delete;
void SetUpCommandLine(base::CommandLine* command_line) override {
if (GetParam() == CheckWebAppExistence::kAsync) {
feature_list_.InitAndEnableFeature(features::kCheckWebAppExistenceAsync);
} else {
feature_list_.InitAndDisableFeature(features::kCheckWebAppExistenceAsync);
}
}
protected:
std::unique_ptr<AppBannerManagerTest> CreateAppBannerManager() {
content::WebContents* web_contents =
chrome_test_utils::GetActiveWebContents(this);
return std::make_unique<AppBannerManagerTest>(web_contents);
}
void RunBannerTest(
content::WebContents* web_contents,
AppBannerManagerTest* manager,
const GURL& url,
std::optional<InstallableStatusCode> expected_code_for_histogram,
bool is_off_the_record = false) {
base::HistogramTester histograms;
// Spin the run loop and wait for the manager to finish.
base::RunLoop run_loop;
manager->clear_will_show();
manager->PrepareDone(run_loop.QuitClosure());
ASSERT_TRUE(content::NavigateToURL(web_contents, url));
run_loop.Run();
EXPECT_EQ(expected_code_for_histogram.value_or(
InstallableStatusCode::MAX_ERROR_CODE) ==
InstallableStatusCode::SHOWING_WEB_APP_BANNER,
manager->banner_shown());
EXPECT_EQ(std::nullopt, manager->install_source());
// Generally the manager will be in the complete state, however some test
// cases navigate the page, causing the state to go back to INACTIVE.
EXPECT_TRUE(manager->state() == State::COMPLETE ||
manager->state() == State::PENDING_PROMPT_CANCELED ||
manager->state() == State::PENDING_PROMPT_NOT_CANCELED ||
manager->state() == State::INACTIVE);
// If in incognito, ensure that nothing is recorded.
if (is_off_the_record || !expected_code_for_histogram) {
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
} else {
EXPECT_THAT(histograms.GetAllSamples(kInstallableStatusCodeHistogram),
base::BucketsAre(base::Bucket(
expected_code_for_histogram.value(), /*count=*/1)));
}
}
void TriggerBannerFlowWithNavigation(AppBannerManagerTest* manager,
const GURL& url,
bool expected_will_show,
std::optional<State> expected_state) {
// Use NavigateToURLWithDisposition as it isn't overloaded, so can be used
// with Bind.
TriggerBannerFlow(
manager, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
}),
expected_will_show, expected_state);
}
void TriggerBannerFlow(AppBannerManagerTest* manager,
base::OnceClosure trigger_task,
bool expected_will_show,
std::optional<State> expected_state) {
base::RunLoop run_loop;
manager->clear_will_show();
manager->PrepareDone(run_loop.QuitClosure());
std::move(trigger_task).Run();
run_loop.Run();
EXPECT_EQ(expected_will_show, manager->banner_shown());
if (expected_state)
EXPECT_EQ(expected_state, manager->state());
}
private:
// Disable the banners in the browser so it won't interfere with the test.
base::AutoReset<bool> disable_banner_trigger_;
base::test::ScopedFeatureList feature_list_;
};
// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerNoTypeInManifest DISABLED_WebAppBannerNoTypeInManifest
#else
#define MAYBE_WebAppBannerNoTypeInManifest WebAppBannerNoTypeInManifest
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_WebAppBannerNoTypeInManifest) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_no_type.json"),
std::nullopt);
}
// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerNoTypeInManifestCapsExtension \
DISABLED_WebAppBannerNoTypeInManifestCapsExtension
#else
#define MAYBE_WebAppBannerNoTypeInManifestCapsExtension \
WebAppBannerNoTypeInManifestCapsExtension
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_WebAppBannerNoTypeInManifestCapsExtension) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_no_type_caps.json"),
std::nullopt);
}
// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerSvgIcon DISABLED_WebAppBannerSvgIcon
#else
#define MAYBE_WebAppBannerSvgIcon WebAppBannerSvgIcon
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_WebAppBannerSvgIcon) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_svg_icon.json"),
std::nullopt);
}
// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerWebPIcon DISABLED_WebAppBannerWebPIcon
#else
#define MAYBE_WebAppBannerWebPIcon WebAppBannerWebPIcon
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_WebAppBannerWebPIcon) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_webp_icon.json"),
std::nullopt);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
DelayedManifestTriggersPipeline) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(
web_contents(), manager.get(),
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"),
InstallableStatusCode::NO_MANIFEST);
// Dynamically add the manifest.
base::HistogramTester histograms;
TriggerBannerFlow(
manager.get(), base::BindLambdaForTesting([&]() {
EXPECT_TRUE(content::ExecJs(web_contents(), "addManifestLinkTag()"));
}),
/*expected_will_show=*/false, std::nullopt);
TriggerBannerFlow(manager.get(), base::DoNothing(),
/*expected_will_show=*/false,
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
RemovingManifestStopsPipeline) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(
web_contents(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
// Dynamically remove the manifest.
base::HistogramTester histograms;
TriggerBannerFlow(
manager.get(), base::BindLambdaForTesting([&]() {
EXPECT_TRUE(content::ExecJs(web_contents(), "removeAllManifestTags()"));
}),
false, AppBannerManager::State::COMPLETE);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 1);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::NO_MANIFEST, 1);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
ManifestChangeTriggersPipeline) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
// Cause the manifest test page to reach the PENDING_PROMPT stage of the
// app banner pipeline.
RunBannerTest(
web_contents(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
// Dynamically change the manifest, which results in a
// Stop(MANIFEST_URL_CHANGED), and a restart of the pipeline.
{
base::HistogramTester histograms;
// Note - The state of the appbannermanager here will be racy, so don't
// check for that.
TriggerBannerFlow(
manager.get(), base::BindLambdaForTesting([&]() {
EXPECT_TRUE(content::ExecJs(
web_contents(),
"addManifestLinkTag('/banners/manifest_one_icon.json')"));
}),
false, std::nullopt);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 1);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::MANIFEST_URL_CHANGED,
1);
}
// The pipeline should either have completed, or it is scheduled in the
// background. Wait for the next prompt request if so.
if (manager->state() !=
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED) {
base::HistogramTester histograms;
base::RunLoop run_loop;
manager->PrepareDone(run_loop.QuitClosure());
run_loop.Run();
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
}
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
NoPageManifestProvidesDefaultManifest) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
GURL page_url =
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
RunBannerTest(web_contents(), manager.get(), page_url,
InstallableStatusCode::NO_MANIFEST);
std::optional<WebAppBannerData> banner =
manager->GetCurrentWebAppBannerData();
// Check the default manifest was populated.
ASSERT_TRUE(banner);
EXPECT_TRUE(blink::IsDefaultManifest(banner->manifest(), page_url));
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MissingManifest) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_missing.json"),
InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerInIFrame) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
GURL url = embedded_test_server()->GetURL("/banners/iframe_test_page.html");
RunBannerTest(web_contents(), manager.get(), url,
InstallableStatusCode::NO_MANIFEST);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
webapps::InstallableWebAppCheckResult::kNo);
// The banner will be the default one for the current page.
std::optional<WebAppBannerData> banner =
manager->GetCurrentWebAppBannerData();
ASSERT_TRUE(banner);
EXPECT_TRUE(blink::IsDefaultManifest(banner->manifest(), url));
}
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, DoesNotShowInIncognito) {
Browser* incognito_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
content::WebContents* web_contents =
incognito_browser->tab_strip_model()->GetActiveWebContents();
std::unique_ptr<AppBannerManagerTest> manager =
std::make_unique<AppBannerManagerTest>(web_contents);
RunBannerTest(web_contents, manager.get(), GetBannerURL(),
InstallableStatusCode::IN_INCOGNITO, true);
}
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerNotCreated) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
GURL test_url = GetBannerURL();
// Navigate and expect the manager to end up waiting for prompt to be called.
TriggerBannerFlowWithNavigation(manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
// Navigate and expect Stop() to be called.
TriggerBannerFlowWithNavigation(manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::RENDERER_CANCELLED, 1);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerCancelled) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
// Explicitly call preventDefault(), but don't call prompt().
GURL test_url = GetBannerURLWithAction("cancel_prompt");
// Navigate and expect the manager to end up waiting for prompt() to be
// called.
TriggerBannerFlowWithNavigation(manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_CANCELED);
// Navigate to about:blank and expect Stop() to be called.
TriggerBannerFlowWithNavigation(manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::RENDERER_CANCELLED, 1);
}
// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerPromptWithGesture \
DISABLED_WebAppBannerPromptWithGesture
#else
#define MAYBE_WebAppBannerPromptWithGesture WebAppBannerPromptWithGesture
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_WebAppBannerPromptWithGesture) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
GURL test_url = GetBannerURLWithAction("stash_event");
// Navigate to page and get the pipeline started.
TriggerBannerFlowWithNavigation(manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
// Now let the page call prompt with a gesture. The banner should be shown.
TriggerBannerFlow(manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
web_contents(), "callStashedPrompt();",
true /* with_gesture */),
true /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::SHOWING_WEB_APP_BANNER,
1);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerReprompt) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
GURL test_url = GetBannerURLWithAction("stash_event");
// Navigate to page and get the pipeline started.
TriggerBannerFlowWithNavigation(manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
// Call prompt to show the banner.
TriggerBannerFlow(manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
web_contents(), "callStashedPrompt();",
true /* with_gesture */),
true /* expected_will_show */, State::COMPLETE);
// Dismiss the banner.
base::RunLoop run_loop;
manager->PrepareDone(base::DoNothing());
manager->PrepareBannerPromptReply(run_loop.QuitClosure());
manager->SendBannerDismissed();
// Wait for OnBannerPromptReply event.
run_loop.Run();
// Call prompt again to show the banner again.
TriggerBannerFlow(manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
web_contents(), "callStashedPrompt();",
true /* with_gesture */),
true /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::SHOWING_WEB_APP_BANNER,
1);
}
// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PreferRelatedAppUnknown DISABLED_PreferRelatedAppUnknown
#else
#define MAYBE_PreferRelatedAppUnknown PreferRelatedAppUnknown
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_PreferRelatedAppUnknown) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_prefer_related_apps_unknown.json");
TriggerBannerFlowWithNavigation(manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
}
// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PreferRelatedChromeApp DISABLED_PreferRelatedChromeApp
#else
#define MAYBE_PreferRelatedChromeApp PreferRelatedChromeApp
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_PreferRelatedChromeApp) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_prefer_related_chrome_app.json");
TriggerBannerFlowWithNavigation(
manager.get(), test_url, false /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(
kInstallableStatusCodeHistogram,
InstallableStatusCode::PREFER_RELATED_APPLICATIONS, 1);
}
// TODO(crbug.com/370270547): Many related tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ListedRelatedChromeAppInstalled \
DISABLED_ListedRelatedChromeAppInstalled
#else
#define MAYBE_ListedRelatedChromeAppInstalled ListedRelatedChromeAppInstalled
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_ListedRelatedChromeAppInstalled) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_listing_related_chrome_app.json");
TriggerBannerFlowWithNavigation(
manager.get(), test_url, false /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(
kInstallableStatusCodeHistogram,
InstallableStatusCode::PREFER_RELATED_APPLICATIONS, 1);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerTerminated) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
GURL test_url = GetBannerURL();
// Navigate and expect the manager to end up waiting for prompt() to be
// called.
TriggerBannerFlowWithNavigation(manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
// Navigate to about:blank and expect it to be terminated because the previous
// URL is still pending.
TriggerBannerFlowWithNavigation(manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
// Expect the installation config to be empty, as the page is not eligible
// for installation.
EXPECT_EQ(manager->GetCurrentWebAppBannerData(), std::nullopt);
EXPECT_EQ(manager->GetCurrentBannerConfig(), std::nullopt);
// Expect RENDERER_CANCELLED to be called when an existing call is terminated.
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
InstallableStatusCode::RENDERER_CANCELLED, 1);
}
class AppBannerManagerBrowserTestWithChromeBFCache
: public AppBannerManagerBrowserTest {
public:
AppBannerManagerBrowserTestWithChromeBFCache() = default;
~AppBannerManagerBrowserTestWithChromeBFCache() override = default;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// For using an HTTPS server.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kIgnoreCertificateErrors);
SetupFeaturesAndParameters();
}
void SetupFeaturesAndParameters() {
feature_list_.InitWithFeaturesAndParameters(
content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
}
content::RenderFrameHost* current_frame_host() {
return web_contents()->GetPrimaryMainFrame();
}
GURL Get2ndInstallableURL() {
return embedded_test_server()->GetURL("/banners/nested_sw_test_page.html");
}
bool IsRenderHostStoredInBackForwardCache(content::RenderFrameHost* rfh) {
return rfh->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}
void AssertBackForwardCacheIsUsedAsExpected(
const content::RenderFrameHostWrapper& rfh) {
ASSERT_EQ(IsRenderHostStoredInBackForwardCache(rfh.get()),
content::BackForwardCache::IsBackForwardCacheFeatureEnabled());
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTestWithChromeBFCache,
VerifyBFCacheBehavior) {
ASSERT_TRUE(embedded_test_server()->Start());
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
base::HistogramTester histograms;
// Triggering flow to first URL with a pending prompt.
TriggerBannerFlowWithNavigation(manager.get(), GetBannerURL(),
/*expected_will_show=*/false,
State::PENDING_PROMPT_NOT_CANCELED);
content::RenderFrameHostWrapper rfh_a(current_frame_host());
ASSERT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
// Navigating to 2nd installable URL while PENDING_PROMPT will trigger
// the pipeline.
TriggerBannerFlowWithNavigation(manager.get(), Get2ndInstallableURL(),
/*expected_will_show=*/false, std::nullopt);
AssertBackForwardCacheIsUsedAsExpected(rfh_a);
content::RenderFrameHostWrapper rfh_b(current_frame_host());
// Navigate backward to 1st installable URL.
web_contents()->GetController().GoBack();
ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
// Verify pipeline has been triggered for new page load.
EXPECT_NE(manager->state(), AppBannerManager::State::INACTIVE);
AssertBackForwardCacheIsUsedAsExpected(rfh_b);
// Navigate forward to 2nd installable URL.
web_contents()->GetController().GoForward();
ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
// Verify pipeline has been triggered for new page load.
EXPECT_NE(manager->state(), AppBannerManager::State::INACTIVE);
AssertBackForwardCacheIsUsedAsExpected(rfh_a);
}
namespace {
class FailingInstallableManager : public InstallableManager {
public:
explicit FailingInstallableManager(content::WebContents* web_contents)
: InstallableManager(web_contents) {}
void FailNext(std::unique_ptr<InstallableData> installable_data) {
failure_data_ = std::move(installable_data);
}
void GetData(const InstallableParams& params,
InstallableCallback callback) override {
if (failure_data_) {
auto temp_data = std::move(failure_data_);
std::move(callback).Run(*temp_data);
return;
}
InstallableManager::GetData(params, std::move(callback));
}
private:
std::unique_ptr<InstallableData> failure_data_;
};
class AppBannerManagerMPArchBrowserTest : public AppBannerManagerBrowserTest {
public:
AppBannerManagerMPArchBrowserTest() = default;
~AppBannerManagerMPArchBrowserTest() override = default;
AppBannerManagerMPArchBrowserTest(const AppBannerManagerMPArchBrowserTest&) =
delete;
AppBannerManagerMPArchBrowserTest& operator=(
const AppBannerManagerMPArchBrowserTest&) = delete;
void SetUpCommandLine(base::CommandLine* command_line) override {}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
AppBannerManagerBrowserTest::SetUpOnMainThread();
}
};
class AppBannerManagerPrerenderBrowserTest
: public AppBannerManagerMPArchBrowserTest {
public:
AppBannerManagerPrerenderBrowserTest()
: prerender_helper_(base::BindRepeating(
&AppBannerManagerPrerenderBrowserTest::web_contents,
base::Unretained(this))) {}
~AppBannerManagerPrerenderBrowserTest() override = default;
AppBannerManagerPrerenderBrowserTest(
const AppBannerManagerPrerenderBrowserTest&) = delete;
AppBannerManagerPrerenderBrowserTest& operator=(
const AppBannerManagerPrerenderBrowserTest&) = delete;
void SetUp() override {
prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
AppBannerManagerMPArchBrowserTest::SetUp();
}
content::test::PrerenderTestHelper& prerender_test_helper() {
return prerender_helper_;
}
private:
content::test::PrerenderTestHelper prerender_helper_;
};
IN_PROC_BROWSER_TEST_F(AppBannerManagerPrerenderBrowserTest,
PrerenderingShouldNotUpdateState) {
auto initial_url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), initial_url));
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
EXPECT_EQ(manager->state(), AppBannerManager::State::INACTIVE);
// Load a page in the prerender.
GURL prerender_url = GetBannerURL();
const content::FrameTreeNodeId host_id =
prerender_test_helper().AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
EXPECT_FALSE(host_observer.was_activated());
EXPECT_EQ(manager->state(), AppBannerManager::State::INACTIVE);
// Activate the prerender page.
prerender_test_helper().NavigatePrimaryPage(prerender_url);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(manager->state(), AppBannerManager::State::FETCHING_MANIFEST);
}
class AppBannerManagerFencedFrameBrowserTest
: public AppBannerManagerMPArchBrowserTest {
public:
AppBannerManagerFencedFrameBrowserTest() = default;
~AppBannerManagerFencedFrameBrowserTest() override = default;
AppBannerManagerFencedFrameBrowserTest(
const AppBannerManagerFencedFrameBrowserTest&) = delete;
AppBannerManagerFencedFrameBrowserTest& operator=(
const AppBannerManagerFencedFrameBrowserTest&) = delete;
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}
private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
};
IN_PROC_BROWSER_TEST_F(AppBannerManagerFencedFrameBrowserTest,
FencedFrameShouldNotUpdateState) {
// Navigate to an initial page.
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), initial_url));
// Initialize a MockWebContentsObserver to ensure that DidUpdateManifestURL is
// not invoked for fenced frame.
testing::NiceMock<content::MockWebContentsObserver> observer(web_contents());
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
EXPECT_EQ(manager->state(), AppBannerManager::State::INACTIVE);
// Create a fenced frame.
GURL fenced_frame_url = embedded_test_server()->GetURL(
"/banners/fenced_frames/manifest_test_page.html?manifest=/banners/"
"manifest.json");
content::RenderFrameHost* fenced_frame_host =
fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetPrimaryMainFrame(), fenced_frame_url);
EXPECT_NE(nullptr, fenced_frame_host);
EXPECT_EQ(manager->state(), AppBannerManager::State::INACTIVE);
// Cross check that DidUpdateWebManifestURL is not called for fenced frame
// RenderFrameHost.
EXPECT_CALL(observer, DidUpdateWebManifestURL(fenced_frame_host, testing::_))
.Times(0);
// Navigate the fenced frame.
fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
fenced_frame_url);
EXPECT_EQ(manager->state(), AppBannerManager::State::INACTIVE);
}
// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ShowBanner DISABLED_ShowBanner
#else
#define MAYBE_ShowBanner ShowBanner
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_ShowBanner) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(
web_contents(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, NoServiceWorker) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
embedded_test_server()->GetURL(
"/banners/manifest_no_service_worker.html"),
/*expected_code_for_histogram=*/std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, NoFetchHandler) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(web_contents(), manager.get(),
embedded_test_server()->GetURL(
"/banners/no_sw_fetch_handler_test_page.html"),
/*expected_code_for_histogram=*/std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, PendingServiceWorker) {
std::unique_ptr<AppBannerManagerTest> manager =
std::make_unique<AppBannerManagerTest>(web_contents());
RunBannerTest(web_contents(), manager.get(),
embedded_test_server()->GetURL(
"/banners/manifest_no_service_worker.html"),
std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
ASSERT_TRUE(manager->GetCurrentBannerConfig());
EXPECT_EQ(manager->GetCurrentBannerConfig()->GetWebOrNativeAppName(),
u"Manifest test app");
}
// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ValidManifestShowBanner DISABLED_ValidManifestShowBanner
#else
#define MAYBE_ValidManifestShowBanner ValidManifestShowBanner
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
MAYBE_ValidManifestShowBanner) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
RunBannerTest(
web_contents(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
std::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
}
// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ImplicitName DISABLED_ImplicitName
#else
#define MAYBE_ImplicitName ImplicitName
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_ImplicitName) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_empty_name_short_name.json&application-name=TestApp");
RunBannerTest(web_contents(), manager.get(), test_url, std::nullopt);
ASSERT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
ASSERT_TRUE(manager->GetCurrentBannerConfig());
EXPECT_EQ(manager->GetCurrentBannerConfig()->GetWebOrNativeAppName(),
u"TestApp");
}
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, ImplicitNameDocumentTitle) {
std::unique_ptr<AppBannerManagerTest> manager(CreateAppBannerManager());
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_empty_name_short_name.json");
RunBannerTest(web_contents(), manager.get(), test_url, std::nullopt);
ASSERT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResult(),
InstallableWebAppCheckResult::kYes_Promotable);
ASSERT_TRUE(manager->GetCurrentBannerConfig());
EXPECT_EQ(manager->GetCurrentBannerConfig()->GetWebOrNativeAppName(),
u"Web app banner test page");
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(http://crbug.com/329255543): Add the config data after the struct is
// converted to a class w/o const members (this makes it hard to work with
// TestFuture).
using AppBannerInstallableCallback =
base::RepeatingCallback<void(std::optional<ManifestId>)>;
class AppBannerManagerObserverAdapter : public AppBannerManager::Observer {
public:
AppBannerManagerObserverAdapter(AppBannerManager* source,
AppBannerInstallableCallback callback,
InstallableWebAppCheckResult filter)
: callback_(std::move(callback)), filter_(filter) {
observation_.Observe(source);
}
void OnInstallableWebAppStatusUpdated(
InstallableWebAppCheckResult result,
const std::optional<WebAppBannerData>& data) override {
if (result != filter_) {
LOG(WARNING) << "Filtered result: " << base::ToString(result);
return;
}
callback_.Run(data.has_value() ? std::make_optional(data->manifest_id)
: std::nullopt);
}
private:
AppBannerInstallableCallback callback_;
InstallableWebAppCheckResult filter_;
base::ScopedObservation<AppBannerManager, AppBannerManager::Observer>
observation_{this};
};
// Test class that doesn't do complex faking of the AppBannerManager.
class AppBannerManagerNoFakeBrowserTest
: public AppBannerManagerBrowserTestBase {
public:
AppBannerManagerNoFakeBrowserTest() = default;
private:
// Disable the banners in the browser so it won't interfere with the test.
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(AppBannerManagerNoFakeBrowserTest, Prompts) {
const GURL kAppUrl =
embedded_test_server()->GetURL("/banners/manifest_test_page.html");
AppBannerManager* app_banner_manager =
AppBannerManager::FromWebContents(web_contents());
base::test::TestFuture<std::optional<ManifestId>> future;
AppBannerManagerObserverAdapter observer(
app_banner_manager, future.GetRepeatingCallback(),
InstallableWebAppCheckResult::kYes_Promotable);
ASSERT_TRUE(content::NavigateToURL(web_contents(), kAppUrl));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(kAppUrl, future.Get());
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerNoFakeBrowserTest,
PromptsForInnerCraftedOuterDiy) {
const GURL kDiyAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/index.html");
const GURL kInnerAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html");
// Install a DIY app.
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kDiyAppUrl);
web_app_info->is_diy_app = true;
web_app_info->title = u"test web app";
web_app_info->user_display_mode =
web_app::mojom::UserDisplayMode::kStandalone;
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
AppBannerManager* app_banner_manager =
AppBannerManager::FromWebContents(web_contents());
base::test::TestFuture<std::optional<ManifestId>> future;
AppBannerManagerObserverAdapter observer(
app_banner_manager, future.GetRepeatingCallback(),
InstallableWebAppCheckResult::kYes_Promotable);
ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(kInnerAppUrl, future.Get());
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerNoFakeBrowserTest,
NoPromptsForInnerDiyOuterDiy) {
const GURL kDiyAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/index.html");
const GURL kInnerDiyAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/nested/diy.html");
// Install a DIY app.
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kDiyAppUrl);
web_app_info->is_diy_app = true;
web_app_info->title = u"test web app";
web_app_info->user_display_mode =
web_app::mojom::UserDisplayMode::kStandalone;
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
AppBannerManager* app_banner_manager =
AppBannerManager::FromWebContents(web_contents());
base::test::TestFuture<std::optional<ManifestId>> future;
AppBannerManagerObserverAdapter observer(app_banner_manager,
future.GetRepeatingCallback(),
InstallableWebAppCheckResult::kNo);
ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerDiyAppUrl));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(kInnerDiyAppUrl, future.Get());
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerNoFakeBrowserTest,
NoPromptForOuterCraftedDisplayBrowserInnerCrafted) {
const GURL kOuterAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/index.html");
const GURL kInnerAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html");
// Even if the outer crafted app opens in a browser tab, it should still block
// any nested installations.
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kOuterAppUrl);
web_app_info->title = u"test web app";
web_app_info->user_display_mode = web_app::mojom::UserDisplayMode::kBrowser;
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
AppBannerManager* app_banner_manager =
AppBannerManager::FromWebContents(web_contents());
base::test::TestFuture<std::optional<ManifestId>> future;
AppBannerManagerObserverAdapter observer(
app_banner_manager, future.GetRepeatingCallback(),
InstallableWebAppCheckResult::kNo_AlreadyInstalled);
ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(kInnerAppUrl, future.Get());
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerNoFakeBrowserTest,
NoPromptForOuterCraftedDisplayStandaloneInnerCrafted) {
const GURL kOuterAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/index.html");
const GURL kInnerAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html");
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kOuterAppUrl);
web_app_info->title = u"test web app";
web_app_info->user_display_mode =
web_app::mojom::UserDisplayMode::kStandalone;
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
AppBannerManager* app_banner_manager =
AppBannerManager::FromWebContents(web_contents());
base::test::TestFuture<std::optional<ManifestId>> future;
AppBannerManagerObserverAdapter observer(
app_banner_manager, future.GetRepeatingCallback(),
InstallableWebAppCheckResult::kNo_AlreadyInstalled);
ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(kInnerAppUrl, future.Get());
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerNoFakeBrowserTest,
NoPromptForOuterCraftedDisplayBrowserInnerDiy) {
const GURL kOuterAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/index.html");
const GURL kInnerAppUrl =
embedded_test_server()->GetURL("/web_apps/nesting/nested/diy.html");
// Install a DIY app.
auto web_app_info =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kOuterAppUrl);
web_app_info->title = u"test web app";
web_app_info->user_display_mode = web_app::mojom::UserDisplayMode::kBrowser;
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
AppBannerManager* app_banner_manager =
AppBannerManager::FromWebContents(web_contents());
base::test::TestFuture<std::optional<ManifestId>> future;
AppBannerManagerObserverAdapter observer(app_banner_manager,
future.GetRepeatingCallback(),
InstallableWebAppCheckResult::kNo);
ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(kInnerAppUrl, future.Get());
}
#endif // !BUILDFLAG(IS_ANDROID)
INSTANTIATE_TEST_SUITE_P(All,
AppBannerManagerBrowserTest,
::testing::Values(CheckWebAppExistence::kAsync,
CheckWebAppExistence::kSync));
} // namespace
} // namespace webapps