blob: 8006f2f35b740239562ffed7b3a97c6e3ff5b36a [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/installable/installable_manager.h"
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_simple_task_runner.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/https_upgrades_util.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/webapps/browser/banners/app_banner_manager.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/installable/installable_data.h"
#include "components/webapps/browser/installable/installable_evaluator.h"
#include "components/webapps/browser/installable/installable_icon_fetcher.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.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"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/ui_test_utils.h"
#endif
namespace webapps {
namespace {
#if !BUILDFLAG(IS_ANDROID)
const char kInsecureOrigin[] = "http://www.google.com";
const char kOtherInsecureOrigin[] = "http://maps.google.com";
const char kUnsafeSecureOriginFlag[] =
"unsafely-treat-insecure-origin-as-secure";
#endif
InstallableParams GetManifestParams() {
InstallableParams params;
params.check_eligibility = true;
return params;
}
InstallableParams GetWebAppParams() {
InstallableParams params = GetManifestParams();
params.installable_criteria = InstallableCriteria::kValidManifestWithIcons;
params.valid_primary_icon = true;
return params;
}
InstallableParams GetPrimaryIconParams() {
InstallableParams params = GetManifestParams();
params.valid_primary_icon = true;
return params;
}
InstallableParams GetPrimaryIconPreferMaskableParams() {
InstallableParams params = GetManifestParams();
params.valid_primary_icon = true;
params.prefer_maskable_icon = true;
return params;
}
} // anonymous namespace
// Used only for testing pages where the manifest URL is changed. This class
// will dispatch a RunLoop::QuitClosure when internal state is reset.
class ResetDataInstallableManager : public InstallableManager {
public:
explicit ResetDataInstallableManager(content::WebContents* web_contents)
: InstallableManager(web_contents) {}
~ResetDataInstallableManager() override = default;
void SetQuitClosure(base::RepeatingClosure quit_closure) {
quit_closure_ = quit_closure;
}
bool GetOnResetData() { return is_reset_data_; }
void ClearOnResetData() { is_reset_data_ = false; }
protected:
void OnResetData() override {
is_reset_data_ = true;
if (quit_closure_)
quit_closure_.Run();
}
private:
base::RepeatingClosure quit_closure_;
bool is_reset_data_ = false;
};
class CallbackTester {
public:
explicit CallbackTester(
base::RepeatingClosure quit_closure,
scoped_refptr<base::SequencedTaskRunner> test_task_runner =
base::SequencedTaskRunner::GetCurrentDefault())
: quit_closure_(quit_closure), test_task_runner_(test_task_runner) {}
void OnDidFinishInstallableCheck(const InstallableData& data) {
errors_ = data.errors;
manifest_url_ = *data.manifest_url;
manifest_ = data.manifest->Clone();
metadata_ = data.web_page_metadata->Clone();
primary_icon_url_ = *data.primary_icon_url;
if (data.primary_icon)
primary_icon_ = std::make_unique<SkBitmap>(*data.primary_icon);
has_maskable_primary_icon_ = data.has_maskable_primary_icon;
installable_check_passed_ = data.installable_check_passed;
screenshots_ = *data.screenshots;
test_task_runner_->PostTask(FROM_HERE, quit_closure_);
}
const std::vector<InstallableStatusCode>& errors() const { return errors_; }
const GURL& manifest_url() const { return manifest_url_; }
const blink::mojom::Manifest& manifest() const {
DCHECK(manifest_);
return *manifest_;
}
const mojom::WebPageMetadata& metadata() const { return *metadata_; }
const GURL& primary_icon_url() const { return primary_icon_url_; }
const SkBitmap* primary_icon() const { return primary_icon_.get(); }
bool has_maskable_primary_icon() const { return has_maskable_primary_icon_; }
const std::vector<Screenshot>& screenshots() const { return screenshots_; }
bool installable_check_passed() const { return installable_check_passed_; }
private:
std::vector<InstallableStatusCode> errors_;
GURL manifest_url_;
blink::mojom::ManifestPtr manifest_ = blink::mojom::Manifest::New();
mojom::WebPageMetadataPtr metadata_ = mojom::WebPageMetadata::New();
GURL primary_icon_url_;
std::unique_ptr<SkBitmap> primary_icon_;
bool has_maskable_primary_icon_;
std::vector<Screenshot> screenshots_;
bool installable_check_passed_;
base::RepeatingClosure quit_closure_;
scoped_refptr<base::SequencedTaskRunner> test_task_runner_;
};
class NestedCallbackTester {
public:
NestedCallbackTester(InstallableManager* manager,
const InstallableParams& params,
base::OnceClosure quit_closure)
: manager_(manager),
params_(params),
quit_closure_(std::move(quit_closure)) {}
void Run() {
manager_->GetData(
params_, base::BindOnce(&NestedCallbackTester::OnDidFinishFirstCheck,
base::Unretained(this)));
}
void OnDidFinishFirstCheck(const InstallableData& data) {
errors_ = data.errors;
manifest_url_ = *data.manifest_url;
manifest_ = data.manifest->Clone();
primary_icon_url_ = *data.primary_icon_url;
if (data.primary_icon)
primary_icon_ = std::make_unique<SkBitmap>(*data.primary_icon);
installable_check_passed_ = data.installable_check_passed;
manager_->GetData(
params_, base::BindOnce(&NestedCallbackTester::OnDidFinishSecondCheck,
base::Unretained(this)));
}
void OnDidFinishSecondCheck(const InstallableData& data) {
EXPECT_EQ(errors_, data.errors);
EXPECT_EQ(manifest_url_, *data.manifest_url);
EXPECT_EQ(primary_icon_url_, *data.primary_icon_url);
EXPECT_EQ(primary_icon_.get(), data.primary_icon);
EXPECT_EQ(installable_check_passed_, data.installable_check_passed);
EXPECT_EQ(blink::IsEmptyManifest(*manifest_),
blink::IsEmptyManifest(*data.manifest));
EXPECT_EQ(manifest_->start_url, data.manifest->start_url);
EXPECT_EQ(manifest_->display, data.manifest->display);
EXPECT_EQ(manifest_->name, data.manifest->name);
EXPECT_EQ(manifest_->short_name, data.manifest->short_name);
EXPECT_EQ(manifest_->display_override, data.manifest->display_override);
std::move(quit_closure_).Run();
}
private:
raw_ptr<InstallableManager> manager_;
InstallableParams params_;
base::OnceClosure quit_closure_;
std::vector<InstallableStatusCode> errors_;
GURL manifest_url_;
blink::mojom::ManifestPtr manifest_;
GURL primary_icon_url_;
std::unique_ptr<SkBitmap> primary_icon_;
bool installable_check_passed_;
};
class InstallableManagerBrowserTest : public PlatformBrowserTest {
public:
InstallableManagerBrowserTest()
: disable_banner_trigger_(&test::g_disable_banner_triggering_for_testing,
true),
scoped_min_favicon_size_(&test::g_minimum_favicon_size_for_testing,
32) {
}
void SetUpOnMainThread() override {
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/banners");
ASSERT_TRUE(embedded_test_server()->Start());
}
// Returns a test server URL to a page controlled by a |manifest_url| injected
// as the manifest tag.
std::string GetURLOfPageWithManifest(const std::string& manifest_url) {
return "/banners/manifest_test_page.html?manifest=" +
embedded_test_server()->GetURL(manifest_url).spec();
}
std::string GetUrlOfPageWithTags(
const std::string& base_page,
const std::map<std::string, std::string> tags) {
std::string test_url = base_page + "?";
for (const auto& [key, value] : tags) {
test_url.append("&" + key + "=" + value);
}
return test_url;
}
std::string GetUrlOfPageWithManifestAndTags(
const std::string& manifest_url,
std::map<std::string, std::string> tags) {
std::string test_url = "/banners/manifest_test_page.html";
tags["manifest"] = manifest_url;
return GetUrlOfPageWithTags(test_url, tags);
}
void NavigateToPath(const std::string& path) {
GURL test_url = embedded_test_server()->GetURL(path);
ASSERT_TRUE(content::NavigateToURL(web_contents(), test_url));
content::RunAllTasksUntilIdle();
}
void NavigateAndRunInstallableManager(CallbackTester* tester,
const InstallableParams& params,
const std::string& url) {
NavigateToPath(url);
RunInstallableManager(tester, params);
}
std::vector<content::InstallabilityError>
NavigateAndGetAllInstallabilityErrors(const std::string& url) {
NavigateToPath(url);
return GetAllInstallabilityErrors();
}
void RunInstallableManager(CallbackTester* tester,
const InstallableParams& params) {
InstallableManager* manager = GetManager();
manager->GetData(
params, base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester)));
}
InstallableManager* GetManager() {
InstallableManager::CreateForWebContents(web_contents());
InstallableManager* manager =
InstallableManager::FromWebContents(web_contents());
CHECK(manager);
return manager;
}
std::vector<content::InstallabilityError> GetAllInstallabilityErrors() {
InstallableManager* manager = GetManager();
base::RunLoop run_loop;
std::vector<content::InstallabilityError> result;
manager->GetAllErrors(base::BindLambdaForTesting(
[&](std::vector<content::InstallabilityError> installability_errors) {
result = std::move(installability_errors);
run_loop.Quit();
}));
run_loop.Run();
return result;
}
content::WebContents* web_contents() {
return chrome_test_utils::GetActiveWebContents(this);
}
private:
// Disable the banners in the browser so it won't interfere with the test.
base::AutoReset<bool> disable_banner_trigger_;
// Set a min favicon size for testing.
base::AutoReset<int> scoped_min_favicon_size_;
};
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
ManagerBeginsInEmptyState) {
// Ensure that the InstallableManager starts off with everything null.
InstallableManager* manager = GetManager();
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_TRUE(manager->manifest_url().is_empty());
EXPECT_FALSE(manager->page_data_->primary_icon_fetched());
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}
// Skip ManagerInIncognito on Android. Android launches incognito differently.
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, ManagerInIncognito) {
// Ensure that the InstallableManager returns an error if called in an
// incognito profile.
Browser* incognito_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
content::WebContents* web_contents =
incognito_browser->tab_strip_model()->GetActiveWebContents();
auto manager = std::make_unique<InstallableManager>(web_contents);
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// NavigateToPath
GURL test_url =
embedded_test_server()->GetURL("/banners/manifest_test_page.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, test_url));
manager->GetData(GetManifestParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_TRUE(manager->manifest_url().is_empty());
EXPECT_FALSE(manager->page_data_->primary_icon_fetched());
EXPECT_EQ(
std::vector<InstallableStatusCode>{InstallableStatusCode::IN_INCOGNITO},
tester->errors());
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}
#endif
// TODO(crbug.com/379660142) Re-enable this test once the flakiness is fixed.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_CheckNoManifest DISABLED_CheckNoManifest
#else
#define MAYBE_CheckNoManifest CheckNoManifest
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, MAYBE_CheckNoManifest) {
// Ensure that a page with no manifest returns the appropriate error and with
// null fields for everything.
base::HistogramTester histograms;
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// Navigating resets histogram state, so do it before recording a histogram.
GURL url =
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
RunInstallableManager(tester.get(), GetManifestParams());
run_loop.Run();
// If there is no manifest, it should be the default one.
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(), url));
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_THAT(tester->errors(), testing::IsEmpty());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifest404) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
std::string path = GetURLOfPageWithManifest("/banners/manifest_missing.json");
NavigateAndRunInstallableManager(tester.get(), GetManifestParams(), path);
run_loop.Run();
// The installable manager should return a manifest URL even if it 404s.
// Additionally a default manifest should be returned.
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
embedded_test_server()->GetURL(path)));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR},
tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifestOnly) {
// Verify that asking for just the manifest works as expected.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetManifestParams(),
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckInstallableParamsDefaultConstructor) {
// Verify that using InstallableParams' default constructor is equivalent to
// just asking for the manifest alone.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params;
NavigateAndRunInstallableManager(tester.get(), params,
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, FetchWebPageMetaData) {
InstallableParams params;
params.check_eligibility = true;
params.fetch_metadata = true;
const std::map<std::string, std::string> meta_tags = {
{"application-name", "Test App Name"}, {"description", "description"}};
// Test fetch web page metadata.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), params,
GetUrlOfPageWithTags("/banners/manifest_test_page.html", meta_tags));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
EXPECT_EQ(u"Web app banner test page", tester->metadata().title);
EXPECT_EQ(u"description", tester->metadata().description);
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
// Test fetch web page metadata when no manifest.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
std::string path =
GetUrlOfPageWithTags("/banners/no_manifest_test_page.html", meta_tags);
NavigateAndRunInstallableManager(tester.get(), params, path);
run_loop.Run();
EXPECT_FALSE(tester->metadata().application_name.empty());
EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
embedded_test_server()->GetURL(path)));
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_THAT(tester->errors(), testing::IsEmpty());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckImplicitAppName) {
InstallableParams params;
params.fetch_metadata = true;
params.installable_criteria =
InstallableCriteria::kImplicitManifestFieldsHTML;
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
std::map<std::string, std::string> meta_tags = {
{"application-name", "Test App Name"}};
NavigateAndRunInstallableManager(
tester.get(), params,
GetUrlOfPageWithManifestAndTags("/banners/manifest_empty_name.json",
meta_tags));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_EQ(std::u16string(),
tester->manifest().name.value_or(std::u16string()));
EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckImplicitIcons) {
InstallableParams params;
params.fetch_metadata = true;
params.installable_criteria =
InstallableCriteria::kImplicitManifestFieldsHTML;
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
std::map<std::string, std::string> meta_tags = {
{"application-name", "Test App Name"},
{"icon", "/banners/256x256-red.png"}};
NavigateAndRunInstallableManager(
tester.get(), params,
GetUrlOfPageWithManifestAndTags("/banners/manifest_no_icon.json",
meta_tags));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_EQ(u"Manifest test app", tester->manifest().name);
EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
EXPECT_TRUE(tester->manifest().icons.empty());
EXPECT_FALSE(web_contents()->GetFaviconURLs().empty());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckManifestWithIconThatIsTooSmall) {
// This page has a manifest with only a 48x48 icon which is too small to be
// installable. Asking for a primary icon should fail with NO_ACCEPTABLE_ICON.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetPrimaryIconParams(),
GetURLOfPageWithManifest("/banners/manifest_too_small_icon.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::NO_ACCEPTABLE_ICON},
tester->errors());
}
// Ask for everything. This should fail with NO_ACCEPTABLE_ICON - the primary
// icon fetch has already failed, so that cached error stops the installable
// check from being performed.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
RunInstallableManager(tester.get(), GetPrimaryIconParams());
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::NO_ACCEPTABLE_ICON},
tester->errors());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckManifestWithOnlyRelatedApplications) {
// This page has a manifest with only related applications specified. Asking
// for just the manifest should succeed.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetManifestParams(),
GetURLOfPageWithManifest("/banners/play_app_manifest.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
// Ask for a primary icon (but don't navigate). This should fail with
// NO_ACCEPTABLE_ICON.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
RunInstallableManager(tester.get(), GetPrimaryIconParams());
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::NO_ACCEPTABLE_ICON},
tester->errors());
}
// Ask for everything. This should fail with NO_ACCEPTABLE_ICON - the primary
// icon fetch has already failed, so that cached error stops the installable
// check from being performed.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
auto params = GetWebAppParams();
params.installable_criteria = InstallableCriteria::kDoNotCheck;
RunInstallableManager(tester.get(), params);
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::NO_ACCEPTABLE_ICON},
tester->errors());
}
// Do not ask for primary icon. This should fail with several validity
// errors.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetWebAppParams();
params.valid_primary_icon = false;
RunInstallableManager(tester.get(), params);
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>(
{InstallableStatusCode::START_URL_NOT_VALID,
InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME,
InstallableStatusCode::MANIFEST_DISPLAY_NOT_SUPPORTED,
InstallableStatusCode::MANIFEST_MISSING_SUITABLE_ICON}),
tester->errors());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifestAndIcon) {
// Add to homescreen checks for manifest + primary icon.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetPrimaryIconParams(),
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
// Navigate to a page with a good maskable icon and a bad any
// icon. The maskable icon is fetched for primary icon.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetPrimaryIconPreferMaskableParams(),
GetURLOfPageWithManifest(
"/banners/manifest_bad_non_maskable_icon.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_TRUE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckWebapp) {
// Request everything.
{
base::HistogramTester histograms;
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// Navigating resets histogram state, so do it before recording a histogram.
NavigateToPath("/banners/manifest_test_page.html");
RunInstallableManager(tester.get(), GetWebAppParams());
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
// Verify that the returned state matches manager internal state.
InstallableManager* manager = GetManager();
EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_FALSE(manager->manifest_url().is_empty());
EXPECT_TRUE(manager->page_data_->primary_icon_fetched());
EXPECT_FALSE((manager->icon_url().is_empty()));
EXPECT_NE(nullptr, (manager->icon()));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
(manager->icon_error()));
EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}
// Request everything again without navigating away. This should work fine.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
auto params = GetWebAppParams();
// Make sure check_installability check is run.
params.is_debug_mode = true;
RunInstallableManager(tester.get(), params);
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
// Verify that the returned state matches manager internal state.
InstallableManager* manager = GetManager();
EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_FALSE(manager->manifest_url().is_empty());
EXPECT_TRUE(manager->page_data_->primary_icon_fetched());
EXPECT_FALSE((manager->icon_url().is_empty()));
EXPECT_NE(nullptr, (manager->icon()));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
(manager->icon_error()));
EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}
{
// Check that a subsequent navigation resets state.
ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
InstallableManager* manager = GetManager();
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_TRUE(manager->manifest_url().is_empty());
EXPECT_FALSE(manager->page_data_->primary_icon_fetched());
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckMaskableIcon) {
// Checks that InstallableManager chooses the correct primary icon when the
// manifest contains maskable icons.
// Checks that if a MASKABLE icon is specified, it is used as primary icon.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetPrimaryIconPreferMaskableParams(),
GetURLOfPageWithManifest("/banners/manifest_maskable.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_TRUE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
// Checks that we don't pick a MASKABLE icon if it was not requested.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetPrimaryIconParams(),
GetURLOfPageWithManifest("/banners/manifest_maskable.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
// Checks that we fall back to using an ANY icon if a MASKABLE icon is
// requested but not in the manifest.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(),
GetPrimaryIconPreferMaskableParams(),
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
// Checks that we fall back to using an ANY icon if a MASKABLE icon is
// requested but the maskable icon is bad.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetPrimaryIconPreferMaskableParams(),
GetURLOfPageWithManifest("/banners/manifest_bad_maskable.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
}
// Flaky on Mac. TODO(crbug.com/333331507): Re-enable once the issue is fixed.
#if BUILDFLAG(IS_MAC)
#define MAYBE_CheckFavicon DISABLED_CheckFavicon
#else
#define MAYBE_CheckFavicon CheckFavicon
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, MAYBE_CheckFavicon) {
// Checks that InstallableManager chooses the correct primary icon when
// fetching favicon.
InstallableParams installableParams = GetPrimaryIconPreferMaskableParams();
installableParams.fetch_favicon = true;
// Checks that favicon is fetched when no other icon provided.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), installableParams,
GetUrlOfPageWithManifestAndTags(
"/banners/manifest_no_icon.json",
{{"icon", "/banners/256x256-red.png"}}));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_EQ(tester->primary_icon_url(),
embedded_test_server()->GetURL("/banners/256x256-red.png"));
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
}
// Checks NOT fetching favicon when there is a manifest icon.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), installableParams,
GetUrlOfPageWithManifestAndTags(
"/banners/manifest_one_icon.json",
{{"icon", "/banners/256x256-red.png"}}));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_EQ(tester->primary_icon_url(),
embedded_test_server()->GetURL("/banners/image-512px.png"));
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
}
// Checks that we do not use favicon smaller than min size.
{
// Set a large min size so the icon will not be big enough.
base::AutoReset<int> scoped_min_favicon_size(
&test::g_minimum_favicon_size_for_testing, 1000);
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), installableParams,
GetUrlOfPageWithManifestAndTags(
"/banners/manifest_no_icon.json",
{{"icon", "/banners/256x256-red.png"}}));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckNavigationWithoutRunning) {
{
// Expect the call to ManifestAndIconTimeout to kick off an installable
// check and fail it on a not installable page.
base::HistogramTester histograms;
ASSERT_TRUE(content::NavigateToURL(
web_contents(),
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html")));
InstallableManager* manager = GetManager();
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// Set up a GetData call which will not record an installable metric to
// ensure we wait until the previous check has finished.
manager->GetData(
GetManifestParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
}
{
// Expect the call to ManifestAndIconTimeout to kick off an installable
// check and pass it on an installable page.
base::HistogramTester histograms;
NavigateToPath("/banners/manifest_test_page.html");
InstallableManager* manager = GetManager();
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// Set up a GetData call which will not record an installable metric to
// ensure we wait until the previous check has finished.
manager->GetData(
GetManifestParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckWebappInIframe) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetWebAppParams(),
"/banners/iframe_test_page.html");
run_loop.Run();
// The installable manager should only retrieve items in the main frame.
// The manifest should be the default one for the main frame, and not the
// one in the iframe inside of iframe_test_page.html (which points to the
// `manifest_test_page.html`).
EXPECT_TRUE(blink::IsDefaultManifest(
tester->manifest(),
embedded_test_server()->GetURL("/banners/iframe_test_page.html")));
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckDataUrlIcon) {
// Verify that InstallableManager can handle data URL icons.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetWebAppParams(),
GetURLOfPageWithManifest("/banners/manifest_data_url_icon.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_GT(tester->primary_icon()->width(), 0);
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckManifestCorruptedIcon) {
// Verify that the returned InstallableData::primary_icon is null if the web
// manifest points to a corrupt primary icon.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetPrimaryIconParams(),
GetURLOfPageWithManifest("/banners/manifest_bad_icon.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::NO_ACCEPTABLE_ICON},
tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckChangeInIconDimensions) {
// Verify that a follow-up request for a primary icon with a different size
// works.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetWebAppParams(),
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
auto params = GetWebAppParams();
// Make sure installable_check_passed check is run.
params.is_debug_mode = true;
RunInstallableManager(tester.get(), params);
run_loop.Run();
// The smaller primary icon requirements should allow this to pass.
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
}
#if BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/410745060): Flaky on android bots
#define MAYBE_CheckNestedCallsToGetData DISABLED_CheckNestedCallsToGetData
#else
#define MAYBE_CheckNestedCallsToGetData CheckNestedCallsToGetData
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
MAYBE_CheckNestedCallsToGetData) {
// Verify that we can call GetData while in a callback from GetData.
base::RunLoop run_loop;
InstallableParams params = GetWebAppParams();
std::unique_ptr<NestedCallbackTester> tester(
new NestedCallbackTester(GetManager(), params, run_loop.QuitClosure()));
tester->Run();
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
ManifestUrlChangeFlushesState) {
auto manager = std::make_unique<ResetDataInstallableManager>(web_contents());
// Start on a page with no manifest.
GURL url =
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
{
// Fetch the data. This should return an empty manifest.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(), url));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_THAT(tester->errors(),
testing::ElementsAre(InstallableStatusCode::NO_MANIFEST));
}
{
// Injecting a manifest URL but not navigating should flush the state.
base::RunLoop run_loop;
manager->SetQuitClosure(run_loop.QuitClosure());
EXPECT_TRUE(content::ExecJs(web_contents(), "addManifestLinkTag()"));
run_loop.Run();
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
}
{
// Fetch the data again. This should succeed.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
EXPECT_EQ(u"Manifest test app", tester->manifest().name);
EXPECT_EQ(std::u16string(),
tester->manifest().short_name.value_or(std::u16string()));
}
{
// Flush the state again by changing the manifest URL.
base::RunLoop run_loop;
manager->SetQuitClosure(run_loop.QuitClosure());
GURL manifest_url = embedded_test_server()->GetURL(
"/banners/manifest_short_name_only.json");
EXPECT_TRUE(content::ExecJs(
web_contents(), "changeManifestUrl('" + manifest_url.spec() + "');"));
run_loop.Run();
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
}
{
// Fetch again. This should return the data from the new manifest.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_EQ(std::u16string(),
tester->manifest().name.value_or(std::u16string()));
EXPECT_EQ(u"Manifest", tester->manifest().short_name);
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
}
#if defined(ARCH_CPU_X86)
#define MAYBE_DebugModeWithNoManifest DISABLED_DebugModeWithNoManifest
#else
#define MAYBE_DebugModeWithNoManifest DebugModeWithNoManifest
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
MAYBE_DebugModeWithNoManifest) {
// Ensure that a page with no manifest stops with NO_MANIFEST in debug mode.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetWebAppParams();
params.is_debug_mode = true;
ASSERT_TRUE(content::NavigateToURL(
web_contents(),
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html")));
RunInstallableManager(tester.get(), params);
run_loop.Run();
// The default manifest is created, but we still report no manifest.
EXPECT_THAT(tester->errors(),
testing::ElementsAre(InstallableStatusCode::NO_MANIFEST));
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
DebugModeAccumulatesErrorsWithManifest) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetWebAppParams();
params.is_debug_mode = true;
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest("/banners/play_app_manifest.json"));
run_loop.Run();
EXPECT_THAT(tester->errors(),
testing::UnorderedElementsAre(
InstallableStatusCode::START_URL_NOT_VALID,
InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME,
InstallableStatusCode::MANIFEST_DISPLAY_NOT_SUPPORTED,
InstallableStatusCode::MANIFEST_MISSING_SUITABLE_ICON,
InstallableStatusCode::NO_ACCEPTABLE_ICON));
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
DebugModeBadFallbackMaskable) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetPrimaryIconPreferMaskableParams();
params.is_debug_mode = true;
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest("/banners/manifest_one_bad_maskable.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::NO_ACCEPTABLE_ICON},
tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
GetAllInstallabilityErrorsNoErrors) {
EXPECT_THAT(
NavigateAndGetAllInstallabilityErrors("/banners/manifest_test_page.html"),
testing::IsEmpty());
// Should pass a second time with no issues.
EXPECT_THAT(GetAllInstallabilityErrors(), testing::IsEmpty());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
GetAllInstallabilityErrorsWithNoManifest) {
EXPECT_THAT(NavigateAndGetAllInstallabilityErrors(
"/banners/no_manifest_test_page.html"),
testing::UnorderedElementsAre(
GetInstallabilityError(InstallableStatusCode::NO_MANIFEST)));
// Should return a second time with no issues.
EXPECT_THAT(GetAllInstallabilityErrors(),
testing::UnorderedElementsAre(
GetInstallabilityError(InstallableStatusCode::NO_MANIFEST)));
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
GetAllInstallabilityErrorsWithPlayAppManifest) {
auto errors = std::vector<content::InstallabilityError>(
{GetInstallabilityError(InstallableStatusCode::START_URL_NOT_VALID),
GetInstallabilityError(
InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME),
GetInstallabilityError(
InstallableStatusCode::MANIFEST_DISPLAY_NOT_SUPPORTED),
GetInstallabilityError(
InstallableStatusCode::MANIFEST_MISSING_SUITABLE_ICON)});
errors.push_back(
GetInstallabilityError(InstallableStatusCode::NO_ACCEPTABLE_ICON));
EXPECT_EQ(errors,
NavigateAndGetAllInstallabilityErrors(
GetURLOfPageWithManifest("/banners/play_app_manifest.json")));
}
#if !BUILDFLAG(IS_ANDROID)
class InstallableManagerAllowlistOriginBrowserTest
: public InstallableManagerBrowserTest {
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(kUnsafeSecureOriginFlag, kInsecureOrigin);
}
};
IN_PROC_BROWSER_TEST_F(InstallableManagerAllowlistOriginBrowserTest,
SecureOriginCheckRespectsUnsafeFlag) {
// TODO(crbug.com/361129282): Remove scoped http allowlisting once the
// `unsafely-treat-insecure-origin-as-secure` flag adds to the HTTP allowlist.
ScopedAllowHttpForHostnamesForTesting allow_http(
{"www.google.com", "maps.google.com"}, browser()->profile()->GetPrefs());
// The allowlisted origin should be regarded as secure.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kInsecureOrigin)));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(InstallableEvaluator::IsContentSecure(web_contents()));
// While a non-allowlisted origin should not.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(kOtherInsecureOrigin)));
EXPECT_FALSE(InstallableEvaluator::IsContentSecure(contents));
}
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckScreenshots) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetManifestParams();
params.fetch_screenshots = true;
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest("/banners/manifest_with_screenshots.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(1u, tester->screenshots().size());
// Corresponding form_factor should filter out the screenshot with mismatched
// form_factor.
#if BUILDFLAG(IS_ANDROID)
EXPECT_LT(tester->screenshots()[0].image.width(),
tester->screenshots()[0].image.height());
#else
EXPECT_GT(tester->screenshots()[0].image.width(),
tester->screenshots()[0].image.height());
#endif
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckScreenshotsPlatform) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetManifestParams();
params.fetch_screenshots = true;
size_t num_of_screenshots = 0;
#if BUILDFLAG(IS_ANDROID)
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest(
"/banners/manifest_with_narrow_screenshots.json"));
// Screenshots with unspecified form_factor is not filtered out.
num_of_screenshots = 2;
// EXPECT_EQ(2u, tester->screenshots().size());
#else
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest("/banners/manifest_with_wide_screenshots.json"));
// Screenshots with unspecified form_factor is filtered out.
num_of_screenshots = 1;
#endif
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(num_of_screenshots, tester->screenshots().size());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckScreenshotsNumber) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetManifestParams();
params.fetch_screenshots = true;
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest(
"/banners/manifest_with_too_many_screenshots.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(8u, tester->screenshots().size());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckLargeScreenshotsFilteredOut) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetManifestParams();
params.fetch_screenshots = true;
NavigateAndRunInstallableManager(
tester.get(), params,
GetURLOfPageWithManifest("/banners/manifest_large_screenshot.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->installable_check_passed());
for (const auto& screenshot : tester->screenshots()) {
EXPECT_LE(screenshot.image.width(), 3840);
EXPECT_LE(screenshot.image.height(), 3840);
}
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
ManifestLinkChangeReportsError) {
InstallableManager* manager = GetManager();
scoped_refptr<base::TestSimpleTaskRunner> test_task_runner =
base::MakeRefCounted<base::TestSimpleTaskRunner>();
manager->SetSequencedTaskRunnerForTesting(test_task_runner);
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure(), test_task_runner));
NavigateAndRunInstallableManager(tester.get(), GetManifestParams(),
"/banners/manifest_test_page.html");
// Simulate a manifest URL update by just calling the observer function.
static_cast<content::WebContentsObserver*>(manager)->DidUpdateWebManifestURL(
web_contents()->GetPrimaryMainFrame(), GURL());
// This will run all tasks currently pending on the task runner. This includes
// any changes that could have been caused by calling DidUpdateWebManifestURL,
// which should synchronously modify the data to be passed to the tester
// callback.
test_task_runner->RunPendingTasks();
run_loop.Run();
ASSERT_EQ(tester->errors().size(), 1u);
EXPECT_EQ(tester->errors()[0], InstallableStatusCode::MANIFEST_URL_CHANGED);
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckManifestOnly_DisplayOverride) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetManifestParams(),
GetURLOfPageWithManifest("/banners/manifest_display_override.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
ASSERT_EQ(2u, tester->manifest().display_override.size());
EXPECT_EQ(blink::mojom::DisplayMode::kMinimalUi,
tester->manifest().display_override[0]);
EXPECT_EQ(blink::mojom::DisplayMode::kStandalone,
tester->manifest().display_override[1]);
EXPECT_TRUE(tester->primary_icon_url().is_empty());
EXPECT_EQ(nullptr, tester->primary_icon());
EXPECT_FALSE(tester->has_maskable_primary_icon());
EXPECT_FALSE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
ManifestDisplayOverrideReportsError_DisplayOverride) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetWebAppParams(),
GetURLOfPageWithManifest(
"/banners/manifest_display_override_contains_browser.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
ASSERT_EQ(3u, tester->manifest().display_override.size());
EXPECT_EQ(blink::mojom::DisplayMode::kBrowser,
tester->manifest().display_override[0]);
EXPECT_EQ(blink::mojom::DisplayMode::kMinimalUi,
tester->manifest().display_override[1]);
EXPECT_EQ(blink::mojom::DisplayMode::kStandalone,
tester->manifest().display_override[2]);
EXPECT_EQ(
std::vector<InstallableStatusCode>{
InstallableStatusCode::MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED},
tester->errors());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
FallbackToDisplayBrowser_DisplayOverride) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetWebAppParams(),
GetURLOfPageWithManifest(
"/banners/manifest_display_override_display_is_browser.json"));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_FALSE(tester->manifest_url().is_empty());
ASSERT_EQ(1u, tester->manifest().display_override.size());
EXPECT_EQ(blink::mojom::DisplayMode::kStandalone,
tester->manifest().display_override[0]);
EXPECT_FALSE(tester->primary_icon_url().is_empty());
EXPECT_NE(nullptr, tester->primary_icon());
EXPECT_GT(tester->primary_icon()->width(), 0);
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}
class InstallableManagerInPrerenderingBrowserTest
: public InstallableManagerBrowserTest {
public:
InstallableManagerInPrerenderingBrowserTest()
: prerender_helper_(base::BindRepeating(
&InstallableManagerInPrerenderingBrowserTest::web_contents,
base::Unretained(this))) {}
~InstallableManagerInPrerenderingBrowserTest() override = default;
content::test::PrerenderTestHelper* prerender_helper() {
return &prerender_helper_;
}
private:
content::test::PrerenderTestHelper prerender_helper_;
};
IN_PROC_BROWSER_TEST_F(InstallableManagerInPrerenderingBrowserTest,
InstallableManagerInPrerendering) {
auto manager = std::make_unique<ResetDataInstallableManager>(web_contents());
GURL url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
manager->ClearOnResetData();
// Loads a page in the prerendering.
const std::string path = "/banners/manifest_test_page.html";
auto prerender_url = embedded_test_server()->GetURL(path);
content::FrameTreeNodeId host_id =
prerender_helper()->AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
// The prerendering should not affect the current data.
EXPECT_FALSE(manager->GetOnResetData());
{
// Fetches the data.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
// It should have the default manifest for `url` since no data since
// manifest_test_page.html is loaded in the prerendering.
EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), url));
EXPECT_EQ(
std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
tester->errors());
}
{
// If the page is activated from the prerendering and the data should be
// reset.
base::RunLoop run_loop;
manager->SetQuitClosure(run_loop.QuitClosure());
NavigateToPath(path);
run_loop.Run();
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
}
{
// Fetch the data again. This should succeed.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
EXPECT_EQ(u"Manifest test app", tester->manifest().name);
EXPECT_EQ(std::u16string(),
tester->manifest().short_name.value_or(std::u16string()));
}
}
class MockInstallableManager : public InstallableManager {
public:
explicit MockInstallableManager(content::WebContents* web_contents)
: InstallableManager(web_contents) {}
~MockInstallableManager() override = default;
MOCK_METHOD(void, OnResetData, (), (override));
MOCK_METHOD(void,
DidUpdateWebManifestURL,
(content::RenderFrameHost * rfh, const GURL& manifest_url),
(override));
};
MATCHER_P(IsManifestURL, file_name, std::string()) {
return arg.ExtractFileName() == file_name;
}
MATCHER_P(IsPrerenderedRFH, render_frame_host, std::string()) {
return arg->GetGlobalId() == render_frame_host->GetGlobalId();
}
// TODO(crbug.com/40275175): Test failed on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_NotifyManifestUrlChangedInActivation \
DISABLED_NotifyManifestUrlChangedInActivation
#else
#define MAYBE_NotifyManifestUrlChangedInActivation \
NotifyManifestUrlChangedInActivation
#endif
// Tests that NotifyManifestUrlChanged is called on the page that has manifest
// after the activation from the prerendering.
IN_PROC_BROWSER_TEST_F(InstallableManagerInPrerenderingBrowserTest,
MAYBE_NotifyManifestUrlChangedInActivation) {
auto manager = std::make_unique<MockInstallableManager>(web_contents());
GURL url = embedded_test_server()->GetURL("/empty.html");
// OnResetData() is called when a navigation is finished.
EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Loads a page in the prerendering.
auto prerender_url =
embedded_test_server()->GetURL("/banners/manifest_test_page.html");
// OnResetData() should not be called on the prerendering.
EXPECT_CALL(*manager.get(), OnResetData()).Times(0);
content::FrameTreeNodeId host_id =
prerender_helper()->AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
content::RenderFrameHost* render_frame_host =
prerender_helper()->GetPrerenderedMainFrameHost(host_id);
{
// Fetches the data.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
// It should have default data for the original `url` since
// manifest_test_page.html is loaded in the prerendering.
EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), url));
EXPECT_EQ(
std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
tester->errors());
}
{
// If the page is activated from the prerendering and the data should be
// reset and notify the updated manifest url.
EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
EXPECT_CALL(*manager.get(),
DidUpdateWebManifestURL(IsPrerenderedRFH(render_frame_host),
IsManifestURL("manifest.json")));
prerender_helper()->NavigatePrimaryPage(prerender_url);
}
EXPECT_TRUE(host_observer.was_activated());
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
{
// Fetch the data again. This should succeed.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
EXPECT_EQ(u"Manifest test app", tester->manifest().name);
EXPECT_EQ(std::u16string(),
tester->manifest().short_name.value_or(std::u16string()));
}
}
// TODO(crbug.com/40275175): Test failed on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_NotNotifyManifestUrlChangedInActivation \
DISABLED_NotNotifyManifestUrlChangedInActivation
#else
#define MAYBE_NotNotifyManifestUrlChangedInActivation \
NotNotifyManifestUrlChangedInActivation
#endif
// Tests that NotifyManifestUrlChanged is not called without manifest after
// the activation from the prerendering.
IN_PROC_BROWSER_TEST_F(InstallableManagerInPrerenderingBrowserTest,
MAYBE_NotNotifyManifestUrlChangedInActivation) {
auto manager = std::make_unique<MockInstallableManager>(web_contents());
GURL url = embedded_test_server()->GetURL("/empty.html");
// OnResetData() is called when a navigation is finished.
EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Loads a page in the prerendering.
auto prerender_url =
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
// OnResetData() should not be called on the prerendering.
EXPECT_CALL(*manager.get(), OnResetData()).Times(0);
content::FrameTreeNodeId host_id =
prerender_helper()->AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
{
// Fetches the data.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
// It should have default data for the original `url` since
// manifest_test_page.html is loaded in the prerendering.
EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), url));
EXPECT_EQ(
std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
tester->errors());
}
// OnResetData() is called when a navigation is finished.
EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
// OnResetData() should not be called when a page doesn't have a manifest.
EXPECT_CALL(*manager.get(), DidUpdateWebManifestURL(testing::_, testing::_))
.Times(0);
prerender_helper()->NavigatePrimaryPage(prerender_url);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
{
// Fetch the data again. This should return the default manifest for the
// prerendered page now.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
manager->GetData(
GetWebAppParams(),
base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester.get())));
run_loop.Run();
EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), prerender_url));
EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
manager->manifest_error());
EXPECT_THAT(tester->errors(),
testing::ElementsAre(InstallableStatusCode::NO_MANIFEST));
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, NoManifestRootScopeTest) {
InstallableParams params;
params.fetch_metadata = true;
params.installable_criteria = InstallableCriteria::kNoManifestAtRootScope;
std::map<std::string, std::string> meta_tags = {
{"application-name", "Test App Name"},
{"icon", "/banners/256x256-red.png"}};
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
std::string path =
GetUrlOfPageWithTags("/no_manifest_test_page.html", meta_tags);
NavigateAndRunInstallableManager(tester.get(), params, path);
run_loop.Run();
EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
embedded_test_server()->GetURL(path)));
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
EXPECT_TRUE(tester->manifest().icons.empty());
EXPECT_FALSE(web_contents()->GetFaviconURLs().empty());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_THAT(tester->errors(), testing::IsEmpty());
}
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
std::string path =
GetUrlOfPageWithTags("/no_manifest_test_page.html", meta_tags);
NavigateAndRunInstallableManager(tester.get(), params, path);
run_loop.Run();
EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
embedded_test_server()->GetURL(path)));
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
EXPECT_TRUE(tester->manifest().icons.empty());
EXPECT_FALSE(web_contents()->GetFaviconURLs().empty());
EXPECT_TRUE(tester->installable_check_passed());
EXPECT_THAT(tester->errors(), testing::IsEmpty());
}
}
} // namespace webapps