blob: e24da98a78fb3b7bfef58ec638e78e4b04189a05 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include <vector>
#include "base/strings/stringprintf.h"
#include "base/test/allow_check_is_test_for_testing.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/loader/keep_alive_request_browsertest_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/page_load_metrics/browser/features.h"
#include "components/variations/net/variations_http_headers.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/keep_alive_url_loader_utils.h"
#include "content/public/test/test_utils.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/url_util.h"
namespace {
using testing::Contains;
using testing::Key;
using testing::Not;
} // namespace
class ChromeKeepAliveURLBrowserTestBase
: public ChromeKeepAliveRequestBrowserTestBase {
public:
ChromeKeepAliveURLBrowserTestBase() {
InitFeatureList({{blink::features::kKeepAliveInBrowserMigration, {}}});
}
~ChromeKeepAliveURLBrowserTestBase() override = default;
// Not copyable.
ChromeKeepAliveURLBrowserTestBase(const ChromeKeepAliveURLBrowserTestBase&) =
delete;
ChromeKeepAliveURLBrowserTestBase& operator=(
const ChromeKeepAliveURLBrowserTestBase&) = delete;
};
// Basic Chrome browser tests to cover behaviors when handling fetch keepalive
// requests in browser process.
//
// Tests here ensure the behaviors are the same as their counterparts in
// `content/browser` even with extra logic added by Chrome embedder.
class ChromeKeepAliveURLBrowserTest
: public ChromeKeepAliveURLBrowserTestBase,
public ::testing::WithParamInterface<std::string> {
protected:
const std::string& method() const { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(
All,
ChromeKeepAliveURLBrowserTest,
::testing::Values(net::HttpRequestHeaders::kGetMethod,
net::HttpRequestHeaders::kPostMethod),
[](const testing::TestParamInfo<ChromeKeepAliveURLBrowserTest::ParamType>&
info) { return info.param; });
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLBrowserTest, OneRequest) {
const std::string target_url = kKeepAliveEndpoint;
auto request_handler = std::move(RegisterRequestHandlers({target_url})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(
web_contents(), GetKeepAlivePageURL(kPrimaryHost, target_url, method())));
// Ensure the keepalive request is sent, but delay response.
request_handler->WaitForRequest();
// End the keepalive request by sending back response.
request_handler->Send(k200TextResponse);
request_handler->Done();
content::TitleWatcher watcher(web_contents(), kPromiseResolvedPageTitle);
EXPECT_EQ(watcher.WaitAndGetTitle(), kPromiseResolvedPageTitle);
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
}
// Delays response to a keepalive ping until after the page making the keepalive
// ping has been unloaded. The browser must ensure the response is received and
// processed by the browser.
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLBrowserTest,
ReceiveResponseAfterPageUnload) {
const std::string target_url = kKeepAliveEndpoint;
auto request_handler = std::move(RegisterRequestHandlers({target_url})[0]);
ASSERT_TRUE(server()->Start());
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(kPrimaryHost, target_url, method()),
request_handler.get(), k200TextResponse);
// The response should be processed in browser.
loaders_observer().WaitForTotalOnReceiveResponseProcessed(1);
}
// Shutdown delay is not supported on Android.
#if !BUILDFLAG(IS_ANDROID)
// Mac browser shutdown is flaky: https://crbug.com/1259913
#if BUILDFLAG(IS_MAC)
#define MAYBE_ReceiveResponseAfterBrowserShutdown \
DISABLED_ReceiveResponseAfterBrowserShutdown
#else
#define MAYBE_ReceiveResponseAfterBrowserShutdown \
ReceiveResponseAfterBrowserShutdown
#endif
// Verifies that a keepalive ping can be made within a short timeframe after
// browser shutdown.
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLBrowserTest,
MAYBE_ReceiveResponseAfterBrowserShutdown) {
const std::string target_url = kKeepAliveEndpoint;
auto request_handler = std::move(RegisterRequestHandlers({target_url})[0]);
ASSERT_TRUE(server()->Start());
auto keepalive_page_url =
GetKeepAlivePageURL(kPrimaryHost, target_url, method());
ASSERT_TRUE(content::NavigateToURL(web_contents(), keepalive_page_url));
// Close the browser.
CloseBrowserSynchronously(browser());
ASSERT_TRUE(browser_shutdown::IsTryingToQuit());
ASSERT_TRUE(BrowserList::GetInstance()->empty());
ASSERT_EQ(browser_shutdown::GetShutdownType(),
browser_shutdown::ShutdownType::kWindowClose);
// The keepalive request may be sent before or after shutting down, but only
// get processed by the server after shutting down here.
request_handler->WaitForRequest();
// The disconnected loader is pending to receive response.
// Send back response to terminate in-browser request handling.
request_handler->Send(k200TextResponse);
request_handler->Done();
// The response should be processed by browser before shutting down.
// TODO(crbug.com/40236167): Deflake WaitForTotalOnReceiveResponseProcessed
}
#endif // !BUILDFLAG(IS_ANDROID)
// Delays response to a keepalive ping until after the page making the keepalive
// ping is put into BackForwardCache. The response should be processed by the
// renderer after the page is restored from BackForwardCache.
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLBrowserTest,
ReceiveResponseInBackForwardCache) {
const std::string target_url = kKeepAliveEndpoint;
auto request_handler = std::move(RegisterRequestHandlers({target_url})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(
web_contents(), GetKeepAlivePageURL(kPrimaryHost, target_url, method())));
content::RenderFrameHostWrapper rfh_1(current_frame_host());
// Ensure the keepalive request is sent before leaving the current page.
request_handler->WaitForRequest();
// Navigate to cross-origin page.
ASSERT_TRUE(NavigateToURL(web_contents(), GetCrossOriginPageURL()));
// Ensure the previous page has been put into BackForwardCache.
ASSERT_EQ(rfh_1->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
// Send back response.
request_handler->Send(k200TextResponse);
// The response is immediately forwarded to the in-BackForwardCache renderer.
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
// Go back to `rfh_1`.
ASSERT_TRUE(HistoryGoBack(web_contents()));
// The response should be processed in renderer. Hence resolving Promise.
content::TitleWatcher watcher(web_contents(), kPromiseResolvedPageTitle);
EXPECT_EQ(watcher.WaitAndGetTitle(), kPromiseResolvedPageTitle);
request_handler->Done();
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
}
// Delays handling redirect for a keepalive ping until after the page making the
// keepalive ping has been unloaded. The browser must ensure the redirect is
// verified and properly processed by the browser.
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLBrowserTest,
ReceiveRedirectAfterPageUnload) {
const std::string target_url = kKeepAliveEndpoint;
const char redirect_target[] = "/beacon-redirected";
auto request_handlers =
RegisterRequestHandlers({target_url, redirect_target});
ASSERT_TRUE(server()->Start());
// Set up redirects according to the following redirect chain:
// fetch("http://a.com:<port>/beacon", keepalive: true)
// --> http://a.com:<port>/beacon-redirected
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(kPrimaryHost, target_url, method()),
request_handlers[0].get(),
base::StringPrintf(k301ResponseTemplate, redirect_target));
// The redirect request should be processed in browser and gets sent.
request_handlers[1]->WaitForRequest();
// No variations header when redirected to non-Google.
EXPECT_THAT(request_handlers[1]->http_request()->headers,
Not(Contains(Key(variations::kClientDataHeader))));
// End the keepalive request by sending back final response.
request_handlers[1]->Send(k200TextResponse);
request_handlers[1]->Done();
// The response should be processed in browser.
loaders_observer().WaitForTotalOnReceiveResponseProcessed(1);
}
// Delays handling an unsafe redirect for a keepalive ping until after the page
// making the keepalive ping has been unloaded.
// The browser must ensure the unsafe redirect is not followed.
// TODO(crbug.com/407716208): Broken by crrev.com/c/6039011. Fix and re-enable
// this test.
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLBrowserTest,
DISABLED_ReceiveUnSafeRedirectAfterPageUnload) {
const std::string target_url = kKeepAliveEndpoint;
const char unsafe_redirect_target[] = "chrome://settings";
auto request_handler = std::move(RegisterRequestHandlers({target_url})[0]);
ASSERT_TRUE(server()->Start());
// Sets up redirects according to the following redirect chain:
// fetch("http://a.com:<port>/beacon", keepalive: true)
// --> chrome://settings
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(kPrimaryHost, target_url, method()),
request_handler.get(),
base::StringPrintf("HTTP/1.1 301 Moved Permanently\r\n"
"Location: %s\r\n"
"\r\n",
unsafe_redirect_target));
// The redirect is unsafe, so the loader is terminated.
loaders_observer().WaitForTotalOnCompleteProcessed(
{net::ERR_UNSAFE_REDIRECT});
// The test is too flaky to assert on the number of redirects processed.
}
// Checks that when a fetch keepalive request's redirect is handled in browser
// the variations header (X-Client-Data) is attached to the requests to Google.
// TODO(crbug.com/407998594): Fix test before re-enabling.
IN_PROC_BROWSER_TEST_P(
ChromeKeepAliveURLBrowserTest,
DISABLED_ReceiveMultipleRedirectsToGoogleAfterPageUnload) {
const std::string target_url = kKeepAliveEndpoint;
const std::string redirect_target1 = "/redirected1";
const std::string redirect_target2 = "/redirected2";
SetUseHttps();
auto request_handlers =
RegisterRequestHandlers({target_url, redirect_target1, redirect_target2});
ASSERT_TRUE(server()->Start());
// Set up redirects according to the following redirect chain:
// fetch("https://www.google.com:<port>/beacon", keepalive: true)
// --> https://www.google.com:<port>/redirected1
// --> https://www.google.com:<port>/redirected2
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(kGoogleHost, target_url, method()),
request_handlers[0].get(),
base::StringPrintf(k301ResponseTemplate, redirect_target1.c_str()));
request_handlers[1]->WaitForRequest();
EXPECT_THAT(request_handlers[1]->http_request()->headers,
Contains(Key(variations::kClientDataHeader)));
request_handlers[1]->Send(
base::StringPrintf(k301ResponseTemplate, redirect_target2.c_str()));
request_handlers[1]->Done();
request_handlers[2]->WaitForRequest();
EXPECT_THAT(request_handlers[2]->http_request()->headers,
Contains(Key(variations::kClientDataHeader)));
// End the keepalive request by sending back final response.
request_handlers[2]->Send(k200TextResponse);
request_handlers[2]->Done();
// The redirects/response should all be processed in browser.
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(2);
loaders_observer().WaitForTotalOnReceiveResponseProcessed(1);
}
// Chrome browser tests to cover variation header-related behaviors for fetch
// keepalive requests.
class ChromeKeepAliveURLVariationBrowserTest
: public ChromeKeepAliveURLBrowserTestBase,
public ::testing::WithParamInterface<std::string> {
protected:
const std::string& method() const { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(
All,
ChromeKeepAliveURLVariationBrowserTest,
::testing::Values(net::HttpRequestHeaders::kGetMethod,
net::HttpRequestHeaders::kPostMethod),
[](const testing::TestParamInfo<
ChromeKeepAliveURLVariationBrowserTest::ParamType>& info) {
return info.param;
});
// Verifies that the variations header (X-Client-Data) is attached to network
// requests to Google, but stripped on redirects to non-Google.
IN_PROC_BROWSER_TEST_P(ChromeKeepAliveURLVariationBrowserTest,
ReceiveRedirectToGoogleAfterPageUnloadAndStripHeaders) {
const std::string target_url = kKeepAliveEndpoint;
SetUseHttps();
auto request_handlers =
RegisterRequestHandlers({target_url, "/redirect", "/final"});
ASSERT_TRUE(server()->Start());
// Set up redirects according to the following redirect chain:
// fetch("https://www.google.com:<port>/beacon", keepalive: true)
// --> https://www.google.com:<port>/redirect
// --> https://www.b.com:<port>/final
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(kGoogleHost, target_url, method()),
request_handlers[0].get(),
("HTTP/1.1 301 Moved Permanently\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Location: /redirect\r\n"
"\r\n"));
// The 1st redirect should be processed in browser.
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(1);
// This redirect request to Google should contain variation header.
request_handlers[1]->WaitForRequest();
EXPECT_THAT(request_handlers[1]->http_request()->headers,
testing::Contains(testing::Key(variations::kClientDataHeader)));
// The 2nd redirect request should contain no variation header.
request_handlers[1]->Send(
"HTTP/1.1 301 Moved Permanently\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Location: https://b.com/final\r\n"
"\r\n");
request_handlers[1]->Done();
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(2);
// The request is dropped by network service, so no way to verify variation
// header from `request_handlers[2]`.
}