blob: c0d2b9f49177884b98b6d892b532f3b1f7fd3f28 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "weblayer/browser/webapps/webapk_install_scheduler.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "components/webapps/browser/android/shortcut_info.h"
#include "components/webapps/browser/android/webapk/webapk_types.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "weblayer/browser/tab_impl.h"
#include "weblayer/shell/browser/shell.h"
#include "weblayer/test/weblayer_browser_test.h"
// Keep these tests in sync with tests for building the WebAPK-proto in
// chrome/browser/android/webapk/webapk_installer_unittest.cc.
namespace {
const base::FilePath::CharType kTestDataDir[] =
FILE_PATH_LITERAL("components/test/data/webapps");
// Start URL for the WebAPK
const char* kStartUrl = "/index.html";
// The URLs of best icons from Web Manifest. We use a random file in the test
// data directory. Since WebApkInstallScheduler does not try to decode the file
// as an image it is OK that the file is not an image.
const char* kBestPrimaryIconUrl = "/simple.html";
const char* kBestSplashIconUrl = "/nostore.html";
const char* kBestShortcutIconUrl = "/title1.html";
// Icon which has Cross-Origin-Resource-Policy: same-origin set.
const char* kBestPrimaryIconCorpUrl = "/cors_same_origin.png";
} // namespace
namespace weblayer {
class TestWebApkInstallScheduler : public WebApkInstallScheduler {
public:
TestWebApkInstallScheduler(const webapps::ShortcutInfo& shortcut_info,
const SkBitmap& primary_icon,
bool is_primary_icon_maskable,
WebApkInstallFinishedCallback callback)
: WebApkInstallScheduler(shortcut_info,
primary_icon,
is_primary_icon_maskable,
std::move(callback)) {}
TestWebApkInstallScheduler(const TestWebApkInstallScheduler&) = delete;
TestWebApkInstallScheduler& operator=(const TestWebApkInstallScheduler&) =
delete;
void ScheduleWithChrome(
std::unique_ptr<std::string> serialized_proto) override {
PostTaskToRunSuccessCallback();
}
// Function used for testing FetchProtoAndScheduleInstall. |callback| can
// be set to forward the result-value in the OnResult-callback to a test for
// verification.
void FetchProtoAndScheduleInstallForTesting(
content::WebContents* web_contents,
WebApkInstallScheduler::FinishCallback callback) {
callback_ = std::move(callback);
WebApkInstallScheduler::FetchProtoAndScheduleInstallForTesting(
web_contents);
}
void OnResult(webapps::WebApkInstallResult result) override {
// Pass the |result| to the callback for verification.
std::move(callback_).Run(result);
WebApkInstallScheduler::OnResult(result);
}
void PostTaskToRunSuccessCallback() {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&TestWebApkInstallScheduler::OnResult,
base::Unretained(this),
webapps::WebApkInstallResult::SUCCESS));
}
private:
WebApkInstallScheduler::FinishCallback callback_;
};
// Wrapper class for running WebApkInstallScheduler#FetchProtoAndScheduleInstall
// that makes the WebApkInstallResult that is received in the OnResult-callback
// accessible for testing.
class WebApkInstallSchedulerRunner {
public:
WebApkInstallSchedulerRunner() {}
WebApkInstallSchedulerRunner(const WebApkInstallSchedulerRunner&) = delete;
WebApkInstallSchedulerRunner& operator=(const WebApkInstallSchedulerRunner&) =
delete;
~WebApkInstallSchedulerRunner() {}
void RunFetchProtoAndScheduleInstall(
std::unique_ptr<TestWebApkInstallScheduler> fetcher,
content::WebContents* web_contents) {
base::RunLoop run_loop;
on_completed_callback_ = run_loop.QuitClosure();
// WebApkInstallScheduler owns itself.
fetcher.release()->FetchProtoAndScheduleInstallForTesting(
web_contents, base::BindOnce(&WebApkInstallSchedulerRunner::OnCompleted,
base::Unretained(this)));
run_loop.Run();
}
webapps::WebApkInstallResult result() { return result_; }
private:
void OnCompleted(webapps::WebApkInstallResult result) {
result_ = result;
std::move(on_completed_callback_).Run();
}
// Called after the installation process has succeeded or failed.
base::OnceClosure on_completed_callback_;
// The result of the installation process.
webapps::WebApkInstallResult result_;
};
class WebApkInstallSchedulerTest : public WebLayerBrowserTest {
public:
WebApkInstallSchedulerTest() = default;
~WebApkInstallSchedulerTest() override = default;
WebApkInstallSchedulerTest(const WebApkInstallSchedulerTest&) = delete;
WebApkInstallSchedulerTest& operator=(const WebApkInstallSchedulerTest&) =
delete;
void SetUpOnMainThread() override {
WebLayerBrowserTest::SetUpOnMainThread();
web_contents_ = static_cast<TabImpl*>(shell()->tab())->web_contents();
test_server_.AddDefaultHandlers(base::FilePath(kTestDataDir));
ASSERT_TRUE(test_server_.Start());
}
content::WebContents* web_contents() { return web_contents_; }
net::test_server::EmbeddedTestServer* test_server() { return &test_server_; }
std::unique_ptr<TestWebApkInstallScheduler> DefaultWebApkInstallScheduler(
webapps::ShortcutInfo info) {
std::unique_ptr<TestWebApkInstallScheduler> scheduler_bridge(
new TestWebApkInstallScheduler(
info, SkBitmap(), false,
base::BindOnce(&WebApkInstallSchedulerTest::OnInstallFinished,
base::Unretained(this))));
return scheduler_bridge;
}
webapps::ShortcutInfo DefaultShortcutInfo() {
webapps::ShortcutInfo info(test_server_.GetURL(kStartUrl));
info.best_primary_icon_url = test_server_.GetURL(kBestPrimaryIconUrl);
info.splash_image_url = test_server_.GetURL(kBestSplashIconUrl);
info.best_shortcut_icon_urls.push_back(
test_server_.GetURL(kBestShortcutIconUrl));
return info;
}
private:
raw_ptr<content::WebContents> web_contents_;
net::EmbeddedTestServer test_server_;
void OnInstallFinished(GURL manifest_id) {}
};
// Test building the WebAPK-proto is succeeding.
IN_PROC_BROWSER_TEST_F(WebApkInstallSchedulerTest, Success) {
WebApkInstallSchedulerRunner runner;
runner.RunFetchProtoAndScheduleInstall(
DefaultWebApkInstallScheduler(DefaultShortcutInfo()), web_contents());
EXPECT_EQ(webapps::WebApkInstallResult::SUCCESS, runner.result());
}
// Test that building the WebAPK-proto succeeds when the primary icon is guarded
// by a Cross-Origin-Resource-Policy: same-origin header and the icon is
// same-origin with the start URL.
IN_PROC_BROWSER_TEST_F(WebApkInstallSchedulerTest,
CrossOriginResourcePolicySameOriginIconSuccess) {
webapps::ShortcutInfo shortcut_info = DefaultShortcutInfo();
shortcut_info.best_primary_icon_url =
test_server()->GetURL(kBestPrimaryIconCorpUrl);
WebApkInstallSchedulerRunner runner;
runner.RunFetchProtoAndScheduleInstall(
DefaultWebApkInstallScheduler(shortcut_info), web_contents());
EXPECT_EQ(webapps::WebApkInstallResult::SUCCESS, runner.result());
}
// Test that building the WebAPK-proto fails if fetching the bitmap at the best
// primary icon URL returns no content. In a perfect world the fetch would
// always succeed because the fetch for the same icon succeeded recently.
IN_PROC_BROWSER_TEST_F(WebApkInstallSchedulerTest,
BestPrimaryIconUrlDownloadTimesOut) {
webapps::ShortcutInfo shortcut_info = DefaultShortcutInfo();
shortcut_info.best_primary_icon_url = test_server()->GetURL("/nocontent");
WebApkInstallSchedulerRunner runner;
runner.RunFetchProtoAndScheduleInstall(
DefaultWebApkInstallScheduler(shortcut_info), web_contents());
EXPECT_EQ(webapps::WebApkInstallResult::ICON_HASHER_ERROR, runner.result());
}
// Test that building the WebAPK-proto fails if fetching the bitmap at the best
// splash icon URL returns no content. In a perfect world the fetch would always
// succeed because the fetch for the same icon succeeded recently.
IN_PROC_BROWSER_TEST_F(WebApkInstallSchedulerTest,
BestSplashIconUrlDownloadTimesOut) {
webapps::ShortcutInfo shortcut_info = DefaultShortcutInfo();
shortcut_info.best_primary_icon_url = test_server()->GetURL("/nocontent");
WebApkInstallSchedulerRunner runner;
runner.RunFetchProtoAndScheduleInstall(
DefaultWebApkInstallScheduler(shortcut_info), web_contents());
EXPECT_EQ(webapps::WebApkInstallResult::ICON_HASHER_ERROR, runner.result());
}
} // namespace weblayer