#include <memory>
#include <optional>
#include <tuple>
#include <vector>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/allow_check_is_test_for_testing.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h"
#include "content/browser/attribution_reporting/test/mock_attribution_manager.h"
#include "content/browser/back_forward_cache_test_util.h"
#include "content/browser/loader/keep_alive_request_browsertest_util.h"
#include "content/browser/loader/keep_alive_url_loader.h"
#include "content/browser/loader/keep_alive_url_loader_service.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/keep_alive_url_loader_utils.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/origin.h"
#include "url/url_util.h"
namespace content {
namespace {
using testing::Contains;
using testing::Pair;
constexpr char16_t kPromiseResolvedPageTitle[] = u"Resolved";
std::string GetConnectSrcCSPHeader(const url::Origin& origin) {
return base::StringPrintf("Content-Security-Policy: connect-src 'self' %s",
origin.Serialize().c_str());
}
MATCHER(IsFrameHidden,
base::StrCat({"Frame is", negation ? " not" : "", " hidden"})) {
return arg->GetVisibilityState() == PageVisibilityState::kHidden;
}
}
class FetchKeepAliveCommonTestBase : public KeepAliveRequestBrowserTestBase {
protected:
void LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
const GURL& keepalive_page_url,
net::test_server::ControllableHttpResponse* keepalive_request_handler,
const std::string& response) {
ASSERT_TRUE(NavigateToURL(web_contents(), keepalive_page_url));
RenderFrameHostImplWrapper rfh_1(current_frame_host());
DisableBackForwardCache(web_contents());
keepalive_request_handler->WaitForRequest();
if (loader_service()) {
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
}
FetchHistogramsFromChildProcesses();
ASSERT_TRUE(NavigateToURL(web_contents(), GetCrossOriginPageURL()));
ASSERT_NE(current_frame_host(), rfh_1.get());
ASSERT_TRUE(rfh_1.WaitUntilRenderFrameDeleted());
if (loader_service()) {
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
}
keepalive_request_handler->Send(response);
keepalive_request_handler->Done();
}
void LoadPageWithKeepAliveRequestAndSendResponse(
const GURL& keepalive_page_url,
net::test_server::ControllableHttpResponse* keepalive_request_handler,
const std::string& response) {
ASSERT_TRUE(NavigateToURL(web_contents(), keepalive_page_url));
RenderFrameHostImplWrapper rfh_1(current_frame_host());
keepalive_request_handler->WaitForRequest();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
ASSERT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
keepalive_request_handler->Send(response);
keepalive_request_handler->Done();
}
GURL GetKeepAlivePageURL(
const std::string& method,
size_t num_requests = 1,
std::optional<std::string> headers = std::nullopt) const {
std::string url = base::StringPrintf(
"/set-header-with-file/content/test/data/fetch-keepalive.html?"
"method=%s&requests=%zu",
method.c_str(), num_requests);
if (headers.has_value()) {
url += "&" + *headers;
}
return server()->GetURL(kPrimaryHost, url);
}
GURL GetCrossOriginPageURL() {
return server()->GetURL(kSecondaryHost, "/title2.html");
}
};
class KeepAliveURLBrowserTest
: public FetchKeepAliveCommonTestBase,
public ::testing::WithParamInterface<std::string> {
protected:
const FeaturesType& GetEnabledFeatures() override {
static const FeaturesType enabled_features =
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
{{blink::features::kKeepAliveInBrowserMigration, {}}});
return enabled_features;
}
};
INSTANTIATE_TEST_SUITE_P(
All,
KeepAliveURLBrowserTest,
::testing::Values(net::HttpRequestHeaders::kGetMethod,
net::HttpRequestHeaders::kPostMethod),
[](const testing::TestParamInfo<KeepAliveURLBrowserTest::ParamType>& info) {
return info.param;
});
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, OneRequest) {
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(web_contents(), GetKeepAlivePageURL(method)));
request_handler->WaitForRequest();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
request_handler->Send(k200TextResponse);
request_handler->Done();
TitleWatcher watcher(web_contents(), kPromiseResolvedPageTitle);
EXPECT_EQ(watcher.WaitAndGetTitle(), kPromiseResolvedPageTitle);
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 1),
ExpectedFailedRequests(0, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
DISABLED_TwoConcurrentRequestsPerHost) {
const std::string method = GetParam();
const size_t num_requests = 2;
auto request_handlers =
RegisterRequestHandlers({kKeepAliveEndpoint, kKeepAliveEndpoint});
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(
NavigateToURL(web_contents(), GetKeepAlivePageURL(method, num_requests)));
request_handlers[0]->WaitForRequest();
request_handlers[1]->WaitForRequest();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), num_requests);
request_handlers[0]->Send(k200TextResponse);
request_handlers[1]->Send(k200TextResponse);
request_handlers[0]->Done();
request_handlers[1]->Done();
TitleWatcher watcher(web_contents(), kPromiseResolvedPageTitle);
EXPECT_EQ(watcher.WaitAndGetTitle(), kPromiseResolvedPageTitle);
loaders_observer().WaitForTotalOnReceiveResponseForwarded(2);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK, net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, RequestWithCookie) {
const std::string cookie = "keepaliveTestCookie=testCookieValue";
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(web_contents(), server()->GetURL("/empty.html")));
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
document.cookie = $1 + '; path=/';
fetch($2, {keepalive: true, method: $3});
)",
cookie, kKeepAliveEndpoint, method),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
request_handler->WaitForRequest();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
request_handler->Send(k200TextResponse);
request_handler->Done();
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
EXPECT_THAT(request_handler->http_request()->headers,
Contains(Pair(net::HttpRequestHeaders::kCookie, cookie)));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
RequestAfterNetworkServiceCrashes) {
if (IsInProcessNetworkService()) {
return;
}
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(web_contents(), server()->GetURL("/empty.html")));
SimulateNetworkServiceCrash();
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
fetch($1, {keepalive: true, method: $2});
)",
kKeepAliveEndpoint, method),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
request_handler->WaitForRequest();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
request_handler->Send(k200TextResponse);
request_handler->Done();
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
}
#if BUILDFLAG(IS_MAC)
#define MAYBE_ReceiveResponseAfterPageUnload \
DISABLED_ReceiveResponseAfterPageUnload
#else
#define MAYBE_ReceiveResponseAfterPageUnload ReceiveResponseAfterPageUnload
#endif
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
MAYBE_ReceiveResponseAfterPageUnload) {
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(method), request_handler.get(),
k200TextResponse));
loaders_observer().WaitForTotalOnReceiveResponseProcessed(1);
EXPECT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 0),
ExpectedFailedRequests(0, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
ReceiveResponseInBackForwardCache) {
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(web_contents(), GetKeepAlivePageURL(method)));
RenderFrameHostImplWrapper rfh_1(current_frame_host());
request_handler->WaitForRequest();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
FetchHistogramsFromChildProcesses();
ASSERT_TRUE(NavigateToURL(web_contents(), GetCrossOriginPageURL()));
ASSERT_EQ(rfh_1->GetLifecycleState(),
RenderFrameHost::LifecycleState::kInBackForwardCache);
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
ASSERT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
request_handler->Send(k200TextResponse);
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
ASSERT_TRUE(HistoryGoBack(web_contents()));
TitleWatcher watcher(web_contents(), kPromiseResolvedPageTitle);
EXPECT_EQ(watcher.WaitAndGetTitle(), kPromiseResolvedPageTitle);
request_handler->Done();
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 1),
ExpectedFailedRequests(0, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, MultipleRedirectsRequest) {
const auto beacon_endpoint =
base::StringPrintf("%s?id=%s", kKeepAliveEndpoint, kBeaconId);
auto request_handler =
std::move(RegisterRequestHandlers({beacon_endpoint})[0]);
ASSERT_TRUE(server()->Start());
const auto target_url = server()->GetURL(kSecondaryHost, beacon_endpoint);
const auto beacon_url = GetCrossOriginMultipleRedirectsURL(target_url);
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
fetch($1, {keepalive: true, mode: 'no-cors'});
)",
beacon_url),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(3);
ASSERT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 1u);
request_handler->WaitForRequest();
request_handler->Send(k200TextResponse);
request_handler->Done();
loaders_observer().WaitForTotalOnReceiveRedirectForwarded(3);
loaders_observer().WaitForTotalOnReceiveResponseForwarded(1);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
EXPECT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 1),
ExpectedFailedRequests(0, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
MultipleRedirectsAndFailInBetweenRequest) {
const auto beacon_endpoint =
base::StringPrintf("%s?id=%s", kKeepAliveEndpoint, kBeaconId);
ASSERT_TRUE(server()->Start());
const auto target_url = server()->GetURL(kSecondaryHost, beacon_endpoint);
const auto beacon_url = GetSameAndCrossOriginRedirectsURL(target_url);
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
fetch($1, {keepalive: true, mode: 'cors'});
)",
beacon_url),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(1);
ASSERT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 1u);
loaders_observer().WaitForTotalOnReceiveRedirectForwarded(1);
loaders_observer().WaitForTotalOnCompleteForwarded({net::ERR_FAILED});
EXPECT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1, 1));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
MultipleRedirectsAndFailAtLastRequest) {
const auto beacon_endpoint =
base::StringPrintf("%s?id=%s", kKeepAliveEndpoint, kBeaconId);
auto request_handler =
std::move(RegisterRequestHandlers({beacon_endpoint})[0]);
ASSERT_TRUE(server()->Start());
const auto target_url = server()->GetURL(kSecondaryHost, beacon_endpoint);
const auto beacon_url = GetSameOriginMultipleRedirectsURL(target_url);
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
fetch($1, {keepalive: true, mode: 'cors'});
)",
beacon_url),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(2);
ASSERT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 1u);
request_handler->WaitForRequest();
request_handler->Send(k200TextResponse);
request_handler->Done();
loaders_observer().WaitForTotalOnReceiveRedirectForwarded(2);
loaders_observer().WaitForTotalOnCompleteForwarded({net::ERR_FAILED});
EXPECT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1, 1));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
ReceiveRedirectAfterPageUnload) {
const std::string method = GetParam();
const char redirect_target[] = "/beacon-redirected";
auto request_handlers =
RegisterRequestHandlers({kKeepAliveEndpoint, redirect_target});
ASSERT_TRUE(server()->Start());
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(method), request_handlers[0].get(),
base::StringPrintf(k301Response, redirect_target)));
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(1);
request_handlers[1]->WaitForRequest();
request_handlers[1]->Send(k200TextResponse);
request_handlers[1]->Done();
loaders_observer().WaitForTotalOnReceiveResponseProcessed(1);
EXPECT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 0),
ExpectedFailedRequests(0, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
ReceiveUnSafeRedirectAfterPageUnload) {
const std::string method = GetParam();
const char unsafe_redirect_target[] = "chrome://settings";
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(method), request_handler.get(),
base::StringPrintf(k301Response, unsafe_redirect_target)));
loaders_observer().WaitForTotalOnComplete({net::ERR_UNSAFE_REDIRECT});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
ReceiveViolatingCSPRedirectAfterPageUnload) {
const std::string method = GetParam();
const char violating_csp_redirect_target[] = "http://b.com/beacon-redirected";
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
const GURL allowed_csp_url = server()->GetURL(kAllowedCspHost, "/");
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(
method, 1,
GetConnectSrcCSPHeader(url::Origin::Create(allowed_csp_url))),
request_handler.get(),
base::StringPrintf(k301Response, violating_csp_redirect_target)));
loaders_observer().WaitForTotalOnComplete({net::ERR_BLOCKED_BY_CSP});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1, 0));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, ReceiveMixedContentRedirect) {
SetUseHttps();
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
std::string same_content_target =
server()->GetURL(kPrimaryHost, "/beacon-redirected").spec();
std::string mixed_content_target = same_content_target;
base::ReplaceSubstringsAfterOffset(&mixed_content_target, 0, "https", "http");
ASSERT_NO_FATAL_FAILURE(LoadPageWithKeepAliveRequestAndSendResponse(
GetKeepAlivePageURL(method), request_handler.get(),
base::StringPrintf(k301Response, mixed_content_target.c_str())));
loaders_observer().WaitForTotalOnReceiveRedirectForwarded(1);
loaders_observer().WaitForTotalOnReceiveResponseProcessed(0);
loaders_observer().WaitForTotalOnCompleteForwarded({});
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1));
}
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
ReceiveMixedContentRedirectAfterUnload) {
SetUseHttps();
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
auto redirected_request_handler =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/beacon-redirected");
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(embedded_test_server()->Start());
std::string mixed_content_target =
embedded_test_server()->GetURL(kPrimaryHost, "/beacon-redirected").spec();
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(method), request_handler.get(),
base::StringPrintf(k301Response, mixed_content_target.c_str())));
redirected_request_handler->WaitForRequest();
redirected_request_handler->Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"Acked!");
redirected_request_handler->Done();
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(1);
loaders_observer().WaitForTotalOnReceiveResponseProcessed(1);
EXPECT_EQ(loader_service()->NumDisconnectedLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 0),
ExpectedFailedRequests(0, 0));
}
#if BUILDFLAG(IS_MAC)
#define MAYBE_ReceiveViolatingCSPRedirectInChildFrame \
DISABLED_ReceiveViolatingCSPRedirectInChildFrame
#else
#define MAYBE_ReceiveViolatingCSPRedirectInChildFrame \
ReceiveViolatingCSPRedirectInChildFrame
#endif
IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest,
MAYBE_ReceiveViolatingCSPRedirectInChildFrame) {
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({GetKeepAliveEndpoint("main")})[0]);
ASSERT_TRUE(server()->Start());
const GURL main_target_url =
server()->GetURL(kAllowedCspHost, GetKeepAliveEndpoint("main"));
const GURL child_target_url =
server()->GetURL(kAllowedCspHost, GetKeepAliveEndpoint("child"));
const GURL main_beacon_url = GetSameOriginRedirectURL(main_target_url);
const GURL child_beacon_url = GetSameOriginRedirectURL(child_target_url);
ASSERT_TRUE(NavigateToURL(
web_contents(),
server()->GetURL(
kPrimaryHost,
"/set-header-with-file/content/test/data/title1.html?" +
GetConnectSrcCSPHeader(url::Origin::Create(main_target_url)))));
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
fetch($1, {keepalive: true, mode: 'cors'});
let childLoaded;
let childThrown;
var childLoadedPromise = new Promise(resolve => childLoaded = resolve);
var childThrownPromise = new Promise(resolve => childThrown = resolve);
window.addEventListener('message', e => {
if (e.data === 'loaded') {
childLoaded(true);
} else {
childThrown(e.data);
}
});
// Child Frame (Same-Origin):
// Prepares the child page that also sends out a keepalive request.
const iframe = document.createElement('iframe');
iframe.srcdoc = `
<meta http-equiv="Content-Security-Policy" content="connect-src 'self';">
<script>
fetch($2, {keepalive: true, mode: 'cors'}).catch(e =>
window.parent.postMessage(e.message, "*")
);
window.parent.postMessage('loaded', "*");
</script>
`;
document.body.appendChild(iframe);
)",
main_beacon_url, child_beacon_url),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
ASSERT_EQ(current_frame_host()->child_count(), 1u);
EXPECT_EQ(true, EvalJs(web_contents(), "childLoadedPromise"));
EXPECT_EQ("Failed to fetch", EvalJs(web_contents(), "childThrownPromise"));
request_handler->WaitForRequest();
request_handler->Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"Acked!");
request_handler->Done();
loaders_observer().WaitForTotalOnReceiveRedirectProcessed(1);
loaders_observer().WaitForTotalOnReceiveResponse(1);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(2, 2),
ExpectedStartedRequests(2, 2),
ExpectedSucceededRequests(1, 1),
ExpectedFailedRequests(1, 1));
}
class FetchKeepAlivePreMigrationBrowserTest
: public FetchKeepAliveCommonTestBase,
public ::testing::WithParamInterface<std::string> {
protected:
const FeaturesType& GetEnabledFeatures() override {
static const FeaturesType enabled_features =
content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(
{{features::kBackForwardCache, {}}});
return enabled_features;
}
const DisabledFeaturesType& GetDisabledFeatures() override {
static const DisabledFeaturesType disabled_features =
GetDefaultDisabledBackForwardCacheFeaturesForTesting(
{blink::features::kKeepAliveInBrowserMigration,
blink::features::kFetchLaterAPI});
return disabled_features;
}
};
INSTANTIATE_TEST_SUITE_P(
All,
FetchKeepAlivePreMigrationBrowserTest,
::testing::Values(net::HttpRequestHeaders::kGetMethod,
net::HttpRequestHeaders::kPostMethod),
[](const testing::TestParamInfo<
FetchKeepAlivePreMigrationBrowserTest::ParamType>& info) {
return info.param;
});
IN_PROC_BROWSER_TEST_P(FetchKeepAlivePreMigrationBrowserTest, OneRequest) {
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_TRUE(NavigateToURL(web_contents(), GetKeepAlivePageURL(method)));
request_handler->WaitForRequest();
request_handler->Send(k200TextResponse);
request_handler->Done();
TitleWatcher watcher(web_contents(), kPromiseResolvedPageTitle);
EXPECT_EQ(watcher.WaitAndGetTitle(), kPromiseResolvedPageTitle);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(0, 1),
ExpectedStartedRequests(0, 1),
ExpectedSucceededRequests(0, 1),
ExpectedFailedRequests(0, 0));
}
IN_PROC_BROWSER_TEST_P(FetchKeepAlivePreMigrationBrowserTest,
ReceiveResponseAfterPageUnload) {
const std::string method = GetParam();
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(method), request_handler.get(),
k200TextResponse));
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(0, 1),
ExpectedStartedRequests(0, 1),
ExpectedSucceededRequests(0),
ExpectedFailedRequests(0));
}
class KeepAliveURLAttributionReportingBrowserTest
: public FetchKeepAliveCommonTestBase,
public ::testing::WithParamInterface<std::string> {
protected:
void SetUp() override {
SetUseHttps();
FetchKeepAliveCommonTestBase::SetUp();
}
void SetUpOnMainThread() override {
auto mock_manager = std::make_unique<MockAttributionManager>();
auto mock_data_host_manager =
std::make_unique<MockAttributionDataHostManager>();
mock_manager->SetDataHostManager(std::move(mock_data_host_manager));
static_cast<StoragePartitionImpl*>(
web_contents()->GetBrowserContext()->GetDefaultStoragePartition())
->OverrideAttributionManagerForTesting(std::move(mock_manager));
FetchKeepAliveCommonTestBase::SetUpOnMainThread();
}
const FeaturesType& GetEnabledFeatures() override {
static const FeaturesType enabled_features =
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
{{blink::features::kKeepAliveInBrowserMigration, {}}});
return enabled_features;
}
};
INSTANTIATE_TEST_SUITE_P(
All,
KeepAliveURLAttributionReportingBrowserTest,
::testing::Values(net::HttpRequestHeaders::kGetMethod,
net::HttpRequestHeaders::kPostMethod),
[](const testing::TestParamInfo<
KeepAliveURLAttributionReportingBrowserTest::ParamType>& info) {
return info.param;
});
IN_PROC_BROWSER_TEST_P(KeepAliveURLAttributionReportingBrowserTest,
ReceiveViolatingCSPRedirect_NotForwarded) {
const std::string method = GetParam();
const char violating_csp_redirect_target[] =
"http://b.test/beacon-redirected";
auto request_handler =
std::move(RegisterRequestHandlers({kKeepAliveEndpoint})[0]);
ASSERT_TRUE(server()->Start());
const GURL allowed_csp_url = server()->GetURL(kAllowedCspHost, "/");
auto* data_host_manager = static_cast<MockAttributionDataHostManager*>(
AttributionManager::FromWebContents(web_contents())
->GetDataHostManager());
EXPECT_CALL(*data_host_manager, NotifyBackgroundRegistrationStarted).Times(1);
EXPECT_CALL(*data_host_manager, NotifyBackgroundRegistrationData).Times(0);
EXPECT_CALL(*data_host_manager, NotifyBackgroundRegistrationCompleted)
.Times(1);
ASSERT_NO_FATAL_FAILURE(
LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
GetKeepAlivePageURL(
method, 1,
GetConnectSrcCSPHeader(url::Origin::Create(allowed_csp_url))),
request_handler.get(),
base::StringPrintf(k301Response, violating_csp_redirect_target)));
loaders_observer().WaitForTotalOnComplete({net::ERR_BLOCKED_BY_CSP});
}
class KeepAliveFetchRetryBrowserTest
: public FetchKeepAliveCommonTestBase,
public ::testing::WithParamInterface<std::string> {
protected:
static constexpr int kMaxRetryCountPerLoaderForTesting = 2;
static constexpr int kMaxRetryCountPerNetworkIsolationKeyForTesting = 3;
static constexpr int kMaxRetryCountPerFactoryForTesting = 4;
void SetUp() override {
SetUseHttps();
FetchKeepAliveCommonTestBase::SetUp();
}
const FeaturesType& GetEnabledFeatures() override {
static const FeaturesType enabled_features =
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
{{blink::features::kFetchRetry,
{
{"max_retry_count",
base::NumberToString(kMaxRetryCountPerLoaderForTesting)},
{"max_retries_per_factory",
base::NumberToString(kMaxRetryCountPerFactoryForTesting)},
{"max_retries_per_nik",
base::NumberToString(
kMaxRetryCountPerNetworkIsolationKeyForTesting)},
{"min_retry_delta", "1ms"},
{"min_retry_backoff", "1.0"},
{"max_retry_age", "1d"},
}}});
return enabled_features;
}
void LoadPageAndTriggerFetchKeepaliveWithRetry(const GURL& fetch_url) {
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
TriggerFetchKeepaliveWithRetry(fetch_url);
}
void TriggerFetchKeepaliveWithRetry(const GURL& fetch_url) {
ASSERT_TRUE(ExecJs(
web_contents(),
JsReplace(R"(
window.fetchPromise = fetch($1, {
keepalive: true,
method: $2,
retryOptions: {
maxAttempts: $3,
retryAfterUnload: true,
retryNonIdempotent: true,
maxAge: 500
}});)",
fetch_url, GetParam(), kMaxRetryCountPerLoaderForTesting),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
}
void ExpectFetchResolvedInJavaScript(bool result_is_ok) {
EXPECT_EQ(result_is_ok, EvalJs(web_contents(), R"((async function() {
try {
let result = await window.fetchPromise;
return result.ok;
} catch (e) {
// The fetch failed.
return false;
}
})())"));
}
};
INSTANTIATE_TEST_SUITE_P(
All,
KeepAliveFetchRetryBrowserTest,
::testing::Values(net::HttpRequestHeaders::kGetMethod,
net::HttpRequestHeaders::kPostMethod),
[](const testing::TestParamInfo<KeepAliveFetchRetryBrowserTest::ParamType>&
info) { return info.param; });
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest,
FailedRetriedThenSucceeded) {
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
int request_count = 0;
URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != beacon_url) {
return false;
}
request_count++;
if (request_count == 1) {
params->client->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED));
return true;
} else {
EXPECT_EQ(params->url_request.headers.GetHeader(
KeepAliveURLLoader::kRetryAttemptsHeader),
"1");
URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n",
"\r\n", params->client.get());
return true;
}
}));
LoadPageAndTriggerFetchKeepaliveWithRetry(beacon_url);
loaders_observer().WaitForTotalOnComplete(
{net::ERR_NETWORK_CHANGED, net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
ExpectFetchResolvedInJavaScript(true);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 1),
ExpectedFailedRequests(1, 0),
1);
}
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest,
FailedNotRetried_HTTPError) {
net::test_server::ControllableHttpResponse response(server(),
kKeepAliveEndpoint);
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
LoadPageAndTriggerFetchKeepaliveWithRetry(beacon_url);
response.WaitForRequest();
response.Send(net::HTTP_INTERNAL_SERVER_ERROR);
response.Done();
loaders_observer().WaitForTotalOnReceiveResponse(1);
loaders_observer().WaitForTotalOnComplete({net::OK});
loaders_observer().WaitForTotalOnCompleteForwarded({net::OK});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchResolvedInJavaScript(false);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(1, 1),
ExpectedFailedRequests(0),
0);
}
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest,
FailedNotRetried_NonRetryEligibleNetworkError) {
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
URLLoaderInterceptor::SetupRequestFailForURL(beacon_url,
net::ERR_SSL_PROTOCOL_ERROR);
LoadPageAndTriggerFetchKeepaliveWithRetry(beacon_url);
loaders_observer().WaitForTotalOnComplete({net::ERR_SSL_PROTOCOL_ERROR});
loaders_observer().WaitForTotalOnCompleteForwarded(
{net::ERR_SSL_PROTOCOL_ERROR});
EXPECT_EQ(loader_service()->NumLoadersForTesting(), 0u);
ExpectFetchResolvedInJavaScript(false);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1, 1),
0);
}
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest,
FailedRetriedUntilMaxRetryCount) {
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
URLLoaderInterceptor::SetupRequestFailForURL(beacon_url,
net::ERR_NETWORK_CHANGED);
LoadPageAndTriggerFetchKeepaliveWithRetry(beacon_url);
std::vector<int> errors = {net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED,
net::ERR_NETWORK_CHANGED};
loaders_observer().WaitForTotalOnComplete(errors);
EXPECT_EQ(loader_service()->NumLoadersAttemptingRetryForTesting(
false),
0u);
loaders_observer().WaitForTotalOnCompleteForwarded(
{net::ERR_NETWORK_CHANGED});
ExpectFetchResolvedInJavaScript(false);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(3, 1),
kMaxRetryCountPerLoaderForTesting);
TriggerFetchKeepaliveWithRetry(beacon_url);
errors.insert(errors.end(),
{net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED});
loaders_observer().WaitForTotalOnComplete(errors);
EXPECT_EQ(loader_service()->NumLoadersAttemptingRetryForTesting(
false),
0u);
loaders_observer().WaitForTotalOnCompleteForwarded(
{net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED});
ExpectFetchResolvedInJavaScript(false);
FetchHistogramsFromChildProcesses();
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(2, 2),
ExpectedStartedRequests(2, 2),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(5, 2),
kMaxRetryCountPerNetworkIsolationKeyForTesting);
TriggerFetchKeepaliveWithRetry(beacon_url);
errors.push_back(net::ERR_NETWORK_CHANGED);
loaders_observer().WaitForTotalOnComplete(errors);
loaders_observer().WaitForTotalOnCompleteForwarded(
{net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED,
net::ERR_NETWORK_CHANGED});
ExpectFetchResolvedInJavaScript(false);
FetchHistogramsFromChildProcesses();
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(3, 3),
ExpectedStartedRequests(3, 3),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(6, 3),
kMaxRetryCountPerNetworkIsolationKeyForTesting);
ASSERT_TRUE(NavigateToURL(web_contents(), GetCrossOriginPageURL()));
TriggerFetchKeepaliveWithRetry(beacon_url);
errors.insert(errors.end(),
{net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED});
loaders_observer().WaitForTotalOnComplete(errors);
loaders_observer().WaitForTotalOnCompleteForwarded(
{net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED,
net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED});
ExpectFetchResolvedInJavaScript(false);
FetchHistogramsFromChildProcesses();
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(4, 4),
ExpectedStartedRequests(4, 4),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(8, 4),
kMaxRetryCountPerFactoryForTesting);
TriggerFetchKeepaliveWithRetry(beacon_url);
errors.push_back(net::ERR_NETWORK_CHANGED);
loaders_observer().WaitForTotalOnComplete(errors);
loaders_observer().WaitForTotalOnCompleteForwarded(
{net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED,
net::ERR_NETWORK_CHANGED, net::ERR_NETWORK_CHANGED,
net::ERR_NETWORK_CHANGED});
ExpectFetchResolvedInJavaScript(false);
FetchHistogramsFromChildProcesses();
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(5, 5),
ExpectedStartedRequests(5, 5),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(9, 5),
kMaxRetryCountPerFactoryForTesting);
}
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest,
ClearingDataClearsLoaderAttemptingRetry) {
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
URLLoaderInterceptor::SetupRequestFailForURL(beacon_url,
net::ERR_NETWORK_CHANGED);
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
window.fetchPromise = fetch($1, {
keepalive: true,
retryOptions: {
maxAttempts: 10,
initialDelay: (1000 * 3600 * 24), // 1 day
retryNonIdempotent: true,
maxAge: 500
}});)",
beacon_url),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
loaders_observer().WaitForTotalOnComplete({net::ERR_NETWORK_CHANGED});
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 1u);
EXPECT_EQ(loader_service()->NumLoadersAttemptingRetryForTesting(
true),
1u);
EXPECT_EQ(loader_service()->NumLoadersAttemptingRetryForTesting(
false),
1u);
base::RunLoop run_loop;
static_cast<StoragePartitionImpl*>(
web_contents()->GetBrowserContext()->GetDefaultStoragePartition())
->ClearData(StoragePartition::REMOVE_KEEPALIVE_LOADS_ATTEMPTING_RETRY,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
blink::StorageKey(), base::Time(), base::Time::Max(),
run_loop.QuitClosure());
run_loop.Run();
ASSERT_EQ(loader_service()->NumLoadersForTesting(), 0u);
EXPECT_EQ(loader_service()->NumLoadersAttemptingRetryForTesting(
true),
0u);
EXPECT_EQ(loader_service()->NumLoadersAttemptingRetryForTesting(
false),
0u);
ExpectFetchKeepAliveHistogram(
FetchKeepAliveRequestMetricType::kFetch,
ExpectedTotalRequests(1, 1),
ExpectedStartedRequests(1, 1),
ExpectedSucceededRequests(0, 0),
ExpectedFailedRequests(1),
0);
}
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest, RetryOptionsNotSet) {
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
EXPECT_EQ(true, EvalJs(web_contents(), JsReplace(R"((async function() {
const request = new Request($1, {keepalive: true});
return request.getRetryOptions() === null;
})())",
beacon_url)));
}
IN_PROC_BROWSER_TEST_P(KeepAliveFetchRetryBrowserTest, RetryOptionsSet) {
ASSERT_TRUE(server()->Start());
const auto beacon_url = server()->GetURL(kPrimaryHost, kKeepAliveEndpoint);
ASSERT_TRUE(NavigateToURL(web_contents(),
server()->GetURL(kPrimaryHost, "/title1.html")));
EXPECT_EQ(true, EvalJs(web_contents(), JsReplace(R"((async function() {
const init = {
keepalive: true,
retryOptions: {
maxAttempts: 5,
initialDelay: 100,
backoffFactor: 2.5,
maxAge: 5000,
retryAfterUnload: true,
retryNonIdempotent: true,
retryOnlyIfServerUnreached: true,
}
};
const request = new Request($1, init);
const options = request.getRetryOptions();
if (!options) return false;
const inputOptions = init.retryOptions;
return options.maxAttempts === inputOptions.maxAttempts &&
options.initialDelay === inputOptions.initialDelay &&
options.backoffFactor === inputOptions.backoffFactor &&
options.maxAge === inputOptions.maxAge &&
options.retryAfterUnload === inputOptions.retryAfterUnload &&
options.retryNonIdempotent === inputOptions.retryNonIdempotent &&
(options.retryOnlyIfServerUnreached ===
inputOptions.retryOnlyIfServerUnreached);
})())",
beacon_url)));
}
}