blob: ba73b01101aef454494051e606d5ed738d5a9b5c [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/banners/app_banner_manager.h"
#include "chrome/browser/banners/app_banner_manager_browsertest_base.h"
#include "chrome/browser/banners/app_banner_manager_desktop.h"
#include "chrome/browser/banners/app_banner_metrics.h"
#include "chrome/browser/banners/app_banner_settings_helper.h"
#include "chrome/browser/engagement/site_engagement_score.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/installable/installable_logging.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"
namespace banners {
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() override {}
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::Closure on_done) { on_done_ = on_done; }
// Configures a callback to be invoked from OnBannerPromptReply.
void PrepareBannerPromptReply(base::Closure on_banner_prompt_reply) {
on_banner_prompt_reply_ = on_banner_prompt_reply;
}
protected:
// All calls to RequestAppBanner should terminate in one of Stop() (not
// showing banner), UpdateState(State::PENDING_ENGAGEMENT) (waiting for
// sufficient engagement), or ShowBannerUi(). Override these methods to
// capture test status.
void Stop(InstallableStatusCode code) override {
AppBannerManager::Stop(code);
ASSERT_FALSE(banner_shown_.get());
banner_shown_.reset(new bool(false));
install_source_.reset(new WebappInstallSource(WebappInstallSource::COUNT));
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, 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("AppBanner.WebApp.Shown");
ASSERT_FALSE(banner_shown_.get());
banner_shown_.reset(new bool(true));
install_source_.reset(new WebappInstallSource(install_source));
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, on_done_);
}
void UpdateState(AppBannerManager::State state) override {
AppBannerManager::UpdateState(state);
if (state == AppBannerManager::State::PENDING_ENGAGEMENT ||
state == AppBannerManager::State::PENDING_PROMPT) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, on_done_);
}
}
void OnBannerPromptReply(blink::mojom::AppBannerControllerPtr controller,
blink::mojom::AppBannerPromptReply reply) override {
AppBannerManager::OnBannerPromptReply(std::move(controller), reply);
if (on_banner_prompt_reply_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(on_banner_prompt_reply_));
}
}
base::WeakPtr<AppBannerManager> GetWeakPtr() override {
return weak_factory_.GetWeakPtr();
}
void InvalidateWeakPtrs() override { weak_factory_.InvalidateWeakPtrs(); }
bool IsSupportedAppPlatform(const base::string16& platform) const override {
return base::EqualsASCII(platform, "chrome_web_store");
}
bool IsRelatedAppInstalled(
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.string(),
"chrome_web_store") &&
base::EqualsASCII(related_app.id.string(), "installed-extension-id");
}
private:
base::Closure on_done_;
// If non-null, |on_banner_prompt_reply_| will be invoked from
// OnBannerPromptReply.
base::Closure on_banner_prompt_reply_;
std::unique_ptr<bool> banner_shown_;
std::unique_ptr<WebappInstallSource> install_source_;
base::WeakPtrFactory<AppBannerManagerTest> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AppBannerManagerTest);
};
class AppBannerManagerBrowserTest : public AppBannerManagerBrowserTestBase {
public:
AppBannerManagerBrowserTest() : AppBannerManagerBrowserTestBase() {}
void SetUpOnMainThread() override {
AppBannerSettingsHelper::SetTotalEngagementToTrigger(10);
SiteEngagementScore::SetParamValuesForTesting();
// Make sure app banners are disabled in the browser, otherwise they will
// interfere with the test.
AppBannerManagerDesktop::DisableTriggeringForTesting();
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,
base::Optional<InstallableStatusCode> expected_code_for_histogram) {
base::HistogramTester histograms;
manager->clear_will_show();
SiteEngagementService* service =
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 ||
manager->state() == State::INACTIVE);
// If in incognito, ensure that nothing is recorded.
histograms.ExpectTotalCount(banners::kMinutesHistogram, 0);
if (browser->profile()->IsOffTheRecord() || !expected_code_for_histogram) {
histograms.ExpectTotalCount(banners::kInstallableStatusCodeHistogram, 0);
} else {
histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
*expected_code_for_histogram, 1);
}
}
void TriggerBannerFlowWithNavigation(Browser* browser,
AppBannerManagerTest* manager,
const GURL& url,
bool expected_will_show,
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_NAVIGATION),
expected_will_show, expected_state);
}
void TriggerBannerFlow(Browser* browser,
AppBannerManagerTest* manager,
base::OnceClosure trigger_task,
bool expected_will_show,
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());
EXPECT_EQ(expected_state, manager->state());
}
private:
DISALLOW_COPY_AND_ASSIGN(AppBannerManagerBrowserTest);
};
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerNoTypeInManifest) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
RunBannerTest(browser(), manager.get(),
GetBannerURLWithManifest("/banners/manifest_no_type.json"),
base::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"),
base::nullopt);
}
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.ExpectTotalCount(banners::kMinutesHistogram, 0);
histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
INSUFFICIENT_ENGAGEMENT, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerNotCreated) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
SiteEngagementService* service =
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);
// Navigate and expect Stop() to be called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectTotalCount(banners::kMinutesHistogram, 0);
histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
RENDERER_CANCELLED, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerCancelled) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
SiteEngagementService* service =
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);
// Navigate to about:blank and expect Stop() to be called.
TriggerBannerFlowWithNavigation(browser(), manager.get(), GURL("about:blank"),
false /* expected_will_show */,
State::INACTIVE);
histograms.ExpectTotalCount(banners::kMinutesHistogram, 0);
histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
RENDERER_CANCELLED, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerPromptWithGesture) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
SiteEngagementService* service =
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);
// 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.ExpectTotalCount(banners::kMinutesHistogram, 1);
histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
SHOWING_WEB_APP_BANNER, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
WebAppBannerNeedsEngagement) {
AppBannerSettingsHelper::SetTotalEngagementToTrigger(1);
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
SiteEngagementService* service =
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(&SiteEngagementService::HandleNavigation,
base::Unretained(service),
browser()->tab_strip_model()->GetActiveWebContents(),
ui::PageTransition::PAGE_TRANSITION_TYPED),
false /* expected_will_show */, State::PENDING_PROMPT);
// 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.ExpectTotalCount(banners::kMinutesHistogram, 1);
histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
SHOWING_WEB_APP_BANNER, 1);
}
IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest, WebAppBannerReprompt) {
std::unique_ptr<AppBannerManagerTest> manager(
CreateAppBannerManager(browser()));
base::HistogramTester histograms;
SiteEngagementService* service =
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);
// 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->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.ExpectTotalCount(banners::kMinutesHistogram, 1);
histograms.ExpectUniqueSample(banners::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(banners::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(banners::kInstallableStatusCodeHistogram,
PREFER_RELATED_APPLICATIONS, 1);
}
} // namespace banners