// 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 <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 "content/browser/back_forward_cache_test_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/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 "third_party/blink/public/common/features.h" #include "url/origin.h" #include "url/url_util.h" namespace content { namespace { Contains; Pair; constexpr char16_t kPromiseResolvedPageTitle[] = …; constexpr char kPrimaryHost[] = …; constexpr char kSecondaryHost[] = …; constexpr char kAllowedCspHost[] = …; constexpr char kKeepAliveEndpoint[] = …; constexpr char k200TextResponse[] = …; constexpr char k301Response[] = …; constexpr char kBeaconId[] = …; constexpr char kFetchLaterEndpoint[] = …; std::string GetKeepAliveEndpoint(std::optional<std::string> id = std::nullopt) { … } std::string GetConnectSrcCSPHeader(const url::Origin& origin) { … } // Encodes the given `url` using the JS method encodeURIComponent. std::string EncodeURL(const GURL& url) { … } MATCHER(IsFrameHidden, base::StrCat({ … } } // namespace class KeepAliveURLBrowserTestBase : public ContentBrowserTest { … }; class FetchKeepAliveCommonTestBase : public KeepAliveURLBrowserTestBase { … }; // Contains the integration tests for loading fetch(url, {keepalive: true}) // requests via browser process that are difficult to reliably reproduce in web // tests. // // Note that due to using different approach, tests to cover implementation // before `kKeepAliveInBrowserMigration`, i.e. loading via delaying renderer // shutdown, cannot be verified with inspecting KeepAliveURLLoaderService here // and still live in a different file // content/browser/renderer_host/render_process_host_browsertest.cc class KeepAliveURLBrowserTest : public FetchKeepAliveCommonTestBase, public ::testing::WithParamInterface<std::string> { … }; INSTANTIATE_TEST_SUITE_P(…); IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, OneRequest) { … } // Verify keepalive request loading works given 2 concurrent requests to the // same host. // // Note: Chromium allows at most 6 concurrent connections to the same host under // HTTP 1.1 protocol, which `server()` uses by default. // Exceeding this limit will hang the browser. // TODO(crbug.com/40262244): Flaky on Fuchsia and Android. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, DISABLED_TwoConcurrentRequestsPerHost) { … } IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, RequestWithCookie) { … } IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, RequestAfterNetworkServiceCrashes) { … } // TODO(crbug.com/40236167): Re-enable this test on Mac. #if BUILDFLAG(IS_MAC) #define MAYBE_ReceiveResponseAfterPageUnload … #else #define MAYBE_ReceiveResponseAfterPageUnload … #endif // 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(KeepAliveURLBrowserTest, MAYBE_ReceiveResponseAfterPageUnload) { … } // 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(KeepAliveURLBrowserTest, ReceiveResponseInBackForwardCache) { … } // Tests fetch(..., {keepalive: true}) with a cross-origin & CORS-safelisted // request that causes a redirect chain of 4 URLs. // // As the mode is set to "no-cors" for CORS-safelisted requests, the redirect is // processed without an error while the request is cross-origin. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, MultipleRedirectsRequest) { … } // Tests fetch(..., {keepalive: true}) with a cross-origin & CORS-safelisted // request that causes a redirect chain of 3 URLs, where the cross-origin URLs // are the 2nd URL & the 3rd URL in the chain. // // As the mode is set to "cors" for CORS-safelisted requests, the redirect will // fail at the first cross-origin URL. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, MultipleRedirectsAndFailInBetweenRequest) { … } // Tests fetch(..., {keepalive: true}) with a cross-origin & CORS-safelisted // request that causes a redirect chain of 3 URLs, where the cross-origin URL // is the target URL (3rd URL in the chain). // // As the mode is set to "cors" for CORS-safelisted requests, the redirect will // fail at the first cross-origin URL. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, MultipleRedirectsAndFailAtLastRequest) { … } // 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(KeepAliveURLBrowserTest, ReceiveRedirectAfterPageUnload) { … } // 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. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, ReceiveUnSafeRedirectAfterPageUnload) { … } // Delays handling an violating CSP redirect for a keepalive ping until after // the page making the keepalive ping has been unloaded. // The browser must ensure the redirect is not followed. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, ReceiveViolatingCSPRedirectAfterPageUnload) { … } // Verifies a redirect to mixed content target URL is not loaded. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, ReceiveMixedContentRedirect) { … } // Verifies a redirect to mixed content target URL is allowed by // KeepAliveURLLoader if the page making the fetch keepalive request has been // unloaded, the same as pre-migration approach https://crrev.com/c/518743. // // Note that the current implementation in Blink & content cannot handle mixed // content checking without the RFHI of the page that loads the request. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, ReceiveMixedContentRedirectAfterUnload) { … } // TODO(crbug.com/40236167): Re-enable this test on Mac. #if BUILDFLAG(IS_MAC) #define MAYBE_ReceiveViolatingCSPRedirectInChildFrame … #else #define MAYBE_ReceiveViolatingCSPRedirectInChildFrame … #endif // Ensures that a keepalive request in a child frame use its RFH's data instead // of its parent frame's: // The main frame CSP allows `kAllowedCspHost`, while the child frame CSP does // not. See also https://w3c.github.io/webappsec-csp/#security-inherit-csp. IN_PROC_BROWSER_TEST_P(KeepAliveURLBrowserTest, MAYBE_ReceiveViolatingCSPRedirectInChildFrame) { … } // Contains the browser tests for loading fetch(url, {keepalive: true}) // requests **without** routing to browser process. class FetchKeepAlivePreMigrationBrowserTest : public FetchKeepAliveCommonTestBase, public ::testing::WithParamInterface<std::string> { … }; INSTANTIATE_TEST_SUITE_P(…); IN_PROC_BROWSER_TEST_P(FetchKeepAlivePreMigrationBrowserTest, OneRequest) { … } IN_PROC_BROWSER_TEST_P(FetchKeepAlivePreMigrationBrowserTest, ReceiveResponseAfterPageUnload) { … } class SendBeaconBrowserTestBase : public KeepAliveURLBrowserTestBase { … }; class SendBeaconBrowserTest : public SendBeaconBrowserTestBase, public ::testing::WithParamInterface<std::string> { … }; INSTANTIATE_TEST_SUITE_P(…); // TODO(crbug.com/40931297): Re-enable this test on Mac. #if BUILDFLAG(IS_MAC) #define MAYBE_MultipleRedirectsRequestWithIframeRemoval … #else #define MAYBE_MultipleRedirectsRequestWithIframeRemoval … #endif // Tests navigator.sendBeacon() with a cross-origin & CORS-safelisted request // that causes a redirect chain of 4 URLs. // // The JS call happens in an iframe that is removed right after the sendBeacon() // call, so the chain of redirects & response handling must survive the iframe // unload. IN_PROC_BROWSER_TEST_P(SendBeaconBrowserTest, MAYBE_MultipleRedirectsRequestWithIframeRemoval) { … } // Tests navigator.sendBeacon() with a cross-origin & CORS-safelisted request // that causes a redirect chain of 4 URLs. // // Unlike the `MultipleRedirectsRequestWithIframeRemoval` test case above, the // request here is fired within an iframe that will be removed shortly // (delayed by 0ms, roughly in the JS next event cycle). // This is to mimic the following scenario: // // 1. The server returns a redirect. // 2. In the browser process KeepAliveURLLoader::OnReceiveRedirect(), // forwarding_client_ is not null (as renderer/iframe still exists), so it // calls forwarding_client_->OnReceiveRedirect() IPC to forward to renderer. // 3. The renderer process is somehow shut down before its // URLLoaderClient::OnReceiveRedirect() is finished, so the redirect chain is // incompleted. // 4. KeepAliveURLLoader::OnRendererConnectionError() is triggered, and only // aware of forwarding_client_'s disconnection. It should take over redirect // chain handling. // // Without delaying iframe removal, renderer disconnection may happen in between // (2) and (3). IN_PROC_BROWSER_TEST_P(SendBeaconBrowserTest, MultipleRedirectsRequestWithDelayedIframeRemoval) { … } // Tests navigator.sendBeacon() with a cross-origin & CORS-safelisted request // that redirects from url1 to url2. The redirect is handled by a server // endpoint (/no-cors-server-redirect-307) which does not support CORS. // As navigator.sendBeacon() marks its request with `no-cors`, the redirect // should succeed. // TODO(crbug.com/40282448): Flaky on Android and Mac. #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID) #define MAYBE_CrossOriginAndCORSSafelistedRedirectRequest … #else #define MAYBE_CrossOriginAndCORSSafelistedRedirectRequest … #endif IN_PROC_BROWSER_TEST_P(SendBeaconBrowserTest, MAYBE_CrossOriginAndCORSSafelistedRedirectRequest) { … } class SendBeaconBlobBrowserTest : public SendBeaconBrowserTestBase { … }; // Tests navigator.sendBeacon() with a cross-origin & non-CORS-safelisted // request that redirects from url1 to url2. The redirect is handled by a server // endpoint (/no-cors-server-redirect-307) which does not support CORS. // As navigator.sendBeacon() marks its request with `no-cors`, the redirect // should fail. IN_PROC_BROWSER_TEST_F(SendBeaconBlobBrowserTest, CrossOriginAndNonCORSSafelistedRedirectRequest) { … } // A base class to help testing JS fetchLater() API behaviors. class FetchLaterBrowserTestBase : public KeepAliveURLBrowserTestBase { … }; // A type to support parameterized testing for timeout-related tests. struct TestTimeoutType { … }; // Tests to cover FetchLater's behaviors when BackForwardCache is off. // // Disables BackForwardCache such that a page is discarded right away on user // navigating to another page. class FetchLaterNoBackForwardCacheBrowserTest : public FetchLaterBrowserTestBase, public testing::WithParamInterface<TestTimeoutType> { … }; INSTANTIATE_TEST_SUITE_P(…); // All pending FetchLater requests should be sent after the initiator page is // gone, no matter how much time their activateAfter has left. // Disables BackForwardCache such that a page is discarded right away on user // navigating to another page. IN_PROC_BROWSER_TEST_P(FetchLaterNoBackForwardCacheBrowserTest, SendOnPageDiscardBeforeActivationTimeout) { … } class FetchLaterWithBackForwardCacheMetricsBrowserTestBase : public FetchLaterBrowserTestBase, public BackForwardCacheMetricsTestMatcher { … }; // Tests to cover FetchLater's behaviors when BackForwardCache is on but does // not come into play. // // Setting long `BackForwardCache TTL (1min)` so that FetchLater sending cannot // be caused by page eviction out of BackForwardCache. class FetchLaterNoActivationTimeoutBrowserTest : public FetchLaterWithBackForwardCacheMetricsBrowserTestBase { … }; // A pending FetchLater request with default options should be sent after the // initiator page is gone. // Similar to SendOnPageDiscardBeforeActivationTimeout. IN_PROC_BROWSER_TEST_F(FetchLaterNoActivationTimeoutBrowserTest, SendOnPageDeletion) { … } // A pending FetchLater request should have been sent after its page gets // restored from BackForwardCache before getting evicted. It is because, by // default, pending requests are all flushed on BFCache no matter // BackgroundSync is on or not. See http://b/310541607#comment28. IN_PROC_BROWSER_TEST_F( FetchLaterNoActivationTimeoutBrowserTest, FlushedWhenPageIsRestoredBeforeBeingEvictedFromBackForwardCache) { … } // Without an activateAfter set, a pending FetchLater request should not be // sent out during its page frozen state. // Similar to ResetActivationTimeoutTimerOnPageResume. IN_PROC_BROWSER_TEST_F(FetchLaterNoActivationTimeoutBrowserTest, NotSendWhenPageIsResumedAfterBeingFrozen) { … } // Tests to cover FetchLater's activateAfter behaviors when BackForwardCache // is on and may come into play. // // BackForwardCache eviction is simulated by calling // `DisableBFCacheForRFHForTesting(previous_document())` instead of relying on // its TTL. class FetchLaterActivationTimeoutBrowserTest : public FetchLaterWithBackForwardCacheMetricsBrowserTestBase { … }; // When setting activateAfter>0, a pending FetchLater request should be sent // after around the specified time, if no navigation happens. IN_PROC_BROWSER_TEST_F(FetchLaterActivationTimeoutBrowserTest, SendOnActivationTimeout) { … } // A pending FetchLater request should be sent when its page is evicted out of // BackForwardCache. IN_PROC_BROWSER_TEST_F(FetchLaterActivationTimeoutBrowserTest, SendOnBackForwardCachedEviction) { … } // All other send-on-BFCache behaviors are covered in // send-on-deactivate.tentative.https.window.js } // namespace content