blob: 51009ca55e97db7ec7e5cdf12f4246c53dc7308e [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 <map>
#include <memory>
#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/run_loop.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 "build/build_config.h"
#include "chrome/browser/banners/app_banner_manager_browsertest_base.h"
#include "chrome/browser/banners/app_banner_manager_desktop.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/site_engagement/content/site_engagement_score.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/webapps/browser/banners/app_banner_manager.h"
#include "components/webapps/browser/banners/app_banner_metrics.h"
#include "components/webapps/browser/banners/app_banner_settings_helper.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/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
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.
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 {}
bool TriggeringDisabledForTesting() const override { return false; }
void RequestAppBanner(const GURL& validated_url) 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(validated_url);
}
bool banner_shown() { return banner_shown_.get() && *banner_shown_; }
WebappInstallSource install_source() {
if (install_source_.get())
return *install_source_;
return WebappInstallSource::COUNT;
}
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);
}
bool IsAppFullyInstalledForSiteUrl(const GURL& site_url) const override {
return false;
}
bool IsAppPartiallyInstalledForSiteUrl(const GURL& site_url) const override {
return false;
}
void SaveInstallationDismissedForMl(const GURL& manifest_id) override {}
void SaveInstallationIgnoredForMl(const GURL& manifest_id) override {}
void SaveInstallationAcceptedForMl(const GURL& manifest_id) override {}
bool IsMlPromotionBlockedByHistoryGuardrail(
const GURL& manifest_id) override {
return false;
}
void OnMlInstallPrediction(base::PassKey<MLInstallabilityPromoter>,
std::string result_label) override {}
segmentation_platform::SegmentationPlatformService*
GetSegmentationPlatformService() override {
return nullptr;
}
protected:
// 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 one of Stop() (not showing banner)
// or UpdateState(State::PENDING_ENGAGEMENT) (waiting for sufficient
// engagement). 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::make_unique<WebappInstallSource>(WebappInstallSource::COUNT);
if (on_done_)
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_done_));
}
void ShowBannerUi(WebappInstallSource install_source) override {
// Fake the call to ReportStatus here - this is usually called in
// platform-specific code which is not exposed here.
ReportStatus(SHOWING_WEB_APP_BANNER);
RecordDidShowBanner();
ASSERT_FALSE(banner_shown_.get());
banner_shown_ = std::make_unique<bool>(true);
install_source_ = std::make_unique<WebappInstallSource>(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_ENGAGEMENT ||
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(
mojo::Remote<blink::mojom::AppBannerController> controller,
blink::mojom::AppBannerPromptReply reply) override {
AppBannerManager::OnBannerPromptReply(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");
}
bool IsWebAppConsideredInstalled() const override { return false; }
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::unique_ptr<WebappInstallSource> install_source_;
base::WeakPtrFactory<AppBannerManagerTest> weak_factory_{this};
};
class AppBannerManagerBrowserTest : public AppBannerManagerBrowserTestBase {
public:
AppBannerManagerBrowserTest()
: disable_banner_trigger_(&test::g_disable_banner_triggering_for_testing,
true),
total_engagement_(
AppBannerSettingsHelper::ScopeTotalEngagementForTesting(10)) {}
AppBannerManagerBrowserTest(const AppBannerManagerBrowserTest&) = delete;
AppBannerManagerBrowserTest& operator=(const AppBannerManagerBrowserTest&) =
delete;
void SetUpOnMainThread() override {
site_engagement::SiteEngagementScore::SetParamValuesForTesting();
AppBannerManagerBrowserTestBase::SetUpOnMainThread();
}
protected:
std::unique_ptr<AppBannerManagerTest> CreateAppBannerManager(
Browser* browser) {
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
return std::make_unique<AppBannerManagerTest>(web_contents);
}
void RunBannerTest(
Browser* browser,
AppBannerManagerTest* manager,
const GURL& url,
absl::optional<InstallableStatusCode> expected_code_for_histogram) {
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser->profile());
service->ResetBaseScoreForURL(url, 10);
// Spin the run loop and wait for the manager to finish.
base::RunLoop run_loop;
manager->clear_will_show();
manager->PrepareDone(run_loop.QuitClosure());
NavigateParams nav_params(browser, url, ui::PAGE_TRANSITION_LINK);
ui_test_utils::NavigateToURL(&nav_params);
run_loop.Run();
EXPECT_EQ(expected_code_for_histogram.value_or(MAX_ERROR_CODE) ==
SHOWING_WEB_APP_BANNER,
manager->banner_shown());
EXPECT_EQ(WebappInstallSource::COUNT, 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 (browser->profile()->IsOffTheRecord() || !expected_code_for_histogram) {
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
} else {
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
*expected_code_for_histogram, 1);
}
}
void TriggerBannerFlowWithNavigation(Browser* browser,
AppBannerManagerTest* manager,
const GURL& url,
bool expected_will_show,
absl::optional<State> expected_state) {
// Use NavigateToURLWithDisposition as it isn't overloaded, so can be used
// with Bind.
TriggerBannerFlow(
browser, manager,
base::BindOnce(
base::IgnoreResult(&ui_test_utils::NavigateToURLWithDisposition),
browser, url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP),
expected_will_show, expected_state);
}
void TriggerBannerFlow(Browser* browser,
AppBannerManagerTest* manager,
base::OnceClosure trigger_task,
bool expected_will_show,
absl::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::AutoReset<double> total_engagement_;
};
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerNoTypeInManifest) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_no_type.json"),
absl::nullopt);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerNoTypeInManifestCapsExtension) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_no_type_caps.json"),
absl::nullopt);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerSvgIcon) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_svg_icon.json"),
absl::nullopt);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerWebPIcon) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_webp_icon.json"),
absl::nullopt);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
DelayedManifestTriggersPipeline) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(
browser(), manager.get(),
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"),
NO_MANIFEST);
// Dynamically add the manifest.
base::HistogramTester histograms;
TriggerBannerFlow(browser(), manager.get(), base::BindLambdaForTesting([&]() {
EXPECT_TRUE(content::ExecJs(
browser()->tab_strip_model()->GetActiveWebContents(),
"addManifestLinkTag()"));
}),
false,
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
RemovingManifestStopsPipeline) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(
browser(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
absl::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
// Dynamically remove the manifest.
base::HistogramTester histograms;
TriggerBannerFlow(browser(), manager.get(), base::BindLambdaForTesting([&]() {
EXPECT_TRUE(content::ExecJs(
browser()->tab_strip_model()->GetActiveWebContents(),
"removeAllManifestTags()"));
}),
false, AppBannerManager::State::COMPLETE);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 1);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
RENDERER_CANCELLED, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
ManifestChangeTriggersPipeline) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
// Cause the manifest test page to reach the PENDING_PROMPT stage of the
// app banner pipeline.
RunBannerTest(
browser(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
absl::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
// Dynamically change the manifest, which results in a
// Stop(RENDERER_CANCELLED), 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(
browser(), manager.get(), base::BindLambdaForTesting([&]() {
EXPECT_TRUE(content::ExecJs(
browser()->tab_strip_model()->GetActiveWebContents(),
"addManifestLinkTag('/banners/manifest_one_icon.json')"));
}),
false, absl::nullopt);
histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 1);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
RENDERER_CANCELLED, 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_F(AppBannerManagerBrowserTest, NoManifest) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(
browser(), manager.get(),
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"),
NO_MANIFEST);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, MissingManifest) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_missing.json"),
MANIFEST_EMPTY);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerInIFrame) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(
browser(), manager.get(),
embedded_test_server()->GetURL("/banners/iframe_test_page.html"),
NO_MANIFEST);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, DoesNotShowInIncognito) {
Browser* incognito_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(incognito_browser));
RunBannerTest(incognito_browser, manager.get(), GetBannerURL(), IN_INCOGNITO);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerInsufficientEngagement) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
GURL test_url = GetBannerURL();
// First run through: expect the manager to end up stopped in the pending
// state, without showing a banner.
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_ENGAGEMENT);
// Navigate and expect Stop() to be called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
INSUFFICIENT_ENGAGEMENT, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerNotCreated) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser()->profile());
GURL test_url = GetBannerURL();
service->ResetBaseScoreForURL(test_url, 10);
// Navigate and expect the manager to end up waiting for prompt to be called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
// Navigate and expect Stop() to be called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
RENDERER_CANCELLED, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerCancelled) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser()->profile());
// Explicitly call preventDefault(), but don't call prompt().
GURL test_url = GetBannerURLWithAction("cancel_prompt");
service->ResetBaseScoreForURL(test_url, 10);
// Navigate and expect the manager to end up waiting for prompt() to be
// called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_CANCELED);
// Navigate to about:blank and expect Stop() to be called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
RENDERER_CANCELLED, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerPromptWithGesture) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser()->profile());
GURL test_url = GetBannerURLWithAction("stash_event");
service->ResetBaseScoreForURL(test_url, 10);
// Navigate to page and get the pipeline started.
TriggerBannerFlowWithNavigation(browser(), 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(
browser(), manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript, browser(),
"callStashedPrompt();", true /* with_gesture */),
true /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
SHOWING_WEB_APP_BANNER, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerNeedsEngagement) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::AutoReset<double> scoped_engagement =
AppBannerSettingsHelper::ScopeTotalEngagementForTesting(1);
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser()->profile());
GURL test_url = GetBannerURLWithAction("stash_event");
service->ResetBaseScoreForURL(test_url, 0);
// Navigate and expect the manager to end up waiting for sufficient
// engagement.
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_ENGAGEMENT);
// Trigger an engagement increase that signals observers and expect the
// manager to end up waiting for prompt to be called.
TriggerBannerFlow(
browser(), manager.get(),
base::BindOnce(&site_engagement::SiteEngagementService::HandleNavigation,
base::Unretained(service),
browser()->tab_strip_model()->GetActiveWebContents(),
ui::PageTransition::PAGE_TRANSITION_TYPED),
false /* expected_will_show */, State::PENDING_PROMPT_NOT_CANCELED);
// Trigger prompt() and expect the banner to be shown.
TriggerBannerFlow(
browser(), manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript, browser(),
"callStashedPrompt();", true /* with_gesture */),
true /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
SHOWING_WEB_APP_BANNER, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerReprompt) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser()->profile());
GURL test_url = GetBannerURLWithAction("stash_event");
service->ResetBaseScoreForURL(test_url, 10);
// Navigate to page and get the pipeline started.
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_PROMPT_NOT_CANCELED);
// Call prompt to show the banner.
TriggerBannerFlow(
browser(), manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript, browser(),
"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(
browser(), manager.get(),
base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript, browser(),
"callStashedPrompt();", true /* with_gesture */),
true /* expected_will_show */, State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
SHOWING_WEB_APP_BANNER, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, PreferRelatedAppUnknown) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_prefer_related_apps_unknown.json");
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::PENDING_ENGAGEMENT);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, PreferRelatedChromeApp) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_prefer_related_chrome_app.json");
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
PREFER_RELATED_APPLICATIONS, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
ListedRelatedChromeAppInstalled) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
GURL test_url = embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest="
"manifest_listing_related_chrome_app.json");
TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
false /* expected_will_show */,
State::COMPLETE);
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
PREFER_RELATED_APPLICATIONS, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerTerminated) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
site_engagement::SiteEngagementService* service =
site_engagement::SiteEngagementService::Get(browser()->profile());
GURL test_url = GetBannerURL();
service->ResetBaseScoreForURL(test_url, 10);
// Navigate and expect the manager to end up waiting for prompt() to be
// called.
TriggerBannerFlowWithNavigation(browser(), 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(browser(), manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
// Expect the manifest to be reset to an empty manifest.
EXPECT_EQ(manager->manifest(), *blink::mojom::Manifest::New());
// Expect RENDERER_CANCELLED to be called when an existing call is terminated.
histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
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::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
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) {
base::AutoReset<double> scoped_engagement =
AppBannerSettingsHelper::ScopeTotalEngagementForTesting(1);
ASSERT_TRUE(embedded_test_server()->Start());
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
// Triggering flow to first URL with a pending prompt.
TriggerBannerFlowWithNavigation(browser(), 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(browser(), manager.get(),
Get2ndInstallableURL(),
/*expected_will_show=*/false, absl::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 SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
AppBannerManagerBrowserTest::SetUpOnMainThread();
}
content::WebContents* GetWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
};
class AppBannerManagerPrerenderBrowserTest
: public AppBannerManagerMPArchBrowserTest {
public:
AppBannerManagerPrerenderBrowserTest()
: prerender_helper_(base::BindRepeating(
&AppBannerManagerPrerenderBrowserTest::GetWebContents,
base::Unretained(this))) {}
~AppBannerManagerPrerenderBrowserTest() override = default;
AppBannerManagerPrerenderBrowserTest(
const AppBannerManagerPrerenderBrowserTest&) = delete;
AppBannerManagerPrerenderBrowserTest& operator=(
const AppBannerManagerPrerenderBrowserTest&) = delete;
void SetUp() override {
prerender_helper_.SetUp(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(ui_test_utils::NavigateToURL(browser(), initial_url));
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
EXPECT_EQ(manager->state(), AppBannerManager::State::INACTIVE);
// Load a page in the prerender.
GURL prerender_url = GetBannerURL();
const int host_id = prerender_test_helper().AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*GetWebContents(),
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(ui_test_utils::NavigateToURL(browser(), initial_url));
// Initialize a MockWebContentsObserver to ensure that DidUpdateManifestURL is
// not invoked for fenced frame.
testing::NiceMock<content::MockWebContentsObserver> observer(
GetWebContents());
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
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(
GetWebContents()->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);
}
class AppBannerServiceWorkerCriteriaTest : public AppBannerManagerBrowserTest {
public:
AppBannerServiceWorkerCriteriaTest() = default;
~AppBannerServiceWorkerCriteriaTest() override = default;
AppBannerServiceWorkerCriteriaTest(
const AppBannerServiceWorkerCriteriaTest&) = delete;
AppBannerServiceWorkerCriteriaTest& operator=(
const AppBannerServiceWorkerCriteriaTest&) = delete;
void SetUpOnMainThread() override {
AppBannerManagerBrowserTest::SetUpOnMainThread();
}
void CheckInstallableResult(
AppBannerManager::InstallableWebAppCheckResult result,
AppBannerManager::InstallableWebAppCheckResult expected_control_result) {
EXPECT_EQ(result,
AppBannerManager::InstallableWebAppCheckResult::kYes_Promotable);
}
};
IN_PROC_BROWSER_TEST_F(AppBannerServiceWorkerCriteriaTest, ShowBanner) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(
browser(), manager.get(),
embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
absl::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
EXPECT_EQ(manager->GetInstallableWebAppCheckResultForTesting(),
AppBannerManager::InstallableWebAppCheckResult::kYes_Promotable);
}
IN_PROC_BROWSER_TEST_F(AppBannerServiceWorkerCriteriaTest, NoServiceWorker) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
embedded_test_server()->GetURL(
"/banners/manifest_no_service_worker.html"),
/*expected_code_for_histogram=*/absl::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
CheckInstallableResult(manager->GetInstallableWebAppCheckResultForTesting(),
AppBannerManager::InstallableWebAppCheckResult::kNo);
}
IN_PROC_BROWSER_TEST_F(AppBannerServiceWorkerCriteriaTest, NoFetchHandler) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
embedded_test_server()->GetURL(
"/banners/no_sw_fetch_handler_test_page.html"),
/*expected_code_for_histogram=*/absl::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
CheckInstallableResult(manager->GetInstallableWebAppCheckResultForTesting(),
AppBannerManager::InstallableWebAppCheckResult::kNo);
}
IN_PROC_BROWSER_TEST_F(AppBannerServiceWorkerCriteriaTest,
PendingServiceWorker) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::unique_ptr<AppBannerManagerTest> manager =
std::make_unique<AppBannerManagerTest>(web_contents);
RunBannerTest(browser(), manager.get(),
embedded_test_server()->GetURL(
"/banners/manifest_no_service_worker.html"),
absl::nullopt);
EXPECT_EQ(manager->state(),
AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED);
CheckInstallableResult(
manager->GetInstallableWebAppCheckResultForTesting(),
AppBannerManager::InstallableWebAppCheckResult::kUnknown);
EXPECT_EQ(manager->GetAppName(), u"Manifest test app");
}
} // namespace
} // namespace webapps