| // 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 <type_traits> |
| |
| #include "base/feature_list.h" |
| #include "base/location.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/browser/back_forward_cache_test_util.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/permission_controller_delegate.h" |
| #include "content/public/browser/permission_result.h" |
| #include "content/public/browser/render_frame_host.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/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| |
| namespace content { |
| |
| MATCHER(IsFrameVisible, |
| base::StrCat({"Frame is", negation ? " not" : "", " visible"})) { |
| return arg->GetVisibilityState() == PageVisibilityState::kVisible; |
| } |
| |
| MATCHER(IsFrameHidden, |
| base::StrCat({"Frame is", negation ? " not" : "", " hidden"})) { |
| return arg->GetVisibilityState() == PageVisibilityState::kHidden; |
| } |
| |
| class PendingBeaconTimeoutBrowserTestBase : public ContentBrowserTest { |
| protected: |
| using FeaturesType = |
| std::vector<base::test::ScopedFeatureList::FeatureAndParams>; |
| |
| void SetUp() override { |
| feature_list_.InitWithFeaturesAndParameters(GetEnabledFeatures(), {}); |
| ContentBrowserTest::SetUp(); |
| } |
| virtual const FeaturesType& GetEnabledFeatures() = 0; |
| |
| void SetUpOnMainThread() override { |
| CheckPermissionStatus(blink::PermissionType::BACKGROUND_SYNC, |
| blink::mojom::PermissionStatus::GRANTED); |
| // TODO(crbug.com/1293679): Update ContentBrowserTest to support overriding |
| // permissions. |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| // Using base::Unretained() as `embedded_test_server()` is owned by |
| // `content::BrowserTestBase` and should not be able to outlive. |
| embedded_test_server()->RegisterDefaultHandler(base::BindRepeating( |
| &PendingBeaconTimeoutBrowserTestBase::HandleBeaconRequest, |
| base::Unretained(this))); |
| ContentBrowserTest::SetUpOnMainThread(); |
| } |
| |
| // Runs JS `script` in page A, and then navigates to page B. |
| void RunScriptInANavigateToB(const std::string& script) { |
| RunScriptInA(script); |
| |
| // Navigate to B. |
| ASSERT_TRUE( |
| NavigateToURL(embedded_test_server()->GetURL("b.com", "/title1.html"))); |
| } |
| |
| // Runs JS `script` in page A. |
| void RunScriptInA(const std::string& script) { |
| // Navigate to A. |
| ASSERT_TRUE( |
| NavigateToURL(embedded_test_server()->GetURL("a.com", "/title1.html"))); |
| // Execute `script` in A. |
| ASSERT_TRUE(ExecJs(web_contents(), script)); |
| } |
| |
| // Registers a request monitor to wait for `total_beacon` beacons received, |
| // and then starts the test server. |
| void RegisterBeaconRequestMonitor(const size_t total_beacon) { |
| // Using base::Unretained() as `embedded_test_server()` is owned by |
| // `content::BrowserTestBase` and should not be able to outlive. |
| embedded_test_server()->RegisterRequestMonitor(base::BindRepeating( |
| &PendingBeaconTimeoutBrowserTestBase::MonitorBeaconRequest, |
| base::Unretained(this), total_beacon)); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| // Waits for `kBeaconEndpoint` to be requested `total_beacon` times. |
| // If `sent_beacon_count_` does not yet reach `total_beacon`, a RunLoop will |
| // be created and runs until it is stopped by `MonitorBeaconRequest`. |
| void WaitForAllBeaconsSent(size_t total_beacon) { |
| { |
| base::AutoLock auto_lock(count_lock_); |
| if (sent_beacon_count_ >= total_beacon) |
| return; |
| } |
| { |
| base::AutoLock auto_lock(count_lock_); |
| waiting_run_loop_ = std::make_unique<base::RunLoop>( |
| base::RunLoop::Type::kNestableTasksAllowed); |
| } |
| waiting_run_loop_->Run(); |
| { |
| base::AutoLock auto_lock(count_lock_); |
| waiting_run_loop_.reset(); |
| } |
| } |
| |
| WebContents* web_contents() const { return shell()->web_contents(); } |
| |
| RenderFrameHostWrapper& current_document() { |
| current_document_ = |
| std::make_unique<RenderFrameHostWrapper>(current_frame_host()); |
| return *current_document_; |
| } |
| // Caution: the returned might already be killed if BFCache it not working. |
| RenderFrameHostWrapper& previous_document() { |
| CHECK(previous_document_); |
| CHECK(!previous_document_->IsDestroyed()); |
| return *previous_document_; |
| } |
| bool WaitUntilPreviousDocumentDeleted() { |
| CHECK(previous_document_); |
| return previous_document_->WaitUntilRenderFrameDeleted(); |
| } |
| |
| bool NavigateToURL(const GURL& url) { |
| previous_document_ = |
| std::make_unique<RenderFrameHostWrapper>(current_frame_host()); |
| return content::NavigateToURL(web_contents(), url); |
| } |
| |
| size_t sent_beacon_count() { |
| base::AutoLock auto_lock(count_lock_); |
| return sent_beacon_count_; |
| } |
| |
| void CheckPermissionStatus(blink::PermissionType permission_type, |
| blink::mojom::PermissionStatus permission_status) { |
| auto* permission_controller_delegate = |
| web_contents()->GetBrowserContext()->GetPermissionControllerDelegate(); |
| |
| base::MockOnceCallback<void(blink::mojom::PermissionStatus)> callback; |
| EXPECT_CALL(callback, Run(permission_status)); |
| permission_controller_delegate->RequestPermission( |
| permission_type, current_frame_host(), GURL("127.0.0.1"), |
| /*user_gesture=*/true, callback.Get()); |
| } |
| |
| static constexpr char kBeaconEndpoint[] = "/pending_beacon/timeout"; |
| |
| private: |
| // Waits until `total_beacon` beacons received and stops `waiting_run_loop_`. |
| // Invoked on `embedded_test_server()`'s IO Thread, so it's required to use |
| // a lock to protect shared data `sent_beacon_count_` access. |
| void MonitorBeaconRequest(const size_t total_beacon, |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url == kBeaconEndpoint) { |
| { |
| base::AutoLock auto_lock(count_lock_); |
| sent_beacon_count_++; |
| if (sent_beacon_count_ < total_beacon) { |
| return; |
| } |
| } |
| |
| base::AutoLock auto_lock(count_lock_); |
| if (waiting_run_loop_) { |
| waiting_run_loop_->Quit(); |
| } |
| } |
| } |
| |
| // Invoked on `embedded_test_server()`'s IO Thread. |
| // PendingBeacon doesn't really look into its response, so this method just |
| // returns OK status. |
| std::unique_ptr<net::test_server::HttpResponse> HandleBeaconRequest( |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url != kBeaconEndpoint) { |
| return nullptr; |
| } |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| return response; |
| } |
| |
| RenderFrameHost* current_frame_host() { |
| return web_contents()->GetPrimaryMainFrame(); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| |
| base::Lock count_lock_; |
| size_t sent_beacon_count_ GUARDED_BY(count_lock_) = 0; |
| std::unique_ptr<base::RunLoop> waiting_run_loop_; |
| |
| std::unique_ptr<RenderFrameHostWrapper> current_document_ = nullptr; |
| std::unique_ptr<RenderFrameHostWrapper> previous_document_ = nullptr; |
| }; |
| |
| class PendingBeaconWithBackForwardCacheMetricsBrowserTestBase |
| : public PendingBeaconTimeoutBrowserTestBase, |
| public BackForwardCacheMetricsTestMatcher { |
| protected: |
| void SetUpOnMainThread() override { |
| // TestAutoSetUkmRecorder's constructor requires a sequenced context. |
| ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| histogram_tester_ = std::make_unique<base::HistogramTester>(); |
| PendingBeaconTimeoutBrowserTestBase::SetUpOnMainThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| ukm_recorder_.reset(); |
| histogram_tester_.reset(); |
| PendingBeaconTimeoutBrowserTestBase::TearDownOnMainThread(); |
| } |
| |
| // `BackForwardCacheMetricsTestMatcher` implementation. |
| const ukm::TestAutoSetUkmRecorder& ukm_recorder() override { |
| return *ukm_recorder_; |
| } |
| // `BackForwardCacheMetricsTestMatcher` implementation. |
| const base::HistogramTester& histogram_tester() override { |
| return *histogram_tester_; |
| } |
| |
| private: |
| std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_; |
| std::unique_ptr<base::HistogramTester> histogram_tester_; |
| }; |
| |
| struct TestTimeoutType { |
| std::string test_case_name; |
| int32_t timeout; |
| }; |
| |
| // Tests to cover PendingBeacon's backgroundTimeout & timeout behaviors when |
| // BackForwardCache is off. |
| // Disables BackForwardCache by setting its cache size to 0 such that a page is |
| // discarded right away on user navigating to another page. And on page |
| // discard, pending beacons should be sent out no matter what value its |
| // backgroundTimeout/timeout is. |
| class PendingBeaconTimeoutNoBackForwardCacheBrowserTest |
| : public PendingBeaconTimeoutBrowserTestBase, |
| public testing::WithParamInterface<TestTimeoutType> { |
| protected: |
| const FeaturesType& GetEnabledFeatures() override { |
| static const FeaturesType enabled_features = { |
| {blink::features::kPendingBeaconAPI, {{"send_on_navigation", "true"}}}, |
| {features::kBackForwardCache, {{"cache_size", "0"}}}}; |
| return enabled_features; |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| PendingBeaconTimeoutNoBackForwardCacheBrowserTest, |
| testing::ValuesIn<std::vector<TestTimeoutType>>({ |
| {"LongTimeout", 600000}, |
| {"OneSecondTimeout", 1000}, |
| {"ShortTimeout", 1}, |
| {"NoTimeout", 0}, |
| {"DefaultTimeout", -1}, // default. |
| {"NegativeTimeout", -600000}, // behaves the same as default. |
| }), |
| [](const testing::TestParamInfo<TestTimeoutType>& info) { |
| return info.param.test_case_name; |
| }); |
| |
| IN_PROC_BROWSER_TEST_P(PendingBeaconTimeoutNoBackForwardCacheBrowserTest, |
| SendOnPageDiscardNotUsingBackgroundTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with various backgroundTimeout, which should all |
| // be sent on page A discard. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: $2}); |
| )", |
| kBeaconEndpoint, GetParam().timeout)); |
| ASSERT_TRUE(WaitUntilPreviousDocumentDeleted()); |
| |
| // The beacon should have been sent out after the page is gone. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(PendingBeaconTimeoutNoBackForwardCacheBrowserTest, |
| SendOnPageDiscardNotUsingTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with various timeout, which should all be sent on |
| // page A discard. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {timeout: $2}); |
| )", |
| kBeaconEndpoint, GetParam().timeout)); |
| ASSERT_TRUE(WaitUntilPreviousDocumentDeleted()); |
| |
| // The beacon should have been sent out after the page is gone. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // Tests to cover PendingBeacon's backgroundTimeout behaviors. |
| // Setting a long `PendingBeaconMaxBackgroundTimeoutInMs` (1min) > BFCache |
| // timeout (5s) so that beacon sending cannot be caused by reaching max |
| // background timeout limit but only by BFCache eviction if backgroundTimeout |
| // set >= 5s. |
| class PendingBeaconBackgroundTimeoutBrowserTest |
| : public PendingBeaconTimeoutBrowserTestBase { |
| protected: |
| const FeaturesType& GetEnabledFeatures() override { |
| static const FeaturesType enabled_features = { |
| {blink::features::kPendingBeaconAPI, |
| {{"PendingBeaconMaxBackgroundTimeoutInMs", "60000"}, |
| // Don't force sending out beacons on pagehide. |
| {"send_on_navigation", "false"}}}, |
| {features::kBackForwardCache, |
| {{"TimeToLiveInBackForwardCacheInSeconds", "5"}}}, |
| // Forces BFCache to work in low memory device. |
| {features::kBackForwardCacheMemoryControls, |
| {{"memory_threshold_for_back_forward_cache_in_mb", "0"}}}}; |
| return enabled_features; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconBackgroundTimeoutBrowserTest, |
| SendOnHiddenAfterNavigation) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with 0s backgroundTimeout. |
| // It should be sent out right on entering `hidden` state after navigating |
| // away from A. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: 0}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconBackgroundTimeoutBrowserTest, |
| SendOnBackgroundTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with backgroundTimeout (1s) < BFCache TTL (5s). |
| // The beacon should be sent out on entering `hidden` state but before |
| // page deletion. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: 1000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // When backgroundTimeout is set, its timer resets every time when the page |
| // becomes visible if it has not yet expired. |
| IN_PROC_BROWSER_TEST_F( |
| PendingBeaconBackgroundTimeoutBrowserTest, |
| NotSendWhenPageIsRestoredBeforeBackgroundTimeoutExpires) { |
| const size_t total_beacon = 0; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with backgroundTimeout (3s) < BFCache TTL (5s). |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: 3000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| // Navigate back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The page A becomes visible again, so backgroundTimeout timer should stop. |
| ASSERT_THAT(current_document(), IsFrameVisible()); |
| |
| // Verify that beacon is not sent. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconBackgroundTimeoutBrowserTest, |
| SendOnBackForwardCacheEviction) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with backgroundTimeout (8s) > BFCache TTL (5s) |
| // The beacon should be sent out on page deletion. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: 8000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_TRUE(previous_document().WaitUntilRenderFrameDeleted()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconBackgroundTimeoutBrowserTest, |
| SendMultipleOnBackgroundTimeout) { |
| const size_t total_beacon = 5; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p1 = new PendingGetBeacon($1, {backgroundTimeout: 200}); |
| let p2 = new PendingGetBeacon($1, {backgroundTimeout: 100}); |
| let p3 = new PendingGetBeacon($1, {backgroundTimeout: 500}); |
| let p4 = new PendingGetBeacon($1, {backgroundTimeout: 700}); |
| let p5 = new PendingGetBeacon($1, {backgroundTimeout: 300}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // Tests to cover PendingBeacon's timeout behaviors. |
| // Sets `BeaconTimeToLiveInMs` (10s) > BFCache timeout (5s) to also cover beacon |
| // sending on page eviction. |
| using PendingBeaconTimeoutBrowserTest = |
| PendingBeaconBackgroundTimeoutBrowserTest; |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconTimeoutBrowserTest, SendOnZeroTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with 0s timeout. It should be sent out right away |
| // (without the page entering 'hidden' state). |
| RunScriptInA(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {timeout: 0}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(current_document(), IsFrameVisible()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // When timeout is set, it's not relevant whether the page is hidden or not. |
| IN_PROC_BROWSER_TEST_F(PendingBeaconTimeoutBrowserTest, |
| SendOnTimeoutWhenPageIsHidden) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with a timeout which should expire when the page A |
| // is still hidden. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {timeout: 1000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| // Verify that beacon is sent. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // When timeout is set, it's not relevant whether the page is visible or not. |
| IN_PROC_BROWSER_TEST_F(PendingBeaconTimeoutBrowserTest, |
| SendOnTimeoutWhenPageIsVisible) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with a timeout longer enough such that page can |
| // experience visible -> hidden -> visible. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {timeout: 4000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| // beacon is not yet sent. |
| ASSERT_EQ(sent_beacon_count(), 0u); |
| |
| // Navigate back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The page A becomes visible again, but timeout timer never stops. |
| ASSERT_THAT(current_document(), IsFrameVisible()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| // Verify that beacon is sent. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconTimeoutBrowserTest, SendOnShorterTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with long (5s) timeout. And then quickly updates |
| // to a very short (0.01s) timeout. The beacon should be sent out right away. |
| RunScriptInA(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {timeout: 5000}); |
| p.timeout = 10; |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(current_document(), IsFrameVisible()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconTimeoutBrowserTest, SendOnlyOnce) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon which should be sent out right way. |
| // But it won't be sent out twice. |
| RunScriptInA(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {timeout: 0}); |
| p.timeout = 1; |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(current_document(), IsFrameVisible()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconTimeoutBrowserTest, SendMultipleOnTimeout) { |
| const size_t total_beacon = 5; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| RunScriptInA(JsReplace(R"( |
| let p1 = new PendingGetBeacon($1, {timeout: 200}); |
| let p2 = new PendingGetBeacon($1, {timeout: 100}); |
| let p3 = new PendingGetBeacon($1, {timeout: 500}); |
| let p4 = new PendingGetBeacon($1, {timeout: 700}); |
| let p5 = new PendingGetBeacon($1, {timeout: 300}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(current_document(), IsFrameVisible()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // Tests to cover PendingBeacon's backgroundTimeout & timeout mutual behaviors. |
| // Sets a long BFCache timeout (1min) so that beacon won't be sent out due to |
| // page eviction. |
| class PendingBeaconMutualTimeoutWithLongBackForwardCacheTTLBrowserTest |
| : public PendingBeaconWithBackForwardCacheMetricsBrowserTestBase { |
| protected: |
| const FeaturesType& GetEnabledFeatures() override { |
| static const FeaturesType enabled_features = { |
| {blink::features::kPendingBeaconAPI, |
| {// Don't force sending out beacons on pagehide. |
| {"send_on_navigation", "false"}}}, |
| {features::kBackForwardCache, |
| {{"TimeToLiveInBackForwardCacheInSeconds", "60"}}}, |
| // Forces BFCache to work in low memory device. |
| {features::kBackForwardCacheMemoryControls, |
| {{"memory_threshold_for_back_forward_cache_in_mb", "0"}}}}; |
| return enabled_features; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F( |
| PendingBeaconMutualTimeoutWithLongBackForwardCacheTTLBrowserTest, |
| NotSendWhenPageIsRestoredBeforeBeingEvictedFromBackForwardCache) { |
| const size_t total_beacon = 0; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with default backgroundTimeout & timeout. |
| // It should not be sent out as long as the page is alive (not evicted from |
| // BackForwardCache). |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| // Navigate back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The same page A is still alive. |
| ExpectRestored(FROM_HERE); |
| |
| // Verify that beacon is not sent. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // When both backgroundTimeout & timeout is set, whichever expires earlier will |
| // trigger beacon sending (part 1). |
| IN_PROC_BROWSER_TEST_F( |
| PendingBeaconMutualTimeoutWithLongBackForwardCacheTTLBrowserTest, |
| SendOnEarlierTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with long backgroundTimeout (60s) & short |
| // timeout (1s). |
| // The shorter one, i.e. timeout, should be reachable such that the beacon |
| // can be sent before this test case times out. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: 60000, timeout: 1000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| // Verify that beacon is sent. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // When both backgroundTimeout & timeout is set, whichever expires earlier will |
| // trigger beacon sending (part 2). |
| IN_PROC_BROWSER_TEST_F( |
| PendingBeaconMutualTimeoutWithLongBackForwardCacheTTLBrowserTest, |
| SendOnEarlierBackgroundTimeout) { |
| const size_t total_beacon = 1; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates a pending beacon with short backgroundTimeout (1s) & long |
| // timeout (60s). |
| // The shorter one, i.e. backgroundTimeout, should be reachable such that the |
| // beacon can be sent before this test case times out. |
| RunScriptInANavigateToB(JsReplace(R"( |
| let p = new PendingGetBeacon($1, {backgroundTimeout: 1000, timeout: 60000}); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| WaitForAllBeaconsSent(total_beacon); |
| // Verify that beacon is sent. |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| // Tests to cover PendingBeacon's behaviors when enabled forced sending on |
| // pagehide event. |
| // |
| // Setting a long `PendingBeaconMaxBackgroundTimeoutInMs` (1min), and a long |
| // BFCache timeout (1min) so that beacon sending cannot be caused by reaching |
| // max background timeout limit, and cannot be caused by BFCache eviction. |
| class PendingBeaconSendOnPagehideBrowserTest |
| : public PendingBeaconWithBackForwardCacheMetricsBrowserTestBase { |
| protected: |
| const FeaturesType& GetEnabledFeatures() override { |
| static const FeaturesType enabled_features = { |
| {blink::features::kPendingBeaconAPI, |
| {{"PendingBeaconMaxBackgroundTimeoutInMs", "60000"}, |
| {"send_on_navigation", "true"}}}, |
| {features::kBackForwardCache, |
| {{"TimeToLiveInBackForwardCacheInSeconds", "60"}}}, |
| // Forces BFCache to work in low memory device. |
| {features::kBackForwardCacheMemoryControls, |
| {{"memory_threshold_for_back_forward_cache_in_mb", "0"}}}}; |
| return enabled_features; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconSendOnPagehideBrowserTest, |
| SendOnPagehideWhenPageIsPersisted) { |
| const size_t total_beacon = 3; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates 3 pending beacons with default backgroundTimeout & timeout. |
| // They should be sent out on transitioning to pagehide event. |
| RunScriptInANavigateToB(JsReplace(R"( |
| document.title = ''; |
| let p1 = new PendingGetBeacon($1); |
| let p2 = new PendingPostBeacon($1); |
| let p3 = new PendingGetBeacon($1); |
| window.addEventListener('pagehide', (e) => { |
| document.title = e.persisted + '/' + p1.pending + '/' + p2.pending + |
| '/' + p3.pending; |
| }); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| // Navigate back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The same page A is still alive. |
| ExpectRestored(FROM_HERE); |
| // All beacons should have been sent out before previous pagehide. |
| std::u16string expected_title = u"true/false/false/false"; |
| TitleWatcher title_watcher(web_contents(), expected_title); |
| EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconSendOnPagehideBrowserTest, |
| SendOnPagehideBeforeBackgroundTimeout) { |
| const size_t total_beacon = 3; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates 3 pending beacons with long backgroundTimeout < BFCache TTL (1min). |
| // They should be sent out on transitioning to pagehide but before the end of |
| // backgroundTimeout and before BFCache TTL. |
| RunScriptInANavigateToB(JsReplace(R"( |
| document.title = ''; |
| let p1 = new PendingGetBeacon($1, {backgroundTimeout: 20000}); |
| let p2 = new PendingPostBeacon($1, {backgroundTimeout: 15000}); |
| let p3 = new PendingGetBeacon($1, {backgroundTimeout: 10000}); |
| window.addEventListener('pagehide', (e) => { |
| document.title = e.persisted + '/' + p1.pending + '/' + p2.pending + |
| '/' + p3.pending; |
| }); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| // Navigate back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The same page A is still alive. |
| ExpectRestored(FROM_HERE); |
| // All beacons should have been sent out. |
| std::u16string expected_title = u"true/false/false/false"; |
| TitleWatcher title_watcher(web_contents(), expected_title); |
| EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PendingBeaconSendOnPagehideBrowserTest, |
| SendOnPagehideBeforeTimeout) { |
| const size_t total_beacon = 3; |
| RegisterBeaconRequestMonitor(total_beacon); |
| |
| // Creates 3 pending beacons with long timeout < BFCache TTL (1min). |
| // They should be sent out on transitioning to pagehide but before the end of |
| // timeout and before BFCache TTL. |
| RunScriptInANavigateToB(JsReplace(R"( |
| document.title = ''; |
| let p1 = new PendingGetBeacon($1, {timeout: 20000}); |
| let p2 = new PendingPostBeacon($1, {timeout: 10000}); |
| let p3 = new PendingGetBeacon($1, {timeout: 15000}); |
| window.addEventListener('pagehide', (e) => { |
| document.title = e.persisted + '/' + p1.pending + '/' + p2.pending + |
| '/' + p3.pending; |
| }); |
| )", |
| kBeaconEndpoint)); |
| ASSERT_THAT(previous_document(), IsFrameHidden()); |
| |
| // Navigate back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The same page A is still alive. |
| ExpectRestored(FROM_HERE); |
| // All beacons should have been sent out. |
| std::u16string expected_title = u"true/false/false/false"; |
| TitleWatcher title_watcher(web_contents(), expected_title); |
| EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); |
| EXPECT_EQ(sent_beacon_count(), total_beacon); |
| } |
| |
| } // namespace content |