blob: 7c0b6416fb0e3942ae4729f82eea32a118ee6e86 [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 "chrome/browser/installable/installable_manager.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
namespace {
InstallableParams GetManifestParams() {
InstallableParams params;
params.check_installable = false;
params.fetch_valid_icon = false;
return params;
}
InstallableParams GetWebAppParams() {
InstallableParams params = GetManifestParams();
params.ideal_icon_size_in_dp = 48;
params.minimum_icon_size_in_dp = 48;
params.check_installable = true;
params.fetch_valid_icon = true;
return params;
}
InstallableParams GetIconParams() {
InstallableParams params = GetManifestParams();
params.ideal_icon_size_in_dp = 48;
params.minimum_icon_size_in_dp = 48;
params.fetch_valid_icon = true;
return params;
}
} // anonymous namespace
class CallbackTester {
public:
explicit CallbackTester(base::Closure quit_closure)
: quit_closure_(quit_closure) { }
void OnDidFinishInstallableCheck(const InstallableData& data) {
error_code_ = data.error_code;
manifest_url_ = data.manifest_url;
manifest_ = data.manifest;
icon_url_ = data.icon_url;
if (data.icon)
icon_.reset(new SkBitmap(*data.icon));
is_installable_ = data.is_installable;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure_);
}
InstallableStatusCode error_code() const { return error_code_; }
const GURL& manifest_url() const { return manifest_url_; }
const content::Manifest& manifest() const { return manifest_; }
const GURL& icon_url() const { return icon_url_; }
const SkBitmap* icon() const { return icon_.get(); }
bool is_installable() const { return is_installable_; }
private:
base::Closure quit_closure_;
InstallableStatusCode error_code_;
GURL manifest_url_;
content::Manifest manifest_;
GURL icon_url_;
std::unique_ptr<SkBitmap> icon_;
bool is_installable_;
};
class NestedCallbackTester {
public:
NestedCallbackTester(InstallableManager* manager,
const InstallableParams& params,
base::Closure quit_closure)
: manager_(manager), params_(params), quit_closure_(quit_closure) {}
void Run() {
manager_->GetData(params_,
base::Bind(&NestedCallbackTester::OnDidFinishFirstCheck,
base::Unretained(this)));
}
void OnDidFinishFirstCheck(const InstallableData& data) {
error_code_ = data.error_code;
manifest_url_ = data.manifest_url;
manifest_ = data.manifest;
icon_url_ = data.icon_url;
if (data.icon)
icon_.reset(new SkBitmap(*data.icon));
is_installable_ = data.is_installable;
manager_->GetData(params_,
base::Bind(&NestedCallbackTester::OnDidFinishSecondCheck,
base::Unretained(this)));
}
void OnDidFinishSecondCheck(const InstallableData& data) {
EXPECT_EQ(error_code_, data.error_code);
EXPECT_EQ(manifest_url_, data.manifest_url);
EXPECT_EQ(icon_url_, data.icon_url);
EXPECT_EQ(icon_.get(), data.icon);
EXPECT_EQ(is_installable_, data.is_installable);
EXPECT_EQ(manifest_.IsEmpty(), data.manifest.IsEmpty());
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);
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure_);
}
private:
InstallableManager* manager_;
InstallableParams params_;
base::Closure quit_closure_;
InstallableStatusCode error_code_;
GURL manifest_url_;
content::Manifest manifest_;
GURL icon_url_;
std::unique_ptr<SkBitmap> icon_;
bool is_installable_;
};
class InstallableManagerBrowserTest : public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
}
// Returns a test server URL to a page controlled by a service worker with
// |manifest_url| injected as the manifest tag.
std::string GetURLOfPageWithServiceWorkerAndManifest(
const std::string& manifest_url) {
return "/banners/manifest_test_page.html?manifest=" +
embedded_test_server()->GetURL(manifest_url).spec();
}
void NavigateAndRunInstallableManager(CallbackTester* tester,
const InstallableParams& params,
const std::string& url) {
GURL test_url = embedded_test_server()->GetURL(url);
ui_test_utils::NavigateToURL(browser(), test_url);
RunInstallableManager(tester, params);
}
void RunInstallableManager(CallbackTester* tester,
const InstallableParams& params) {
InstallableManager* manager = GetManager();
manager->GetData(params,
base::Bind(&CallbackTester::OnDidFinishInstallableCheck,
base::Unretained(tester)));
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Make sure app banners are disabled in the browser so they do not
// interfere with the test.
command_line->AppendSwitch(switches::kDisableAddToShelf);
}
InstallableManager* GetManager() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
InstallableManager::CreateForWebContents(web_contents);
InstallableManager* manager =
InstallableManager::FromWebContents(web_contents);
CHECK(manager);
return manager;
}
};
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
ManagerBeginsInEmptyState) {
// Ensure that the InstallableManager starts off with everything null.
InstallableManager* manager = GetManager();
EXPECT_TRUE(manager->manifest().IsEmpty());
EXPECT_TRUE(manager->manifest_url().is_empty());
EXPECT_TRUE(manager->icons_.empty());
EXPECT_FALSE(manager->is_installable());
EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
EXPECT_EQ(NO_ERROR_DETECTED, manager->installable_error());
EXPECT_TRUE(manager->tasks_.empty());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckNoManifest) {
// Ensure that a page with no manifest returns the appropriate error and with
// null fields for everything.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetManifestParams(),
"/banners/no_manifest_test_page.html");
run_loop.Run();
// If there is no manifest, everything should be empty.
EXPECT_TRUE(tester->manifest().IsEmpty());
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_MANIFEST, tester->error_code());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifest404) {
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetManifestParams(),
GetURLOfPageWithServiceWorkerAndManifest(
"/banners/manifest_missing.json"));
run_loop.Run();
// The installable manager should return a manifest URL even if it 404s.
// However, the check should fail with a ManifestEmpty error.
EXPECT_TRUE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(MANIFEST_EMPTY, tester->error_code());
}
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(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
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(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
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(),
"/banners/play_app_test_page.html");
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
// Ask for an 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(), GetIconParams());
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
}
// Ask for everything. This should fail with NO_ACCEPTABLE_ICON - the 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(), GetWebAppParams());
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
}
// Ask for a different size icon. This should fail with START_URL_NOT_VALID
// since we won't have a cached icon error.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
InstallableParams params = GetWebAppParams();
params.ideal_icon_size_in_dp = 32;
params.minimum_icon_size_in_dp = 32;
RunInstallableManager(tester.get(), params);
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->manifest().prefer_related_applications);
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(START_URL_NOT_VALID, tester->error_code());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifestAndIcon) {
// Add to homescreen checks for manifest + icon.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetIconParams(),
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->icon_url().is_empty());
EXPECT_NE(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckWebapp) {
// Request everything.
{
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().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->is_installable());
EXPECT_FALSE(tester->icon_url().is_empty());
EXPECT_NE(nullptr, tester->icon());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
// Verify that the returned state matches manager internal state.
InstallableManager* manager = GetManager();
EXPECT_FALSE(manager->manifest().IsEmpty());
EXPECT_FALSE(manager->manifest_url().is_empty());
EXPECT_TRUE(manager->is_installable());
EXPECT_EQ(1u, manager->icons_.size());
EXPECT_FALSE((manager->icon_url({48,48}).is_empty()));
EXPECT_NE(nullptr, (manager->icon({48,48})));
EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
EXPECT_EQ(NO_ERROR_DETECTED, manager->installable_error());
EXPECT_EQ(NO_ERROR_DETECTED, (manager->icon_error({48,48})));
EXPECT_TRUE(manager->tasks_.empty());
}
// Request everything again without navigating away. This should work fine.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
RunInstallableManager(tester.get(), GetWebAppParams());
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->is_installable());
EXPECT_FALSE(tester->icon_url().is_empty());
EXPECT_NE(nullptr, tester->icon());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
// Verify that the returned state matches manager internal state.
InstallableManager* manager = GetManager();
EXPECT_FALSE(manager->manifest().IsEmpty());
EXPECT_FALSE(manager->manifest_url().is_empty());
EXPECT_TRUE(manager->is_installable());
EXPECT_EQ(1u, manager->icons_.size());
EXPECT_FALSE((manager->icon_url({48,48}).is_empty()));
EXPECT_NE(nullptr, (manager->icon({48,48})));
EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
EXPECT_EQ(NO_ERROR_DETECTED, manager->installable_error());
EXPECT_EQ(NO_ERROR_DETECTED, (manager->icon_error({48,48})));
EXPECT_TRUE(manager->tasks_.empty());
}
{
// Check that a subsequent navigation resets state.
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
InstallableManager* manager = GetManager();
EXPECT_TRUE(manager->manifest().IsEmpty());
EXPECT_TRUE(manager->manifest_url().is_empty());
EXPECT_FALSE(manager->is_installable());
EXPECT_TRUE(manager->icons_.empty());
EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
EXPECT_EQ(NO_ERROR_DETECTED, manager->installable_error());
EXPECT_TRUE(manager->tasks_.empty());
}
}
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;
// everything should be empty here.
EXPECT_TRUE(tester->manifest().IsEmpty());
EXPECT_TRUE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_MANIFEST, tester->error_code());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckPageWithManifestAndNoServiceWorker) {
// Just fetch the manifest. This should have no error.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(
tester.get(), GetManifestParams(),
"/banners/manifest_no_service_worker.html");
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
// Fetch the full criteria should fail.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
RunInstallableManager(tester.get(), GetWebAppParams());
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_MATCHING_SERVICE_WORKER, tester->error_code());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckManifestCorruptedIcon) {
// Verify that the returned InstallableData::icon is null if the web manifest
// points to a corrupt icon.
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
NavigateAndRunInstallableManager(tester.get(), GetIconParams(),
GetURLOfPageWithServiceWorkerAndManifest(
"/banners/manifest_bad_icon.json"));
run_loop.Run();
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_FALSE(tester->is_installable());
EXPECT_EQ(NO_ICON_AVAILABLE, tester->error_code());
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
CheckChangeInIconDimensions) {
// Verify that a follow-up request for an 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(tester->manifest().IsEmpty());
EXPECT_TRUE(tester->is_installable());
EXPECT_FALSE(tester->icon_url().is_empty());
EXPECT_NE(nullptr, tester->icon());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// Dial up the icon size requirements to something that isn't available.
// This should now fail with NoIconMatchingRequirements.
InstallableParams params = GetWebAppParams();
params.ideal_icon_size_in_dp = 2000;
params.minimum_icon_size_in_dp = 2000;
RunInstallableManager(tester.get(), params);
run_loop.Run();
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_TRUE(tester->is_installable());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
}
// Navigate and verify the reverse: an overly large icon requested first
// fails, but a smaller icon requested second passes.
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
// This should fail with NoIconMatchingRequirements.
InstallableParams params = GetWebAppParams();
params.ideal_icon_size_in_dp = 2000;
params.minimum_icon_size_in_dp = 2000;
NavigateAndRunInstallableManager(tester.get(), params,
"/banners/manifest_test_page.html");
run_loop.Run();
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_TRUE(tester->is_installable());
EXPECT_TRUE(tester->icon_url().is_empty());
EXPECT_EQ(nullptr, tester->icon());
EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
}
{
base::RunLoop run_loop;
std::unique_ptr<CallbackTester> tester(
new CallbackTester(run_loop.QuitClosure()));
RunInstallableManager(tester.get(), GetWebAppParams());
run_loop.Run();
// The smaller icon requirements should allow this to pass.
EXPECT_FALSE(tester->manifest_url().is_empty());
EXPECT_FALSE(tester->manifest().IsEmpty());
EXPECT_TRUE(tester->is_installable());
EXPECT_FALSE(tester->icon_url().is_empty());
EXPECT_NE(nullptr, tester->icon());
EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
}
}
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
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();
}