blob: 398a34cbbbcfd0a346915457f1412dc59550b9d5 [file] [log] [blame]
// Copyright 2019 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/web_applications/web_app_url_loader.h"
#include "base/barrier_closure.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/test_mock_time_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace web_app {
using Result = WebAppUrlLoader::Result;
using UrlComparison = WebAppUrlLoader::UrlComparison;
// Returns a redirect response to |dest| URL.
std::unique_ptr<net::test_server::HttpResponse> HandleServerRedirect(
const std::string& dest,
const net::test_server::HttpRequest& request) {
GURL request_url = request.GetURL();
LOG(INFO) << "Redirecting from " << request_url << " to " << dest;
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_FOUND);
http_response->AddCustomHeader("Location", dest);
http_response->set_content_type("text/html");
http_response->set_content(base::StringPrintf(
"<html><head></head><body>Redirecting to %s</body></html>",
dest.c_str()));
return http_response;
}
// Run |handler| on requests that exactly match the |relative_url|.
std::unique_ptr<net::test_server::HttpResponse>
HandleMatchingRequestOrReturnEmptyPage(
const std::string& relative_url,
const net::EmbeddedTestServer::HandleRequestCallback& handler,
const net::test_server::HttpRequest& request) {
GURL match = request.GetURL().Resolve(relative_url);
if (request.GetURL() == match)
return handler.Run(request);
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
return http_response;
}
class WebAppUrlLoaderTest : public InProcessBrowserTest {
public:
WebAppUrlLoaderTest() = default;
WebAppUrlLoaderTest(const WebAppUrlLoaderTest&) = delete;
WebAppUrlLoaderTest& operator=(const WebAppUrlLoaderTest&) = delete;
~WebAppUrlLoaderTest() override = default;
void SetUpOnMainThread() override {
web_contents_ = content::WebContents::Create(
content::WebContents::CreateParams(browser()->profile()));
host_resolver()->AddRule("*", "127.0.0.1");
}
void TearDownOnMainThread() override {
// The WebContents needs to be destroyed before the profile.
web_contents_.reset();
}
Result LoadUrlAndWait(UrlComparison url_comparison, const std::string& path) {
absl::optional<Result> result;
base::RunLoop run_loop;
WebAppUrlLoader loader;
loader.LoadUrl(embedded_test_server()->GetURL(path), web_contents(),
url_comparison, base::BindLambdaForTesting([&](Result r) {
result = r;
run_loop.Quit();
}));
EXPECT_TRUE(web_contents()->IsLoading());
run_loop.Run();
// Currently WebAppUrlLoader uses DidFinishLoad which might get called
// even if WebContents::IsLoading() is true.
// TODO(ortuno): Check that the WebContents is no longer loading after
// the callback is called.
return result.value();
}
protected:
content::WebContents* web_contents() { return web_contents_.get(); }
void ResetWebContents() { web_contents_.reset(); }
// Set up a server redirect from |src_relative| (a relative URL) to |dest|.
// Must be called before the server is started.
void SetupRedirect(const std::string& src_relative, const std::string& dest) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&HandleMatchingRequestOrReturnEmptyPage, src_relative,
base::BindRepeating(&HandleServerRedirect, dest)));
}
private:
std::unique_ptr<content::WebContents> web_contents_;
};
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, Loaded) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(Result::kUrlLoaded,
LoadUrlAndWait(UrlComparison::kExact, "/simple.html"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, LoadedWithParamChangeIgnored) {
SetupRedirect("/test-redirect", "/test-redirect?param=stuff");
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(Result::kUrlLoaded,
LoadUrlAndWait(UrlComparison::kIgnoreQueryParamsAndRef,
"/test-redirect"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest,
LoadedWithParamAndRefChangeIgnored) {
// Note we cannot test the ref change in isolation as it is not sent to the
// server, so we cannot check it in the request handler.
SetupRedirect("/test-redirect", "/test-redirect?param=foo#ref");
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(Result::kUrlLoaded,
LoadUrlAndWait(UrlComparison::kIgnoreQueryParamsAndRef,
"/test-redirect"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, LoadedWithPathChangeIgnored) {
SetupRedirect("/test-redirect", "/test-redirect-new-path");
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(Result::kUrlLoaded,
LoadUrlAndWait(UrlComparison::kSameOrigin, "/test-redirect"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, RedirectWithParamChange) {
SetupRedirect("/test-redirect", "/test-redirect?param=stuff");
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(Result::kRedirectedUrlLoaded,
LoadUrlAndWait(UrlComparison::kExact, "/test-redirect"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, RedirectWithPathChange) {
SetupRedirect("/test-redirect", "/test-redirect-new-path");
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(Result::kRedirectedUrlLoaded,
LoadUrlAndWait(UrlComparison::kIgnoreQueryParamsAndRef,
"/test-redirect"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, 302FoundRedirect) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL final_url = embedded_test_server()->GetURL("/simple.html");
EXPECT_EQ(Result::kRedirectedUrlLoaded,
LoadUrlAndWait(UrlComparison::kIgnoreQueryParamsAndRef,
"/server-redirect-302?" + final_url.spec()));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, Hung) {
ASSERT_TRUE(embedded_test_server()->Start());
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner);
WebAppUrlLoader loader;
absl::optional<Result> result;
loader.LoadUrl(embedded_test_server()->GetURL("/hung"), web_contents(),
UrlComparison::kExact,
base::BindLambdaForTesting([&](Result r) { result = r; }));
// Run all pending tasks. The URL should still be loading.
EXPECT_TRUE(web_contents()->IsLoading());
task_runner->RunUntilIdle();
EXPECT_TRUE(web_contents()->IsLoading());
// The callback didn't run yet because the site is still loading.
EXPECT_FALSE(result.has_value());
// Forward the clock so that |loader| times out.
task_runner->FastForwardBy(WebAppUrlLoader::kSecondsToWaitForWebContentsLoad);
EXPECT_FALSE(web_contents()->IsLoading());
EXPECT_EQ(Result::kFailedPageTookTooLong, result.value());
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, WebContentsDestroyed) {
ASSERT_TRUE(embedded_test_server()->Start());
WebAppUrlLoader loader;
absl::optional<Result> result;
base::RunLoop run_loop;
loader.LoadUrl(embedded_test_server()->GetURL("/hung"), web_contents(),
UrlComparison::kExact,
base::BindLambdaForTesting([&](Result r) {
result = r;
run_loop.Quit();
}));
// Run all pending tasks. The URL should still be loading.
EXPECT_TRUE(web_contents()->IsLoading());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(web_contents()->IsLoading());
// The callback didn't run yet because the site is still loading.
EXPECT_FALSE(result.has_value());
ResetWebContents();
run_loop.Run();
EXPECT_EQ(Result::kFailedWebContentsDestroyed, result.value());
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, MultipleLoadUrlCalls) {
ASSERT_TRUE(embedded_test_server()->Start());
WebAppUrlLoader loader;
absl::optional<Result> title1_result;
absl::optional<Result> title2_result;
std::unique_ptr<content::WebContents> web_contents1 =
content::WebContents::Create(
content::WebContents::CreateParams(browser()->profile()));
std::unique_ptr<content::WebContents> web_contents2 =
content::WebContents::Create(
content::WebContents::CreateParams(browser()->profile()));
base::RunLoop run_loop;
base::RepeatingClosure barrier_closure =
base::BarrierClosure(2, run_loop.QuitClosure());
loader.LoadUrl(embedded_test_server()->GetURL("/title1.html"),
web_contents1.get(), UrlComparison::kExact,
base::BindLambdaForTesting([&](Result r) {
title1_result = r;
barrier_closure.Run();
}));
loader.LoadUrl(embedded_test_server()->GetURL("/title2.html"),
web_contents2.get(), UrlComparison::kExact,
base::BindLambdaForTesting([&](Result r) {
title2_result = r;
barrier_closure.Run();
}));
run_loop.Run();
EXPECT_EQ(Result::kUrlLoaded, title1_result.value());
EXPECT_EQ(Result::kUrlLoaded, title2_result.value());
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, NetworkError) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(
Result::kFailedErrorPageLoaded,
LoadUrlAndWait(UrlComparison::kIgnoreQueryParamsAndRef, "/close-socket"));
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest,
PrepareForLoad_AfterNavigationComplete) {
ASSERT_TRUE(embedded_test_server()->Start());
WebAppUrlLoader loader;
// Load a URL, and wait for its completion.
LoadUrlAndWait(UrlComparison::kExact, "/title1.html");
// Prepare for next load.
base::RunLoop run_loop;
loader.PrepareForLoad(web_contents(),
base::BindLambdaForTesting([&](Result result) {
EXPECT_EQ(Result::kUrlLoaded, result);
run_loop.Quit();
}));
run_loop.Run();
// Load the next URL.
LoadUrlAndWait(UrlComparison::kExact, "/title2.html");
}
namespace {
class WebContentsLoadingObserver : public content::WebContentsObserver {
public:
explicit WebContentsLoadingObserver(content::WebContents* contents)
: WebContentsObserver(contents) {}
WebContentsLoadingObserver(const WebContentsLoadingObserver&) = delete;
WebContentsLoadingObserver& operator=(const WebContentsLoadingObserver&) =
delete;
~WebContentsLoadingObserver() override = default;
void Wait() { run_loop_.Run(); }
// content::WebContentsObserver:
void DidStartLoading() override { run_loop_.Quit(); }
private:
base::RunLoop run_loop_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest,
PrepareForLoad_BeforeNavigationComplete) {
ASSERT_TRUE(embedded_test_server()->Start());
WebAppUrlLoader loader;
// Load a URL that takes a long time to load. Use /hung-after-headers here
// because it starts the HTTP response, but never returns a HTML document.
// We intentionally don't wait for load completion.
{
WebContentsLoadingObserver observer(web_contents());
loader.LoadUrl(embedded_test_server()->GetURL("/hung-after-headers"),
web_contents(), UrlComparison::kExact, base::DoNothing());
observer.Wait();
}
// Prepare for next load.
{
EXPECT_TRUE(web_contents()->IsLoading());
base::RunLoop run_loop;
loader.PrepareForLoad(web_contents(),
base::BindLambdaForTesting([&](Result result) {
EXPECT_EQ(Result::kUrlLoaded, result);
run_loop.Quit();
}));
run_loop.Run();
}
// Load the next URL.
LoadUrlAndWait(UrlComparison::kExact, "/title2.html");
}
IN_PROC_BROWSER_TEST_F(WebAppUrlLoaderTest, PrepareForLoad_RecordResultMetric) {
base::HistogramTester histograms;
static constexpr char kPrepareForLoadResultHistogramName[] =
"Webapp.WebAppUrlLoaderPrepareForLoadResult";
ASSERT_TRUE(embedded_test_server()->Start());
WebAppUrlLoader loader;
// Load a URL, and wait for its completion.
LoadUrlAndWait(UrlComparison::kExact, "/title1.html");
histograms.ExpectTotalCount(kPrepareForLoadResultHistogramName, 0);
// Prepare for next load.
{
base::RunLoop run_loop;
loader.PrepareForLoad(web_contents(),
base::BindLambdaForTesting([&](Result result) {
EXPECT_EQ(Result::kUrlLoaded, result);
run_loop.Quit();
}));
run_loop.Run();
}
histograms.ExpectTotalCount(kPrepareForLoadResultHistogramName, 1);
histograms.ExpectBucketCount(kPrepareForLoadResultHistogramName,
Result::kUrlLoaded, 1);
// Load the next URL.
LoadUrlAndWait(UrlComparison::kExact, "/title2.html");
histograms.ExpectTotalCount(kPrepareForLoadResultHistogramName, 1);
// Prepare the next load again.
{
base::RunLoop run_loop;
loader.PrepareForLoad(web_contents(),
base::BindLambdaForTesting([&](Result result) {
EXPECT_EQ(Result::kUrlLoaded, result);
run_loop.Quit();
}));
run_loop.Run();
}
histograms.ExpectTotalCount(kPrepareForLoadResultHistogramName, 2);
histograms.ExpectBucketCount(kPrepareForLoadResultHistogramName,
Result::kUrlLoaded, 2);
}
} // namespace web_app