| // Copyright 2021 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/containers/contains.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/threading/platform_thread.h" | 
 | #include "base/time/time.h" | 
 | #include "build/build_config.h" | 
 | #include "content/browser/back_forward_cache_browsertest.h" | 
 | #include "content/browser/bluetooth/bluetooth_adapter_factory_wrapper.h" | 
 | #include "content/browser/bluetooth/test/mock_bluetooth_delegate.h" | 
 | #include "content/browser/browser_interface_binders.h" | 
 | #include "content/browser/generic_sensor/frame_sensor_provider_proxy.h" | 
 | #include "content/browser/generic_sensor/web_contents_sensor_provider_proxy.h" | 
 | #include "content/browser/hid/hid_test_utils.h" | 
 | #include "content/browser/presentation/presentation_test_utils.h" | 
 | #include "content/browser/renderer_host/back_forward_cache_disable.h" | 
 | #include "content/browser/renderer_host/media/media_devices_dispatcher_host.h" | 
 | #include "content/browser/renderer_host/render_frame_host_impl.h" | 
 | #include "content/browser/web_contents/web_contents_impl.h" | 
 | #include "content/browser/worker_host/dedicated_worker_hosts_for_document.h" | 
 | #include "content/public/browser/disallow_activation_reason.h" | 
 | #include "content/public/browser/hid_delegate.h" | 
 | #include "content/public/browser/media_session.h" | 
 | #include "content/public/browser/payment_app_provider.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_content_browser_client.h" | 
 | #include "content/public/test/content_browser_test_utils.h" | 
 | #include "content/public/test/media_start_stop_observer.h" | 
 | #include "content/public/test/test_navigation_observer.h" | 
 | #include "content/public/test/test_utils.h" | 
 | #include "content/public/test/web_transport_simple_test_server.h" | 
 | #include "content/shell/browser/shell.h" | 
 | #include "device/bluetooth/test/mock_bluetooth_adapter.h" | 
 | #include "net/test/embedded_test_server/controllable_http_response.h" | 
 | #include "net/test/embedded_test_server/embedded_test_server.h" | 
 | #include "net/test/spawned_test_server/spawned_test_server.h" | 
 | #include "net/test/test_data_directory.h" | 
 | #include "services/device/public/cpp/test/fake_hid_manager.h" | 
 | #include "services/device/public/cpp/test/fake_sensor_and_provider.h" | 
 | #include "services/device/public/cpp/test/scoped_geolocation_overrider.h" | 
 | #include "services/device/public/mojom/vibration_manager.mojom.h" | 
 | #include "services/service_manager/public/cpp/interface_provider.h" | 
 | #include "third_party/blink/public/common/features.h" | 
 | #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h" | 
 | #include "third_party/blink/public/mojom/app_banner/app_banner.mojom.h" | 
 | #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h" | 
 | #include "ui/base/idle/idle_time_provider.h" | 
 | #include "ui/base/test/idle_test_utils.h" | 
 |  | 
 | // This file contains back-/forward-cache tests for web-platform features and | 
 | // APIs. It was forked from | 
 | // https://source.chromium.org/chromium/chromium/src/+/main:content/browser/back_forward_cache_browsertest.cc;drc=1288c1bd6a81785cd85b965d61820a7cd87a0e9c | 
 | // | 
 | // When adding tests for new features please also add WPTs. See | 
 | // third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/README.md | 
 |  | 
 | namespace content { | 
 |  | 
 | using ::testing::_; | 
 | using ::testing::Each; | 
 | using ::testing::ElementsAre; | 
 | using ::testing::Not; | 
 | using ::testing::Return; | 
 | using ::testing::UnorderedElementsAreArray; | 
 |  | 
 | using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        PageWithDedicatedWorkerCachedOrNot) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_dedicated_worker.html"))); | 
 |   ASSERT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise")); | 
 |   RenderFrameHostWrapper rfh(current_frame_host()); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // Go back | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Check the outcome. | 
 |   EXPECT_EQ(rfh.get(), current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | class BackForwardCacheWithDedicatedWorkerBrowserTest | 
 |     : public BackForwardCacheBrowserTest { | 
 |  public: | 
 |   const int kMaxBufferedBytesPerProcess = 10000; | 
 |   const base::TimeDelta kGracePeriodToFinishLoading = base::Seconds(5); | 
 |  | 
 |   BackForwardCacheWithDedicatedWorkerBrowserTest() { server_.Start(); } | 
 |  | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     // Disable the feature to test eviction for dedicated worker. | 
 |     DisableFeature( | 
 |         blink::features::kAllowDatapipeDrainedAsBytesConsumerInBFCache); | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |     feature_list_.InitWithFeaturesAndParameters( | 
 |         {{blink::features::kLoadingTasksUnfreezable, | 
 |           {{"max_buffered_bytes_per_process", | 
 |             base::NumberToString(kMaxBufferedBytesPerProcess)}, | 
 |            {"grace_period_to_finish_loading_in_seconds", | 
 |             base::NumberToString(kGracePeriodToFinishLoading.InSeconds())}}}}, | 
 |         {}); | 
 |  | 
 |     server_.SetUpCommandLine(command_line); | 
 |   } | 
 |  | 
 |   int port() const { return server_.server_address().port(); } | 
 |  | 
 |   int CountWorkerClients(RenderFrameHostImpl* rfh) { | 
 |     return EvalJs(rfh, JsReplace(R"( | 
 |       new Promise(async (resolve) => { | 
 |         const resp = await fetch('/service_worker/count_worker_clients'); | 
 |         resolve(parseInt(await resp.text(), 10)); | 
 |       }); | 
 |     )")) | 
 |         .ExtractInt(); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList feature_list_; | 
 |   WebTransportSimpleTestServer server_; | 
 | }; | 
 |  | 
 | // Confirms that a page using a dedicated worker is cached. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        CacheWithDedicatedWorker) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), | 
 |       https_server()->GetURL( | 
 |           "a.test", "/back_forward_cache/page_with_dedicated_worker.html"))); | 
 |   EXPECT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise")); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   // Go back to the original page. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Confirms that an active page using a dedicated worker that calls | 
 | // importScripts won't trigger an eviction IPC, causing the page to reload. | 
 | // Regression test for https://crbug.com/1305041. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     PageWithDedicatedWorkerAndImportScriptsWontTriggerReload) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), https_server()->GetURL( | 
 |                    "a.test", | 
 |                    "/back_forward_cache/" | 
 |                    "page_with_dedicated_worker_and_importscripts.html"))); | 
 |   // Wait until the importScripts() call finished running. | 
 |   EXPECT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise")); | 
 |  | 
 |   // If the importScripts() call triggered an eviction, a reload will be | 
 |   // triggered due to the "evict after docment is restored" will be hit, as the | 
 |   // page is not in back/forward cache. | 
 |   EXPECT_FALSE( | 
 |       web_contents()->GetPrimaryFrameTree().root()->navigation_request()); | 
 | } | 
 |  | 
 | // Confirms that a page using a dedicated worker with WebTransport is not | 
 | // cached. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        DoNotCacheWithDedicatedWorkerWithWebTransport) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), https_server()->GetURL( | 
 |                    "a.test", | 
 |                    "/back_forward_cache/" | 
 |                    "page_with_dedicated_worker_and_webtransport.html"))); | 
 |   // Open a WebTransport. | 
 |   EXPECT_EQ("opened", | 
 |             EvalJs(current_frame_host(), | 
 |                    JsReplace("window.testOpenWebTransport($1);", port()))); | 
 |   RenderFrameDeletedObserver delete_observer_rfh(current_frame_host()); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |   delete_observer_rfh.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to the original page. The page was not cached as the worker used | 
 |   // WebTransport. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | // Confirms that a page using a dedicated worker with a closed WebTransport is | 
 | // cached as WebTransport is not a sticky feature. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        CacheWithDedicatedWorkerWithWebTransportClosed) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), https_server()->GetURL( | 
 |                    "a.test", | 
 |                    "/back_forward_cache/" | 
 |                    "page_with_dedicated_worker_and_webtransport.html"))); | 
 |   // Open and close a WebTransport. | 
 |   EXPECT_EQ("opened", | 
 |             EvalJs(current_frame_host(), | 
 |                    JsReplace("window.testOpenWebTransport($1);", port()))); | 
 |   EXPECT_EQ("closed", | 
 |             EvalJs(current_frame_host(), "window.testCloseWebTransport();")); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   // Go back to the original page. The page was cached. Even though WebTransport | 
 |   // is used once, the page is eligible for back-forward cache as the feature is | 
 |   // not sticky. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // TODO(crbug.com/40823301): Flaky on Linux. | 
 | #if BUILDFLAG(IS_LINUX) | 
 | #define MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBlockingFeature \ | 
 |   DISABLED_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBlockingFeature | 
 | #else | 
 | #define MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBlockingFeature \ | 
 |   DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBlockingFeature | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBlockingFeature) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), https_server()->GetURL( | 
 |                    "a.test", | 
 |                    "/back_forward_cache/" | 
 |                    "page_with_dedicated_worker_and_webtransport.html"))); | 
 |  | 
 |   // Open a WebTransport in the dedicated worker. | 
 |   EXPECT_EQ("opened", | 
 |             EvalJs(current_frame_host(), | 
 |                    JsReplace("window.testOpenWebTransport($1);", port()))); | 
 |   // testOpenWebTransport sends the IPC (BackForwardCacheController. | 
 |   // DidChangeBackForwardCacheDisablingFeatures) from a renderer. Run a script | 
 |   // to wait for the IPC reaching to the browser. | 
 |   EXPECT_EQ(42, EvalJs(current_frame_host(), "42;")); | 
 |   EXPECT_TRUE( | 
 |       DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument( | 
 |           current_frame_host()) | 
 |           ->GetBackForwardCacheDisablingFeatures() | 
 |           .HasAll( | 
 |               {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport})); | 
 |  | 
 |   // Use a blocking feature in the frame. | 
 |   EXPECT_TRUE(ExecJs(current_frame_host(), kBlockingScript)); | 
 |   RenderFrameDeletedObserver delete_observer_rfh(current_frame_host()); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |   delete_observer_rfh.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to the original page. The page was not cached due to WebTransport | 
 |   // and a broadcast channel, which came from the dedicated worker and the frame | 
 |   // respectively. Confirm both are recorded. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport, | 
 |        kBlockingReasonEnum}, | 
 |       {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // TODO(crbug.com/40821593): Disabled due to being flaky. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     DISABLED_DoNotCacheWithDedicatedWorkerWithClosedWebTransportAndDocumentWithBroadcastChannel) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), https_server()->GetURL( | 
 |                    "a.test", | 
 |                    "/back_forward_cache/" | 
 |                    "page_with_dedicated_worker_and_webtransport.html"))); | 
 |  | 
 |   // Open and close a WebTransport in the dedicated worker. | 
 |   EXPECT_EQ("opened", | 
 |             EvalJs(current_frame_host(), | 
 |                    JsReplace("window.testOpenWebTransport($1);", port()))); | 
 |   // testOpenWebTransport sends the IPC (BackForwardCacheController. | 
 |   // DidChangeBackForwardCacheDisablingFeatures) from a renderer. Run a script | 
 |   // to wait for the IPC reaching to the browser. | 
 |   EXPECT_EQ(42, EvalJs(current_frame_host(), "42;")); | 
 |   EXPECT_TRUE( | 
 |       DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument( | 
 |           current_frame_host()) | 
 |           ->GetBackForwardCacheDisablingFeatures() | 
 |           .HasAll( | 
 |               {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport})); | 
 |  | 
 |   EXPECT_EQ("closed", | 
 |             EvalJs(current_frame_host(), | 
 |                    JsReplace("window.testCloseWebTransport($1);", port()))); | 
 |   // testOpenWebTransport sends the IPC (BackForwardCacheController. | 
 |   // DidChangeBackForwardCacheDisablingFeatures) from a renderer. Run a script | 
 |   // to wait for the IPC reaching to the browser. | 
 |   EXPECT_EQ(42, EvalJs(current_frame_host(), "42;")); | 
 |   EXPECT_TRUE(DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument( | 
 |                   current_frame_host()) | 
 |                   ->GetBackForwardCacheDisablingFeatures() | 
 |                   .empty()); | 
 |  | 
 |   // Use a broadcast channel in the frame. | 
 |   EXPECT_TRUE(ExecJs(current_frame_host(), | 
 |                      "window.foo = new BroadcastChannel('foo');")); | 
 |   RenderFrameDeletedObserver delete_observer_rfh(current_frame_host()); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |   delete_observer_rfh.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to the original page. The page was not cached due to a broadcast | 
 |   // channel, which came from the frame. WebTransport was used once in the | 
 |   // dedicated worker but was closed, then this doesn't affect the cache usage. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {}, | 
 |       {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when the page starts fetching in a dedicated worker, goes to | 
 | // BFcache, and then a redirection happens. The cached page should evicted in | 
 | // this case. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        FetchRedirectedWhileStoring) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse fetch1_response(https_server(), | 
 |                                                              "/fetch1"); | 
 |   net::test_server::ControllableHttpResponse fetch2_response(https_server(), | 
 |                                                              "/fetch2"); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // Trigger a fetch in a dedicated worker. | 
 |   std::string worker_script = | 
 |       JsReplace(R"( | 
 |     fetch($1); | 
 |   )", | 
 |                 https_server()->GetURL("a.test", "/fetch1")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                       worker_script))); | 
 |  | 
 |   fetch1_response.WaitForRequest(); | 
 |  | 
 |   // Navigate to B. | 
 |   PageLifecycleStateManagerTestDelegate delegate( | 
 |       rfh_a->render_view_host()->GetPageLifecycleStateManager()); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck()); | 
 |  | 
 |   // Page A is initially stored in the back-forward cache. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Respond the fetch with a redirect. | 
 |   fetch1_response.Send( | 
 |       "HTTP/1.1 302 Moved Temporarily\r\n" | 
 |       "Location: /fetch2\r\n\r\n"); | 
 |   fetch1_response.Done(); | 
 |  | 
 |   // Ensure that the request to /fetch2 was never sent (because the page is | 
 |   // immediately evicted) by checking after 3 seconds. | 
 |   base::RunLoop loop1; | 
 |   base::OneShotTimer timer1; | 
 |   timer1.Start(FROM_HERE, base::Seconds(3), loop1.QuitClosure()); | 
 |   loop1.Run(); | 
 |   EXPECT_EQ(nullptr, fetch2_response.http_request()); | 
 |  | 
 |   // Page A should be evicted from the back-forward cache. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kNetworkRequestRedirected}, {}, {}, {}, | 
 |                     {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when the page starts fetching in a nested dedicated worker, | 
 | // goes to BFcache, and then a redirection happens. The cached page should | 
 | // evicted in this case. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        FetchRedirectedWhileStoring_Nested) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse fetch1_response(https_server(), | 
 |                                                              "/fetch1"); | 
 |   net::test_server::ControllableHttpResponse fetch2_response(https_server(), | 
 |                                                              "/fetch2"); | 
 |  | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // Trigger a fetch in a nested dedicated worker. | 
 |   std::string child_worker_script = | 
 |       JsReplace(R"( | 
 |     fetch($1); | 
 |   )", | 
 |                 https_server()->GetURL("a.test", "/fetch1")); | 
 |   std::string parent_worker_script = JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                                child_worker_script); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |     worker.onmessage = () => { resolve(); } | 
 |   )", | 
 |                                       parent_worker_script))); | 
 |  | 
 |   fetch1_response.WaitForRequest(); | 
 |  | 
 |   // Navigate to B. | 
 |   PageLifecycleStateManagerTestDelegate delegate( | 
 |       rfh_a->render_view_host()->GetPageLifecycleStateManager()); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck()); | 
 |  | 
 |   // Page A is initially stored in the back-forward cache. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Respond the fetch with a redirect. | 
 |   fetch1_response.Send( | 
 |       "HTTP/1.1 302 Moved Temporarily\r\n" | 
 |       "Location: /fetch2\r\n\r\n"); | 
 |   fetch1_response.Done(); | 
 |  | 
 |   // Ensure that the request to /fetch2 was never sent (because the page is | 
 |   // immediately evicted) by checking after 3 seconds. | 
 |   base::RunLoop loop2; | 
 |   base::OneShotTimer timer2; | 
 |   timer2.Start(FROM_HERE, base::Seconds(3), loop2.QuitClosure()); | 
 |   loop2.Run(); | 
 |   EXPECT_EQ(nullptr, fetch2_response.http_request()); | 
 |  | 
 |   // Page A should be evicted from the back-forward cache. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kNetworkRequestRedirected}, {}, {}, {}, | 
 |                     {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when the page starts fetching in a dedicated worker, goes to | 
 | // BFcache, and then the response amount reaches the threshold. The cached page | 
 | // should evicted in this case. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     FetchStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse image_response(https_server(), | 
 |                                                             "/image.png"); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   // Navigate to a page. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html"))); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |  | 
 |   // Trigger a fetch in a dedicated worker. | 
 |   std::string worker_script = | 
 |       JsReplace(R"( | 
 |     fetch($1); | 
 |   )", | 
 |                 https_server()->GetURL("a.test", "/image.png")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                       worker_script))); | 
 |  | 
 |   // Wait for the image request, but don't send anything yet. | 
 |   image_response.WaitForRequest(); | 
 |  | 
 |   // Navigate away. | 
 |   PageLifecycleStateManagerTestDelegate delegate( | 
 |       rfh_a->render_view_host()->GetPageLifecycleStateManager()); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title2.html"))); | 
 |   ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck()); | 
 |  | 
 |   // The worker was still loading when we navigated away, but it's still | 
 |   // eligible for back-forward cache. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |   // Start sending the image response while in the back-forward cache. | 
 |   image_response.Send(net::HTTP_OK, "image/png"); | 
 |   std::string body(kMaxBufferedBytesPerProcess + 1, '*'); | 
 |   image_response.Send(body); | 
 |   image_response.Done(); | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to the first page. We should not restore the page from the | 
 |   // back-forward cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kNetworkExceedsBufferLimit}, {}, {}, {}, | 
 |                     {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when the page starts fetching in a nested dedicated worker, | 
 | // goes to BFcache, and then the response amount reaches the threshold. The | 
 | // cached page should evicted in this case. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     FetchStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit_Nested) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse image_response(https_server(), | 
 |                                                             "/image.png"); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   // Navigate to a page. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html"))); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |  | 
 |   // Trigger a fetch in a nested dedicated worker. | 
 |   std::string child_worker_script = | 
 |       JsReplace(R"( | 
 |     fetch($1); | 
 |   )", | 
 |                 https_server()->GetURL("a.test", "/image.png")); | 
 |   std::string parent_worker_script = JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                                child_worker_script); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                       parent_worker_script))); | 
 |  | 
 |   // Wait for the image request, but don't send anything yet. | 
 |   image_response.WaitForRequest(); | 
 |  | 
 |   // Navigate away. | 
 |   PageLifecycleStateManagerTestDelegate delegate( | 
 |       rfh_a->render_view_host()->GetPageLifecycleStateManager()); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title2.html"))); | 
 |   ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck()); | 
 |   // The worker was still loading when we navigated away, but it's still | 
 |   // eligible for back-forward cache. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |   // Start sending the image response while in the back-forward cache. | 
 |   image_response.Send(net::HTTP_OK, "image/png"); | 
 |   std::string body(kMaxBufferedBytesPerProcess + 1, '*'); | 
 |   image_response.Send(body); | 
 |   image_response.Done(); | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // Go back to the first page. We should not restore the page from the | 
 |   // back-forward cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kNetworkExceedsBufferLimit}, {}, {}, {}, | 
 |                     {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when fetching started in a dedicated worker and the header was | 
 | // received before the page is frozen, but parts of the response body is | 
 | // received when the page is frozen. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        PageWithDrainedDatapipeRequestsForFetchShouldBeEvicted) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse fetch_response(https_server(), | 
 |                                                             "/fetch"); | 
 |  | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call fetch in a dedicated worker before navigating away. | 
 |   std::string worker_script = | 
 |       JsReplace("fetch($1)", https_server()->GetURL("a.test", "/fetch")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                             worker_script))); | 
 |   // Send response header and a piece of the body. This receiving the response | 
 |   // doesn't end (i.e. Done is not called) before navigating away. In this case, | 
 |   // the page will be evicted when the page is frozen. | 
 |   fetch_response.WaitForRequest(); | 
 |   fetch_response.Send(net::HTTP_OK, "text/plain"); | 
 |   fetch_response.Send("body"); | 
 |  | 
 |   // Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back to A. kNetworkRequestDatapipeDrainedAsBytesConsumer is recorded | 
 |   // since receiving the response body started but this didn't end before the | 
 |   // navigation to B. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kNetworkRequestDatapipeDrainedAsBytesConsumer}, {}, | 
 |       {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when fetching started in a nested dedicated worker and the | 
 | // header was received before the page is frozen, but parts of the response body | 
 | // is received when the page is frozen. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     PageWithDrainedDatapipeRequestsForFetchShouldBeEvicted_Nested) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse fetch_response(https_server(), | 
 |                                                             "/fetch"); | 
 |  | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call fetch in a nested dedicated worker before navigating away. | 
 |   std::string child_worker_script = | 
 |       JsReplace("fetch($1)", https_server()->GetURL("a.test", "/fetch")); | 
 |   std::string parent_worker_script = JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                                child_worker_script); | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                             parent_worker_script))); | 
 |   // Send response header and a piece of the body. This receiving the response | 
 |   // doesn't end (i.e. Done is not called) before navigating away. In this case, | 
 |   // the page will be evicted when the page is frozen. | 
 |   fetch_response.WaitForRequest(); | 
 |   fetch_response.Send(net::HTTP_OK, "text/plain"); | 
 |   fetch_response.Send("body"); | 
 |  | 
 |   // Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back to A. kNetworkRequestDatapipeDrainedAsBytesConsumer is recorded | 
 |   // since receiving the response body started but this didn't end before the | 
 |   // navigation to B. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kNetworkRequestDatapipeDrainedAsBytesConsumer}, {}, | 
 |       {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when fetch started in a dedicated worker, but the response | 
 | // never ends after the page is frozen. This should result in an eviction due to | 
 | // timeout. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        ImageStillLoading_ResponseStartedWhileFrozen_Timeout) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse image_response(https_server(), | 
 |                                                             "/image.png"); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call fetch in a dedicated worker before navigating away. | 
 |   std::string worker_script = | 
 |       JsReplace(R"( | 
 |     fetch($1); | 
 |   )", | 
 |                 https_server()->GetURL("a.test", "/image.png")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                             worker_script))); | 
 |  | 
 |   // Wait for the image request, but don't send anything yet. | 
 |   image_response.WaitForRequest(); | 
 |  | 
 |   // Navigate away. | 
 |   PageLifecycleStateManagerTestDelegate delegate( | 
 |       rfh_a->render_view_host()->GetPageLifecycleStateManager()); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck()); | 
 |   // The page was still loading when we navigated away, but it's still eligible | 
 |   // for back-forward cache. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Start sending the image response while in the back-forward cache, but never | 
 |   // finish the request. Eventually the page will get deleted due to network | 
 |   // request timeout. | 
 |   image_response.Send(net::HTTP_OK, "image/png"); | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back to the first page. We should not restore the page from the | 
 |   // back-forward cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kNetworkRequestTimeout}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | // Tests the case when fetch started in a nested dedicated worker, but the | 
 | // response never ends after the page is frozen. This should result in an | 
 | // eviction due to timeout. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |     ImageStillLoading_ResponseStartedWhileFrozen_Timeout_Nested) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   net::test_server::ControllableHttpResponse image_response(https_server(), | 
 |                                                             "/image.png"); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call fetch in a dedicated worker before navigating away. | 
 |   std::string child_worker_script = | 
 |       JsReplace(R"( | 
 |     fetch($1); | 
 |   )", | 
 |                 https_server()->GetURL("a.test", "/image.png")); | 
 |   std::string parent_worker_script = JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                                child_worker_script); | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"( | 
 |     const blob = new Blob([$1]); | 
 |     const blobURL = URL.createObjectURL(blob); | 
 |     const worker = new Worker(blobURL); | 
 |   )", | 
 |                                             parent_worker_script))); | 
 |  | 
 |   // Wait for the image request, but don't send anything yet. | 
 |   image_response.WaitForRequest(); | 
 |  | 
 |   // Navigate away. | 
 |   PageLifecycleStateManagerTestDelegate delegate( | 
 |       rfh_a->render_view_host()->GetPageLifecycleStateManager()); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck()); | 
 |   // The page was still loading when we navigated away, but it's still eligible | 
 |   // for back-forward cache. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Start sending the image response while in the back-forward cache, but never | 
 |   // finish the request. Eventually the page will get deleted due to network | 
 |   // request timeout. | 
 |   image_response.Send(net::HTTP_OK, "image/png"); | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back to the first page. We should not restore the page from the | 
 |   // back-forward cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kNetworkRequestTimeout}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | // Tests that dedicated workers in back/forward cache are not visible to a | 
 | // service worker. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        ServiceWorkerClientMatchAll) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a1(https_server()->GetURL( | 
 |       "a.test", "/service_worker/create_service_worker.html")); | 
 |   GURL url_a2(https_server()->GetURL("a.test", "/service_worker/empty.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a1)); | 
 |   EXPECT_EQ( | 
 |       "DONE", | 
 |       EvalJs(current_frame_host(), | 
 |              "register('/service_worker/fetch_event_worker_clients.js');")); | 
 |  | 
 |   // Reload the page to enable fetch to be hooked by the service worker. | 
 |   web_contents()->GetController().Reload(content::ReloadType::NORMAL, false); | 
 |   EXPECT_TRUE(WaitForLoadStop(web_contents())); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Confirm there is no worker client. | 
 |   EXPECT_EQ(0, CountWorkerClients(rfh_a.get())); | 
 |  | 
 |   // Call fetch in a dedicated worker and verify that we see the expected number | 
 |   // of worker clients. | 
 |   const int kExpectedWorkerClientCount = 1; | 
 |   std::string dedicated_worker_script = JsReplace( | 
 |       R"( | 
 |     (async() => { | 
 |       const response = await fetch($1); | 
 |       postMessage(await response.text()); | 
 |     })(); | 
 |   )", | 
 |       https_server()->GetURL("a.test", "/service_worker/count_worker_clients")); | 
 |   EXPECT_EQ(base::NumberToString(kExpectedWorkerClientCount), | 
 |             EvalJs(rfh_a.get(), JsReplace(R"( | 
 |     new Promise(async (resolve) => { | 
 |       const blobURL = URL.createObjectURL(new Blob([$1])); | 
 |       const dedicatedWorker = new Worker(blobURL); | 
 |       dedicatedWorker.addEventListener('message', e => { | 
 |         resolve(e.data); | 
 |       }); | 
 |     }); | 
 |   )", | 
 |                                           dedicated_worker_script))); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a2)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Confirm that the worker in back/forward cache is invisible from the service | 
 |   // worker. | 
 |   EXPECT_EQ(0, CountWorkerClients(current_frame_host())); | 
 |  | 
 |   // Restore from the back/forward cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(kExpectedWorkerClientCount, | 
 |             CountWorkerClients(current_frame_host())); | 
 | } | 
 |  | 
 | // Tests that dedicated workers, including a nested dedicated workers, in | 
 | // back/forward cache are not visible to a service worker. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        ServiceWorkerClientMatchAll_Nested) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a1(https_server()->GetURL( | 
 |       "a.test", "/service_worker/create_service_worker.html")); | 
 |   GURL url_a2(https_server()->GetURL("a.test", "/service_worker/empty.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a1)); | 
 |   EXPECT_EQ( | 
 |       "DONE", | 
 |       EvalJs(current_frame_host(), | 
 |              "register('/service_worker/fetch_event_worker_clients.js');")); | 
 |  | 
 |   // Reload the page to enable fetch to be hooked by the service worker. | 
 |   web_contents()->GetController().Reload(content::ReloadType::NORMAL, false); | 
 |   EXPECT_TRUE(WaitForLoadStop(web_contents())); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Confirm there is no worker client. | 
 |   EXPECT_EQ(0, CountWorkerClients(rfh_a.get())); | 
 |  | 
 |   // Call fetch in a dedicated worker and verify that we see the expected number | 
 |   // of worker clients. | 
 |   const int kExpectedWorkerClientCount = 2; | 
 |   std::string child_worker_script = JsReplace( | 
 |       R"( | 
 |     (async() => { | 
 |       const response = await fetch($1); | 
 |       postMessage(await response.text()); | 
 |     })(); | 
 |   )", | 
 |       https_server()->GetURL("a.test", "/service_worker/count_worker_clients")); | 
 |   std::string parent_worker_script = JsReplace( | 
 |       R"( | 
 |     const blobURL = URL.createObjectURL(new Blob([$1])); | 
 |     const dedicatedWorker = new Worker(blobURL); | 
 |     dedicatedWorker.addEventListener('message', e => { | 
 |       postMessage(e.data); | 
 |     }); | 
 |   )", | 
 |       child_worker_script); | 
 |   EXPECT_EQ(base::NumberToString(kExpectedWorkerClientCount), | 
 |             EvalJs(rfh_a.get(), JsReplace(R"( | 
 |     new Promise(async (resolve) => { | 
 |       const blobURL = URL.createObjectURL(new Blob([$1])); | 
 |       const dedicatedWorker = new Worker(blobURL); | 
 |       dedicatedWorker.addEventListener('message', e => { | 
 |         resolve(e.data); | 
 |       }); | 
 |     }); | 
 |   )", | 
 |                                           parent_worker_script))); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a2)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Confirm that the worker in back/forward cache is invisible from the service | 
 |   // worker. | 
 |   EXPECT_EQ(0, CountWorkerClients(current_frame_host())); | 
 |  | 
 |   // Restore from the back/forward cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(kExpectedWorkerClientCount, | 
 |             CountWorkerClients(current_frame_host())); | 
 | } | 
 |  | 
 | // Tests that dedicated workers in back/forward cache are not visible to a | 
 | // service worker. This works correctly even if a dedicated worker is not loaded | 
 | // completely when the page is put into back/forward cache, | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, | 
 |                        ServiceWorkerClientMatchAll_LoadWorkerAfterRestoring) { | 
 |   CreateHttpsServer(); | 
 |  | 
 |   // Prepare a controllable HTTP response for a dedicated worker. Use | 
 |   // /service_worker path to match with the service worker's scope. | 
 |   net::test_server::ControllableHttpResponse dedicated_worker_response( | 
 |       https_server(), | 
 |       "/service_worker/dedicated_worker_using_service_worker.js"); | 
 |  | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a1(https_server()->GetURL( | 
 |       "a.test", "/service_worker/create_service_worker.html")); | 
 |   GURL url_a2(https_server()->GetURL("a.test", "/service_worker/empty.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a1)); | 
 |   EXPECT_EQ( | 
 |       "DONE", | 
 |       EvalJs(current_frame_host(), | 
 |              "register('/service_worker/fetch_event_worker_clients.js');")); | 
 |  | 
 |   // Reload the page to enable fetch to be hooked by the service worker. | 
 |   web_contents()->GetController().Reload(content::ReloadType::NORMAL, false); | 
 |   EXPECT_TRUE(WaitForLoadStop(web_contents())); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Confirm there is no worker client. | 
 |   EXPECT_EQ(0, CountWorkerClients(rfh_a.get())); | 
 |  | 
 |   // Start to requet a worker URL. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     window.dedicatedWorkerUsingServiceWorker = new Worker( | 
 |         '/service_worker/dedicated_worker_using_service_worker.js'); | 
 |   )")); | 
 |  | 
 |   dedicated_worker_response.WaitForRequest(); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a2)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Return the dedicated worker script. | 
 |   dedicated_worker_response.Send(net::HTTP_OK, "text/javascript"); | 
 |   dedicated_worker_response.Send(R"( | 
 |     onmessage = e => { | 
 |       postMessage(e.data); | 
 |     }; | 
 |   )"); | 
 |   dedicated_worker_response.Done(); | 
 |  | 
 |   // Confirm that the worker in back/forward cache is invisible from the service | 
 |   // worker. | 
 |   EXPECT_EQ(0, CountWorkerClients(current_frame_host())); | 
 |  | 
 |   // Restore from the back/forward cache. Now the number of client is 1. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // Confirm that the dedicated worker is completely loaded. | 
 |   EXPECT_EQ("foo", EvalJs(current_frame_host(), JsReplace(R"( | 
 |     new Promise(async (resolve) => { | 
 |       window.dedicatedWorkerUsingServiceWorker.onmessage = e => { | 
 |         resolve(e.data); | 
 |       }; | 
 |       window.dedicatedWorkerUsingServiceWorker.postMessage("foo"); | 
 |     }); | 
 |   )"))); | 
 |  | 
 |   EXPECT_EQ(1, CountWorkerClients(current_frame_host())); | 
 | } | 
 |  | 
 | // TODO(crbug.com/40290702): Shared workers are not available on Android. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_PageWithSharedWorkerNotCached \ | 
 |   DISABLED_PageWithSharedWorkerNotCached | 
 | #else | 
 | #define MAYBE_PageWithSharedWorkerNotCached PageWithSharedWorkerNotCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_PageWithSharedWorkerNotCached) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_shared_worker.html"))); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host()); | 
 |  | 
 |   // Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page with the unsupported feature should be deleted (not cached). | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kSharedWorker}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        AllowedFeaturesForSubframesDoNotEvict) { | 
 |   // The main purpose of this test is to check that when a state of a subframe | 
 |   // is updated, CanStoreDocument is still called for the main frame - otherwise | 
 |   // we would always evict the document, even when the feature is allowed as | 
 |   // CanStoreDocument always returns false for non-main frames. | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); | 
 |  | 
 |   // 2) Navigate to C. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_c)); | 
 |  | 
 |   // 3) No-op feature update on a subframe while in cache, should be no-op. | 
 |   ASSERT_FALSE(delete_observer_rfh_b.deleted()); | 
 |   RenderFrameHostImpl::BackForwardCacheBlockingDetails empty_vector; | 
 |   rfh_b->DidChangeBackForwardCacheDisablingFeatures(std::move(empty_vector)); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(current_frame_host(), rfh_a); | 
 |  | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheIfRecordingAudio) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   BackForwardCacheDisabledTester tester; | 
 |  | 
 |   // Navigate to an empty page. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   // Request for audio recording. | 
 |   EXPECT_EQ("success", EvalJs(current_frame_host(), R"( | 
 |     new Promise(resolve => { | 
 |       navigator.mediaDevices.getUserMedia({audio: true}) | 
 |         .then(m => { window.keepaliveMedia = m; resolve("success"); }) | 
 |         .catch(() => { resolve("error"); }); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   RenderFrameDeletedObserver deleted(current_frame_host()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page was still recording audio when we navigated away, so it shouldn't | 
 |   // have been cached. | 
 |   deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // A MediaStreamTrack that's in the live state | 
 |   // will block BFCache. | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kLiveMediaStreamTrack}, {}, | 
 |       {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheIfSubframeRecordingAudio) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   BackForwardCacheDisabledTester tester; | 
 |  | 
 |   // Navigate to a page with an iframe. | 
 |   GURL url(embedded_test_server()->GetURL("/page_with_iframe.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh = current_frame_host(); | 
 |  | 
 |   // Request for audio recording from the subframe. | 
 |   EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"( | 
 |     new Promise(resolve => { | 
 |       navigator.mediaDevices.getUserMedia({audio: true}) | 
 |         .then(m => { resolve("success"); }) | 
 |         .catch(() => { resolve("error"); }); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   RenderFrameDeletedObserver deleted(current_frame_host()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page was still recording audio when we navigated away, so it shouldn't | 
 |   // have been cached. | 
 |   deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // A MediaStreamTrack that's in the live state blocks BFCache. | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kLiveMediaStreamTrack}, {}, | 
 |       {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheIfMediaDeviceSubscribedButDoesCache) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   BackForwardCacheDisabledTester tester; | 
 |  | 
 |   // Navigate to a page with an iframe. | 
 |   GURL url(embedded_test_server()->GetURL("/page_with_iframe.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   RenderFrameHostImpl* rfh = current_frame_host(); | 
 |  | 
 |   EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"( | 
 |     new Promise(resolve => { | 
 |       navigator.mediaDevices.addEventListener( | 
 |           'devicechange', function(event){}); | 
 |       resolve("success"); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   RenderFrameDeletedObserver deleted(current_frame_host()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Ended MediaStreamTrack does not block BFCache. | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Checks that the page is restored from BFCache when it calls | 
 | // mediaDevice.enumerateDevices(). | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        RestoreIfDevicesEnumerated) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   RenderFrameHostWrapper rfh(current_frame_host()); | 
 |  | 
 |   // Use the method enumerateDevices() of MediaDevices API. | 
 |   EXPECT_EQ("success", EvalJs(rfh.get(), R"( | 
 |     navigator.mediaDevices.enumerateDevices().then(() => {return "success"}); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // 3) Go back. MediaDevicesDispatcherHost does not block BFCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Checks that the page is not restored from BFCache when it calls | 
 | // mediaDevice.getDisplayMedia() and still has live MediaStreamTrack. | 
 | // Since mediaDevice.getDisplayMedia() is not supported in Android, the tests | 
 | // can't run on the OS. | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheIfDisplayMediaAccessGranted) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   RenderFrameHostWrapper rfh(current_frame_host()); | 
 |  | 
 |   // Request for video and audio display permission. | 
 |   EXPECT_EQ("success", EvalJs(rfh.get(), R"( | 
 |     new Promise((resolve) => { | 
 |       navigator.mediaDevices.getDisplayMedia({audio: true, video: true}) | 
 |         .then(() => { resolve("success"); }) | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back. A MediaStreamTrack that's in the live state blocks BFCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kLiveMediaStreamTrack}, {}, | 
 |       {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Checks that the page is successfully restored from BFCache after stopping the | 
 | // MediaStreamTrack that was caused by getDisplayMedia(). | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfMediaStreamTrackUsingGetDisplayMediaEnded) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   RenderFrameHostWrapper rfh(current_frame_host()); | 
 |  | 
 |   // Request for video and audio display permission, and stop it. | 
 |   EXPECT_EQ("success", EvalJs(rfh.get(), R"( | 
 |   new Promise((resolve) => { | 
 |     navigator.mediaDevices.getDisplayMedia({ audio: true }) | 
 |       .then((mediaStream) => { | 
 |         mediaStream.getTracks().forEach((track) => track.stop()); | 
 |         resolve("success"); | 
 |       }) | 
 |       .catch((error) => { | 
 |         resolve("error"); | 
 |       }); | 
 |   }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // 3) Go back. an ended MediaStreamTrack doesn't | 
 |   // block BFCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 | #endif  // !BUILDFLAG(IS_ANDROID) | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheIfWebGL) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with WebGL usage | 
 |   GURL url(embedded_test_server()->GetURL( | 
 |       "example.com", "/back_forward_cache/page_with_webgl.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page had an active WebGL context when we navigated away, | 
 |   // but it should be cached. | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Since blink::mojom::HidService binder is not added in | 
 | // content/browser/browser_interface_binders.cc for Android, this test is not | 
 | // applicable for this OS. | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 | class HidBrowserTestContentBrowserClient | 
 |     : public ContentBrowserTestContentBrowserClient { | 
 |  public: | 
 |   MockHidDelegate& delegate() { return delegate_; } | 
 |  | 
 |   // ContentBrowserClient: | 
 |   HidDelegate* GetHidDelegate() override { return &delegate_; } | 
 |  | 
 |  private: | 
 |   testing::NiceMock<MockHidDelegate> delegate_; | 
 | }; | 
 |  | 
 | class BackForwardCacheWebHidTest : public BackForwardCacheBrowserTest { | 
 |  public: | 
 |   void SetUpOnMainThread() override { | 
 |     BackForwardCacheBrowserTest::SetUpOnMainThread(); | 
 |     test_client_ = std::make_unique<HidBrowserTestContentBrowserClient>(); | 
 |     ON_CALL(delegate(), GetHidManager).WillByDefault(Return(&hid_manager_)); | 
 |   } | 
 |  | 
 |   void TearDownOnMainThread() override { | 
 |     BackForwardCacheBrowserTest::TearDownOnMainThread(); | 
 |     test_client_.reset(); | 
 |   } | 
 |  | 
 |   MockHidDelegate& delegate() { return test_client_->delegate(); } | 
 |   device::FakeHidManager* hid_manager() { return &hid_manager_; } | 
 |  | 
 |  private: | 
 |   std::unique_ptr<HidBrowserTestContentBrowserClient> test_client_; | 
 |   device::FakeHidManager hid_manager_; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebHidTest, | 
 |                        DoesNotCacheIfGetDevicesWasCalled) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); | 
 |   RenderFrameHostWrapper rfh_wrapper(current_frame_host()); | 
 |  | 
 |   // Call getDevices to get a list of devices the page is allowed to access. | 
 |   EXPECT_EQ("success", EvalJs(current_frame_host(), R"( | 
 |     new Promise(resolve => { | 
 |       navigator.hid.getDevices() | 
 |         .then(m => { resolve("success"); }) | 
 |         .catch(() => { resolve("error"); }); | 
 |     }); | 
 |   )")); | 
 |   EXPECT_TRUE(current_frame_host()->GetBackForwardCacheDisablingFeatures().Has( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kWebHID)); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page called a WebHID method so it should be deleted. | 
 |   EXPECT_TRUE(rfh_wrapper.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebHID}, {}, | 
 |                     {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebHidTest, | 
 |                        DoesNotCacheIfRequestDeviceWasCalled) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); | 
 |   RenderFrameHostWrapper rfh_wrapper(current_frame_host()); | 
 |  | 
 |   // Call requestDevice to open a permission request dialog. Simulate closing | 
 |   // the dialog without selecting a device. | 
 |   EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(true)); | 
 |   EXPECT_CALL(delegate(), RunChooserInternal).WillOnce([]() { | 
 |     return std::vector<device::mojom::HidDeviceInfoPtr>(); | 
 |   }); | 
 |   EXPECT_TRUE(ExecJs(current_frame_host(), | 
 |                      "navigator.hid.requestDevice({filters: []})")); | 
 |   EXPECT_TRUE(current_frame_host()->GetBackForwardCacheDisablingFeatures().Has( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kWebHID)); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page called a WebHID method so it should be deleted. | 
 |   EXPECT_TRUE(rfh_wrapper.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebHID}, {}, | 
 |                     {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebHidTest, | 
 |                        DoesCacheIfHidAttributeWasAccessed) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); | 
 |   RenderFrameHostWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Access navigator.hid without invoking any WebHID API methods. | 
 |   EXPECT_TRUE(ExecJs(current_frame_host(), "navigator.hid")); | 
 |   EXPECT_FALSE(current_frame_host()->GetBackForwardCacheDisablingFeatures().Has( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kWebHID)); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   ASSERT_TRUE(!rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE( | 
 |       static_cast<RenderFrameHostImpl*>(rfh_a.get())->IsInBackForwardCache()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(current_frame_host(), rfh_a.get()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 | #endif  // !BUILDFLAG(IS_ANDROID) | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        WakeLockReleasedUponEnteringBfcache) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with WakeLock usage. | 
 |   GURL url(https_server()->GetURL( | 
 |       "a.test", "/back_forward_cache/page_with_wakelock.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   // Acquire WakeLock. | 
 |   EXPECT_EQ("DONE", EvalJs(rfh_a, "acquireWakeLock()")); | 
 |   // Make sure that WakeLock is not released yet. | 
 |   EXPECT_FALSE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   shell()->LoadURL(https_server()->GetURL("b.test", "/title1.html")); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page with WakeLock, restored from BackForwardCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(current_frame_host(), rfh_a); | 
 |   EXPECT_TRUE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheWithWebFileSystem) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with WebFileSystem usage. | 
 |   GURL url(embedded_test_server()->GetURL("a.test", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   // Writer a file 'file.txt' with a content 'foo'. | 
 |   EXPECT_EQ("success", EvalJs(rfh_a, R"( | 
 |       new Promise((resolve, reject) => { | 
 |         window.webkitRequestFileSystem( | 
 |           window.TEMPORARY, | 
 |           1024 * 1024, | 
 |           (fs) => { | 
 |             fs.root.getFile('file.txt', {create: true}, (entry) => { | 
 |               entry.createWriter((writer) => { | 
 |                 writer.onwriteend = () => { | 
 |                   resolve('success'); | 
 |                 }; | 
 |                 writer.onerror = reject; | 
 |                 var blob = new Blob(['foo'], {type: 'text/plain'}); | 
 |                 writer.write(blob); | 
 |               }, reject); | 
 |             }, reject); | 
 |           }, reject); | 
 |         }); | 
 |     )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   shell()->LoadURL(embedded_test_server()->GetURL("b.test", "/title1.html")); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // 3) Go back to the page with WebFileSystem. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   // Check the file content is reserved. | 
 |   EXPECT_EQ("foo", EvalJs(rfh_a, R"( | 
 |       new Promise((resolve, reject) => { | 
 |         window.webkitRequestFileSystem( | 
 |           window.TEMPORARY, | 
 |           1024 * 1024, | 
 |           (fs) => { | 
 |             fs.root.getFile('file.txt', {}, (entry) => { | 
 |               entry.file((file) => { | 
 |                 const reader = new FileReader(); | 
 |                 reader.onloadend = (e) => { | 
 |                   resolve(e.target.result); | 
 |                 }; | 
 |                 reader.readAsText(file); | 
 |               }, reject); | 
 |             }, reject); | 
 |           }, reject); | 
 |         }); | 
 |     )")); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | class FakeIdleTimeProvider : public ui::IdleTimeProvider { | 
 |  public: | 
 |   FakeIdleTimeProvider() = default; | 
 |   ~FakeIdleTimeProvider() override = default; | 
 |   FakeIdleTimeProvider(const FakeIdleTimeProvider&) = delete; | 
 |   FakeIdleTimeProvider& operator=(const FakeIdleTimeProvider&) = delete; | 
 |  | 
 |   base::TimeDelta CalculateIdleTime() override { return base::Seconds(0); } | 
 |  | 
 |   bool CheckIdleStateIsLocked() override { return false; } | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIdleManager) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the IdleManager class. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |  | 
 |   ui::test::ScopedIdleProviderForTest scoped_idle_provider( | 
 |       std::make_unique<FakeIdleTimeProvider>()); | 
 |  | 
 |   EXPECT_EQ(42, EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       let idleDetector = new IdleDetector(); | 
 |       await idleDetector.start(); | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // The page uses IdleManager so it should be deleted. | 
 |   deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back and make sure the IdleManager page wasn't in the cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kIdleManager}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheSMSService) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the SMSService. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver rfh_a_deleted(rfh_a); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |     navigator.credentials.get({otp: {transport: ["sms"]}}); | 
 |   )", | 
 |                      EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page uses SMSService so it should be deleted. | 
 |   rfh_a_deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back and make sure the SMSService page wasn't in the cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Note that on certain linux tests, there is occasionally a not restored | 
 |   // reason of kDisableForRenderFrameHostCalled. This is due to the javascript | 
 |   // navigator.credentials.get, which will call on authentication code for linux | 
 |   // but not other operating systems. The authenticator code explicitly invokes | 
 |   // kDisableForRenderFrameHostCalled. This causes flakiness if we check against | 
 |   // all not restored reasons. As a result, we only check for the blocklist | 
 |   // reason. | 
 |   ExpectBlocklistedFeature( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kWebOTPService, FROM_HERE); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | void OnInstallPaymentApp(base::OnceClosure done_callback, | 
 |                          bool* out_success, | 
 |                          bool success) { | 
 |   *out_success = success; | 
 |   std::move(done_callback).Run(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCachePaymentManager) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   GURL service_worker_javascript_file_url = | 
 |       https_server()->GetURL("a.test", "/payments/payment_app.js"); | 
 |   bool success = false; | 
 |   PaymentAppProvider::GetOrCreateForWebContents(shell()->web_contents()) | 
 |       ->InstallPaymentAppForTesting( | 
 |           /*app_icon=*/SkBitmap(), service_worker_javascript_file_url, | 
 |           /*service_worker_scope=*/ | 
 |           service_worker_javascript_file_url.GetWithoutFilename(), | 
 |           /*payment_method_identifier=*/ | 
 |           url::Origin::Create(service_worker_javascript_file_url).Serialize(), | 
 |           base::BindOnce(&OnInstallPaymentApp, run_loop.QuitClosure(), | 
 |                          &success)); | 
 |   run_loop.Run(); | 
 |   ASSERT_TRUE(success); | 
 |  | 
 |   // 1) Navigate to a page which includes PaymentManager functionality. Note | 
 |   // that service workers are used, and therefore we use https server instead of | 
 |   // embedded_server() | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), https_server()->GetURL( | 
 |                    "a.test", "/payments/payment_app_invocation.html"))); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver rfh_a_deleted(rfh_a); | 
 |  | 
 |   // Execute functionality that calls PaymentManager. | 
 |   EXPECT_EQ(42, EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       const registration = await navigator.serviceWorker.getRegistration( | 
 |           '/payments/payment_app.js'); | 
 |       await registration.paymentManager.enableDelegations(['shippingAddress']); | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   // The page uses PaymentManager so it should be deleted. | 
 |   rfh_a_deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager}, {}, {}, | 
 |       {}, FROM_HERE); | 
 |  | 
 |   // Note that on Mac10.10, there is occasionally blocklisting for network | 
 |   // requests (kOutstandingNetworkRequestOthers). This causes flakiness if we | 
 |   // check against all blocklisted features. As a result, we only check for the | 
 |   // blocklist we care about. | 
 |   base::HistogramBase::Sample32 sample = base::HistogramBase::Sample32( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager); | 
 |   std::vector<base::Bucket> blocklist_values = histogram_tester().GetAllSamples( | 
 |       "BackForwardCache.HistoryNavigationOutcome." | 
 |       "BlocklistedFeature"); | 
 |   EXPECT_TRUE(base::Contains(blocklist_values, sample, &base::Bucket::min)); | 
 |  | 
 |   std::vector<base::Bucket> all_sites_blocklist_values = | 
 |       histogram_tester().GetAllSamples( | 
 |           "BackForwardCache.AllSites.HistoryNavigationOutcome." | 
 |           "BlocklistedFeature"); | 
 |  | 
 |   EXPECT_TRUE( | 
 |       base::Contains(all_sites_blocklist_values, sample, &base::Bucket::min)); | 
 | } | 
 |  | 
 | // Pages with acquired keyboard lock should not enter BackForwardCache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheOnKeyboardLock) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the Keyboard lock. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver rfh_a_deleted(rfh_a); | 
 |  | 
 |   AcquireKeyboardLock(rfh_a); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page uses keyboard lock so it should be deleted. | 
 |   rfh_a_deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back and make sure the keyboard lock page wasn't in the cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | // If pages released keyboard lock, they can enter BackForwardCache. It will | 
 | // remain eligible for multiple restores. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfKeyboardLockReleasedMultipleRestores) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the Keyboard lock. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   AcquireKeyboardLock(rfh_a.get()); | 
 |   ReleaseKeyboardLock(rfh_a.get()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   // 3) Go back and page should be restored from BackForwardCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // 4) Go forward and back, the page should be restored from BackForwardCache. | 
 |   ASSERT_TRUE(HistoryGoForward(web_contents())); | 
 |   EXPECT_EQ(rfh_b.get(), current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // If pages previously released the keyboard lock, but acquired it again, they | 
 | // cannot enter BackForwardCache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoNotCacheIfKeyboardLockIsHeldAfterRelease) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the Keyboard lock. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   AcquireKeyboardLock(rfh_a.get()); | 
 |   ReleaseKeyboardLock(rfh_a.get()); | 
 |   AcquireKeyboardLock(rfh_a.get()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page uses keyboard lock so it should be deleted. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back and make sure the keyboard lock page wasn't in the cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | // If pages released keyboard lock before navigation, they can enter | 
 | // BackForwardCache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfKeyboardLockReleased) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the Keyboard lock. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   AcquireKeyboardLock(rfh_a.get()); | 
 |   ReleaseKeyboardLock(rfh_a.get()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // 3) Go back and page should be restored from BackForwardCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | class BackForwardCacheNonStickyDoubleFixBrowserTest | 
 |     : public BackForwardCacheBrowserTest, | 
 |       public testing::WithParamInterface<bool> { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     if (IsBackForwardCacheNonStickyDoubleFixEnabled()) { | 
 |       EnableFeatureAndSetParams(kBackForwardCacheNonStickyDoubleFix, "", ""); | 
 |     } else { | 
 |       DisableFeature(kBackForwardCacheNonStickyDoubleFix); | 
 |     } | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 |  | 
 |   bool IsBackForwardCacheNonStickyDoubleFixEnabled() { return GetParam(); } | 
 | }; | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          BackForwardCacheNonStickyDoubleFixBrowserTest, | 
 |                          testing::Bool()); | 
 |  | 
 | // If pages released keyboard lock during pagehide, they can enter | 
 | // BackForwardCache. This also covers the case of entering BFCache for a | 
 | // second time. KeyboardLock is a good feature to use as it will always | 
 | // block BFCache. See https://crbug.com/360183659 | 
 | IN_PROC_BROWSER_TEST_P(BackForwardCacheNonStickyDoubleFixBrowserTest, | 
 |                        CacheIfKeyboardLockReleasedInPagehide) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // Navigate to a page and start using the Keyboard lock. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   AcquireKeyboardLock(rfh_a.get()); | 
 |   // Register a pagehide handler to release keyboard lock. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     window.onpagehide = function(e) { | 
 |       new Promise(resolve => { | 
 |         navigator.keyboard.unlock(); | 
 |         resolve(); | 
 |       }); | 
 |     }; | 
 |   )")); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // Go back and page should be restored from BackForwardCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // Acquire the lock again. | 
 |   AcquireKeyboardLock(rfh_a.get()); | 
 |  | 
 |   // Navigate away again. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // Go back again. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   if (IsBackForwardCacheNonStickyDoubleFixEnabled()) { | 
 |     // The page should be restored from BackForwardCache. | 
 |     ExpectRestored(FROM_HERE); | 
 |   } else { | 
 |     // The page should not be restored from BackForwardCache. | 
 |     ExpectNotRestored( | 
 |         {NotRestoredReason::kBlocklistedFeatures}, | 
 |         {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, | 
 |         {}, FROM_HERE); | 
 |   } | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheWithDummyStickyFeature) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page and start using the dummy sticky feature. | 
 |   GURL url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting(); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page uses the dummy sticky feature so it should be deleted. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back and make sure the dummy sticky feature page wasn't in the cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kDummy}, {}, | 
 |                     {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests which blocklisted features are tracked in the metrics when we used | 
 | // blocklisted features (sticky and non-sticky) and do a browser-initiated | 
 | // cross-site navigation. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        BlocklistedFeaturesTracking_CrossSite_BrowserInitiated) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a(https_server()->GetURL("a.test", kBlockingPagePath)); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title2.html")); | 
 |   // 1) Navigate to a page. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   scoped_refptr<SiteInstanceImpl> site_instance_a = | 
 |       static_cast<SiteInstanceImpl*>(rfh_a->GetSiteInstance()); | 
 |   RenderFrameDeletedObserver rfh_a_deleted(rfh_a); | 
 |  | 
 |   // 2) Use a dummy sticky blocklisted feature. | 
 |   rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting(); | 
 |  | 
 |   // 3) Navigate cross-site, browser-initiated. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted features. Because we used sticky blocklisted features, we will | 
 |   // not do a proactive BrowsingInstance swap, however the RFH will still change | 
 |   // and get deleted. | 
 |   rfh_a_deleted.WaitUntilDeleted(); | 
 |   EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance())); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Both sticky and non-sticky features are recorded. | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kDummy, | 
 |                      kBlockingReasonEnum}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests which blocklisted features are tracked in the metrics when we used | 
 | // blocklisted features (sticky and non-sticky) and do a renderer-initiated | 
 | // cross-site navigation. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     BlocklistedFeaturesTracking_CrossSite_RendererInitiated) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a(https_server()->GetURL("a.test", kBlockingPagePath)); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title2.html")); | 
 |  | 
 |   // 1) Navigate to a page. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   scoped_refptr<SiteInstanceImpl> site_instance_a = | 
 |       static_cast<SiteInstanceImpl*>(rfh_a->GetSiteInstance()); | 
 |  | 
 |   // 2) Use a Dummy sticky blocklisted feature. | 
 |   rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting(); | 
 |  | 
 |   // 3) Navigate cross-site, renderer-inititated. | 
 |   ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b)); | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted features. Because we used sticky blocklisted features, we will | 
 |   // not do a proactive BrowsingInstance swap. | 
 |   EXPECT_TRUE(site_instance_a->IsRelatedSiteInstance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance())); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Both sticky and non-sticky features are recorded. | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures, | 
 |        NotRestoredReason::kBrowsingInstanceNotSwapped}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kDummy, | 
 |        kBlockingReasonEnum}, | 
 |       {ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {}, {}, | 
 |       FROM_HERE); | 
 |  | 
 |   ASSERT_TRUE(HistoryGoForward(web_contents())); | 
 |  | 
 |   ExpectBrowsingInstanceNotSwappedReason( | 
 |       ShouldSwapBrowsingInstance::kNo_AlreadyHasMatchingBrowsingInstance, | 
 |       FROM_HERE); | 
 |  | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   ExpectBrowsingInstanceNotSwappedReason( | 
 |       ShouldSwapBrowsingInstance::kNo_AlreadyHasMatchingBrowsingInstance, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | // Tests which blocklisted features are tracked in the metrics when we used | 
 | // blocklisted features (sticky and non-sticky) and do a same-site navigation. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        BlocklistedFeaturesTracking_SameSite) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_1(https_server()->GetURL(kBlockingPagePath)); | 
 |   GURL url_2(https_server()->GetURL("/title2.html")); | 
 |  | 
 |   // 1) Navigate to a page. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_1)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   RenderFrameHostImpl* rfh_1 = current_frame_host(); | 
 |   scoped_refptr<SiteInstanceImpl> site_instance_1 = | 
 |       static_cast<SiteInstanceImpl*>(rfh_1->GetSiteInstance()); | 
 |   rfh_1->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |  | 
 |   // 2) Use a dummy sticky blocklisted features. | 
 |   rfh_1->UseDummyStickyBackForwardCacheDisablingFeatureForTesting(); | 
 |  | 
 |   // 3) Navigate same-site. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_2)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // Because we used sticky blocklisted features, we will not do a proactive | 
 |   // BrowsingInstance swap. | 
 |   EXPECT_TRUE(site_instance_1->IsRelatedSiteInstance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance())); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Both sticky and non-sticky reasons are recorded here. | 
 |   ExpectNotRestored( | 
 |       { | 
 |           NotRestoredReason::kBlocklistedFeatures, | 
 |           NotRestoredReason::kBrowsingInstanceNotSwapped, | 
 |       }, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kDummy, | 
 |        kBlockingReasonEnum}, | 
 |       {ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {}, {}, | 
 |       FROM_HERE); | 
 |   // NotRestoredReason tree should match the flattened list. | 
 |   EXPECT_THAT( | 
 |       GetTreeResult()->GetDocumentResult(), | 
 |       MatchesDocumentResult( | 
 |           NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures, | 
 |                               NotRestoredReason::kBrowsingInstanceNotSwapped}), | 
 |           BlockListedFeatures( | 
 |               {blink::scheduler::WebSchedulerTrackedFeature::kDummy, | 
 |                kBlockingReasonEnum}))); | 
 | } | 
 |  | 
 | // Tests which blocklisted features are tracked in the metrics when we used a | 
 | // non-sticky blocklisted feature and do a browser-initiated cross-site | 
 | // navigation. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     BlocklistedFeaturesTracking_CrossSite_BrowserInitiated_NonSticky) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // 1) Navigate to a blocking page. | 
 |   GURL url_a(https_server()->GetURL("a.test", kBlockingPagePath)); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title2.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   scoped_refptr<SiteInstanceImpl> site_instance_a = | 
 |       static_cast<SiteInstanceImpl*>( | 
 |           web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); | 
 |  | 
 |   // 2) Navigate cross-site, browser-initiated. | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted feature. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   // Because we only used non-sticky blocklisted features, we will still do a | 
 |   // proactive BrowsingInstance swap. | 
 |   EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance())); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Because the RenderFrameHostManager changed, the blocklisted features will | 
 |   // be tracked in RenderFrameHostManager::UnloadOldFrame. | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {kBlockingReasonEnum}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests which blocklisted features are tracked in the metrics when we used a | 
 | // non-sticky blocklisted feature and do a renderer-initiated cross-site | 
 | // navigation. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     BlocklistedFeaturesTracking_CrossSite_RendererInitiated_NonSticky) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // 1) Navigate to an blocking page. | 
 |   GURL url_a(https_server()->GetURL("a.test", kBlockingPagePath)); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   scoped_refptr<SiteInstanceImpl> site_instance_a = | 
 |       static_cast<SiteInstanceImpl*>( | 
 |           web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); | 
 |  | 
 |   // 3) Navigate cross-site, renderer-inititated. | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted feature. | 
 |   ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b)); | 
 |   // Because we only used non-sticky blocklisted features, we will still do a | 
 |   // proactive BrowsingInstance swap. | 
 |   EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance())); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Because the RenderFrameHostManager changed, the blocklisted features will | 
 |   // be tracked in RenderFrameHostManager::UnloadOldFrame. | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {kBlockingReasonEnum}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests which blocklisted features are tracked in the metrics when we used a | 
 | // non-sticky blocklisted feature and do a same-site navigation. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        BlocklistedFeaturesTracking_SameSite_NonSticky) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   GURL url_1(https_server()->GetURL(kBlockingPagePath)); | 
 |   GURL url_2(https_server()->GetURL("/title2.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_1)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   scoped_refptr<SiteInstanceImpl> site_instance_1 = | 
 |       static_cast<SiteInstanceImpl*>( | 
 |           web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); | 
 |  | 
 |   // 2) Navigate same-site. | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted feature. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_2)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   // Because we only used non-sticky blocklisted features, we will still do a | 
 |   // proactive BrowsingInstance swap. | 
 |   EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance())); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // Because the RenderFrameHostManager changed, the blocklisted features will | 
 |   // be tracked in RenderFrameHostManager::UnloadOldFrame. | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {kBlockingReasonEnum}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Use a blocklisted feature in multiple locations from an external JavaScript | 
 | // file and make sure all the JavaScript location details are captured. | 
 | // TODO(crbug.com/40241677): WebSocket server is flaky Android. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_MultipleBlocksFromJavaScriptFile \ | 
 |   DISABLED_MultipleBlocksFromJavaScriptFile | 
 | #else | 
 | #define MAYBE_MultipleBlocksFromJavaScriptFile MultipleBlocksFromJavaScriptFile | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_MultipleBlocksFromJavaScriptFile) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with multiple WebSocket usage. | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/back_forward_cache/page_with_websocket_external_script.html")); | 
 |   GURL url_js(embedded_test_server()->GetURL( | 
 |       "a.com", "/back_forward_cache/websocket_external_script.js")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |  | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Open WebSocket connections. | 
 |   const char scriptA[] = R"( | 
 |     openWebSocketConnectionA($1); | 
 |   )"; | 
 |   const char scriptB[] = R"( | 
 |     openWebSocketConnectionB($1); | 
 |   )"; | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptA, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptB, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketAOpen()")); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketBOpen()")); | 
 |  | 
 |   // Call this to access tree result later. | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |  | 
 |   // 2) Navigate to b.com. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ASSERT_EQ(url_a.spec(), current_frame_host()->GetLastCommittedURL()); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebSocket}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 |   auto& map = GetTreeResult()->GetBlockingDetailsMap(); | 
 |   // Only WebSocket should be reported. | 
 |   EXPECT_EQ(static_cast<int>(map.size()), 1); | 
 |   EXPECT_TRUE( | 
 |       map.contains(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket)); | 
 |   // Both socketA and socketB's JavaScript locations should be reported. | 
 |   EXPECT_THAT( | 
 |       map.at(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket), | 
 |       testing::UnorderedElementsAre( | 
 |           MatchesBlockingDetails(MatchesSourceLocation(url_js, "", 10, 15)), | 
 |           MatchesBlockingDetails(MatchesSourceLocation(url_js, "", 17, 15)))); | 
 | } | 
 |  | 
 | // Use a blocklisted feature in multiple locations from an external JavaScript | 
 | // file but stop using one of them before navigating away. Make sure that only | 
 | // the one still in use is reported. | 
 | // TODO(crbug.com/40241677): WebSocket server is flaky Android. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_BlockAndUnblockFromJavaScriptFile \ | 
 |   DISABLED_BlockAndUnblockFromJavaScriptFile | 
 | #else | 
 | #define MAYBE_BlockAndUnblockFromJavaScriptFile \ | 
 |   BlockAndUnblockFromJavaScriptFile | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_BlockAndUnblockFromJavaScriptFile) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with multiple WebSocket usage. | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/back_forward_cache/page_with_websocket_external_script.html")); | 
 |   GURL url_js(embedded_test_server()->GetURL( | 
 |       "a.com", "/back_forward_cache/websocket_external_script.js")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Call this to access tree result later. | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |   // Open WebSocket connections socketA and socketB, but close socketA | 
 |   // immediately.. | 
 |   const char scriptA[] = R"( | 
 |     openWebSocketConnectionA($1); | 
 |   )"; | 
 |   const char scriptB[] = R"( | 
 |     openWebSocketConnectionB($1); | 
 |   )"; | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptA, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptB, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketAOpen()")); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketBOpen()")); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "closeConnection();")); | 
 |   ASSERT_EQ(false, EvalJs(rfh_a.get(), "isSocketAOpen()")); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketBOpen()")); | 
 |  | 
 |   // 2) Navigate to b.com. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back and ensure that the socketB's detail is captured. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ASSERT_EQ(url_a.spec(), current_frame_host()->GetLastCommittedURL()); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebSocket}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 |   auto& map = GetTreeResult()->GetBlockingDetailsMap(); | 
 |   // Only WebSocket should be reported. | 
 |   EXPECT_EQ(static_cast<int>(map.size()), 1); | 
 |   EXPECT_TRUE( | 
 |       map.contains(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket)); | 
 |   // Only socketB's JavaScript locations should be reported. | 
 |   EXPECT_THAT(map.at(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket), | 
 |               testing::UnorderedElementsAre(MatchesBlockingDetails( | 
 |                   MatchesSourceLocation(url_js, "", 17, 15)))); | 
 | } | 
 |  | 
 | // Use a blocklisted feature in multiple places from HTML file and make sure all | 
 | // the JavaScript locations detail are captured. | 
 | // TODO(crbug.com/40241677): WebSocket server is flaky Android. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_MultipleBlocksFromHTMLFile DISABLED_MultipleBlocksFromHTMLFile | 
 | #else | 
 | #define MAYBE_MultipleBlocksFromHTMLFile MultipleBlocksFromHTMLFile | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_MultipleBlocksFromHTMLFile) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with multiple WebSocket usage. | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/back_forward_cache/page_with_websocket_inline_script.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |  | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Open WebSocket connections. | 
 |   const char scriptA[] = R"( | 
 |     openWebSocketConnectionA($1); | 
 |   )"; | 
 |   const char scriptB[] = R"( | 
 |     openWebSocketConnectionB($1); | 
 |   )"; | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptA, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptB, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketAOpen()")); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketBOpen()")); | 
 |   // Call this to access tree result later. | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |  | 
 |   // 2) Navigate to b.com. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ASSERT_EQ(url_a.spec(), current_frame_host()->GetLastCommittedURL()); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebSocket}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 |   auto& map = GetTreeResult()->GetBlockingDetailsMap(); | 
 |   // Only WebSocket should be reported. | 
 |   EXPECT_EQ(static_cast<int>(map.size()), 1); | 
 |   EXPECT_TRUE( | 
 |       map.contains(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket)); | 
 |   // Both socketA and socketB's JavaScript locations should be reported. | 
 |   EXPECT_THAT( | 
 |       map.at(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket), | 
 |       testing::UnorderedElementsAre( | 
 |           MatchesBlockingDetails(MatchesSourceLocation(url_a, "", 11, 15)), | 
 |           MatchesBlockingDetails(MatchesSourceLocation(url_a, "", 18, 15)))); | 
 | } | 
 |  | 
 | // Use a blocklisted feature in multiple locations from HTML file but stop using | 
 | // one of them before navigating away. Make sure that only the one still in use | 
 | // is reported. | 
 | // TODO(crbug.com/40241677): WebSocket server is flaky Android. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_BlockAndUnblockFromHTMLFile DISABLED_BlockAndUnblockFromHTMLFile | 
 | #else | 
 | #define MAYBE_BlockAndUnblockFromHTMLFile BlockAndUnblockFromHTMLFile | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_BlockAndUnblockFromHTMLFile) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page with multiple broadcast channel usage. | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/back_forward_cache/page_with_websocket_inline_script.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |  | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Call this to access tree result later. | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |   // Open WebSocket connections socketA and socketB, but close socketA | 
 |   // immediately. | 
 |   const char scriptA[] = R"( | 
 |     openWebSocketConnectionA($1); | 
 |   )"; | 
 |   const char scriptB[] = R"( | 
 |     openWebSocketConnectionB($1); | 
 |   )"; | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptA, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(123, EvalJs(rfh_a.get(), | 
 |                         JsReplace(scriptB, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketAOpen()")); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketBOpen()")); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "closeConnection();")); | 
 |   ASSERT_EQ(false, EvalJs(rfh_a.get(), "isSocketAOpen()")); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "isSocketBOpen()")); | 
 |  | 
 |   // 2) Navigate to b.com. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ASSERT_EQ(url_a.spec(), current_frame_host()->GetLastCommittedURL()); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebSocket}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 |   auto& map = GetTreeResult()->GetBlockingDetailsMap(); | 
 |   // Only WebSocket should be reported. | 
 |   EXPECT_EQ(static_cast<int>(map.size()), 1); | 
 |   EXPECT_TRUE( | 
 |       map.contains(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket)); | 
 |   // Only socketB's JavaScript locations should be reported. | 
 |   EXPECT_THAT(map.at(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket), | 
 |               testing::UnorderedElementsAre(MatchesBlockingDetails( | 
 |                   MatchesSourceLocation(url_a, "", 18, 15)))); | 
 | } | 
 |  | 
 | // Test that details for sticky feature are captured. | 
 | // TODO(crbug.com/40241677): WebSocket server is flaky Android. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_StickyFeaturesWithDetails DISABLED_StickyFeaturesWithDetails | 
 | #else | 
 | #define MAYBE_StickyFeaturesWithDetails StickyFeaturesWithDetails | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_StickyFeaturesWithDetails) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a_no_store(embedded_test_server()->GetURL( | 
 |       "a.com", "/set-header?Cache-Control: no-store")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to `url_a_no_store`. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Call this to access tree result later. | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |  | 
 |   // Open a WebSocket. | 
 |   const char script[] = R"( | 
 |       new Promise(resolve => { | 
 |         const socket = new WebSocket($1); | 
 |         socket.addEventListener('open', () => resolve(42)); | 
 |       });)"; | 
 |   ASSERT_EQ(42, EvalJs(rfh_a.get(), | 
 |                        JsReplace(script, | 
 |                                  ws_server.GetURL("echo-with-no-extension")))); | 
 |  | 
 |   // 3) Navigate away to `url_b`. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 4) Go back to `url_a`. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebSocket, | 
 |        blink::scheduler::WebSchedulerTrackedFeature:: | 
 |            kMainResourceHasCacheControlNoStore, | 
 |        blink::scheduler::WebSchedulerTrackedFeature::kWebSocketSticky}, | 
 |       {}, {}, {}, FROM_HERE); | 
 |   auto& map = GetTreeResult()->GetBlockingDetailsMap(); | 
 |   EXPECT_EQ(static_cast<int>(map.size()), 3); | 
 |   EXPECT_TRUE( | 
 |       map.contains(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket)); | 
 |   EXPECT_TRUE(map.contains( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kWebSocketSticky)); | 
 |   EXPECT_THAT(map.at(blink::scheduler::WebSchedulerTrackedFeature::kWebSocket), | 
 |               testing::UnorderedElementsAre(MatchesBlockingDetails( | 
 |                   MatchesSourceLocation(GURL::EmptyGURL(), "", 3, 24)))); | 
 |   EXPECT_THAT( | 
 |       map.at(blink::scheduler::WebSchedulerTrackedFeature::kWebSocketSticky), | 
 |       testing::UnorderedElementsAre(MatchesBlockingDetails( | 
 |           MatchesSourceLocation(GURL::EmptyGURL(), "", 3, 24)))); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfOpenIndexedDBConnection) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page with IndexedDB. | 
 |   // After navigating back, the page should be restored. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        EvictCacheIfOnVersionChangeEventReceived) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   Shell* tab_receiving_version_change = shell(); | 
 |   Shell* tab_sending_version_change = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate the tab receiving version change to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_receiving_version_change, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Create two connection with the same version here so that it can cover the | 
 |   // cases when IndexedDB connection coordinator is not implemented correctly to | 
 |   // handle multiple connections' back/forward cache status. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE( | 
 |       ExecJs(rfh_a.get(), "setupNewIndexedDBConnectionWithSameVersion()")); | 
 |  | 
 |   // 2) Navigate the tab receiving version change away, and navigate the tab | 
 |   // sending version change to the same page, and create a new IndexedDB | 
 |   // connection with a higher version. The new IndexedDB connection should be | 
 |   // created without being blocked by the page in back/forward cache. | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(tab_receiving_version_change, | 
 |                     embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_sending_version_change, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |  | 
 |   // Running `setupNewIndexedDBConnectionWithHigherVersion()` will trigger the | 
 |   // `versionchange` event, which should cause the document receiving the | 
 |   // version change to be evicted from back/forward cache. | 
 |   content::DOMMessageQueue queue_sending_version_change( | 
 |       tab_sending_version_change->web_contents()); | 
 |   std::string message_sending_version_change; | 
 |   ExecuteScriptAsync(tab_sending_version_change, | 
 |                      "setupNewIndexedDBConnectionWithHigherVersion()"); | 
 |   ASSERT_TRUE(queue_sending_version_change.WaitForMessage( | 
 |       &message_sending_version_change)); | 
 |   ASSERT_EQ("\"onsuccess\"", message_sending_version_change); | 
 |  | 
 |   // 3) Go back to the page a with IndexedDB. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // The page should be put into the back/forward cache after the navigation, | 
 |   // but gets evicted due to `kIndexedDBEvent`. | 
 |   ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, | 
 |                     {DisallowActivationReasonId::kIndexedDBEvent}, FROM_HERE); | 
 | } | 
 |  | 
 | // Check if the non-sticky feature is properly registered before the | 
 | // `versionchange ` is sent. Since the `versionchange` event's handler won't | 
 | // close the IndexedDB connection, so when the navigation happens, the | 
 | // non-sticky feature will prevent the document from entering BFCache. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     DoesNotCacheIfVersionChangeEventIsSentButIndexedDBConnectionIsNotClosed) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   Shell* tab_receiving_version_change = shell(); | 
 |   Shell* tab_sending_version_change = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate the receiving tab to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_receiving_version_change, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_receiving(current_frame_host()); | 
 |   GURL destination_url = | 
 |       embedded_test_server()->GetURL("a.com", "/title1.html"); | 
 |  | 
 |   ASSERT_TRUE( | 
 |       ExecJs(tab_receiving_version_change, | 
 |              JsReplace("setupIndexedDBVersionChangeHandlerToNavigateTo($1)", | 
 |                        destination_url.spec()))); | 
 |  | 
 |   // 2) Navigate the sending tab to A and use IndexedDB with higher version. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_sending_version_change, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   content::DOMMessageQueue queue_receiving_version_change( | 
 |       tab_receiving_version_change->web_contents()); | 
 |   std::string message_receiving_version_change; | 
 |   content::DOMMessageQueue queue_sending_version_change( | 
 |       tab_sending_version_change->web_contents()); | 
 |   std::string message_sending_version_change; | 
 |   ExecuteScriptAsync(tab_sending_version_change, | 
 |                      "setupNewIndexedDBConnectionWithHigherVersion()"); | 
 |  | 
 |   // 3) Wait until receiving tab receives the event and sending tab successfully | 
 |   // opens the connection. The receiving tab should navigate to another page in | 
 |   // the event handler. Before the navigation, the page should register a | 
 |   // corresponding feature handle and should not be eligible for BFCache. | 
 |   // The document will be disallowed to enter BFCache because of the | 
 |   // `versionchange` event without proper closure of connection. | 
 |   ASSERT_TRUE(queue_receiving_version_change.WaitForMessage( | 
 |       &message_receiving_version_change)); | 
 |   ASSERT_EQ("\"onversionchange\"", message_receiving_version_change); | 
 |  | 
 |   TestNavigationManager navigation_manager( | 
 |       tab_receiving_version_change->web_contents(), destination_url); | 
 |   ASSERT_TRUE(navigation_manager.WaitForRequestStart()); | 
 |   ASSERT_TRUE(rfh_receiving.get()->GetBackForwardCacheDisablingFeatures().Has( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent)); | 
 |   navigation_manager.ResumeNavigation(); | 
 |   ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 |  | 
 |   ASSERT_TRUE(queue_sending_version_change.WaitForMessage( | 
 |       &message_sending_version_change)); | 
 |   ASSERT_EQ("\"onsuccess\"", message_sending_version_change); | 
 |  | 
 |   // 4) Go back to the page A in the receiving tab, the page should not be put | 
 |   // into back/forward cache at all, and the recorded blocklisted feature should | 
 |   // be `kIndexedDBEvent`. | 
 |   ASSERT_TRUE(rfh_receiving.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent}, {}, {}, | 
 |       {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Check if the non-sticky feature is properly registered before the | 
 | // `versionchange ` is sent and removed after the IndexedDB Connection is | 
 | // closed. Since the `versionchange` event's handler will close the IndexedDB | 
 | // connection before navigating away, so the document is eligible for BFCache as | 
 | // the non-sticky feature is removed. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     CacheIfVersionChangeEventIsSentAndIndexedDBConnectionIsClosed) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   Shell* tab_receiving_version_change = shell(); | 
 |   Shell* tab_sending_version_change = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate the receiving tab to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_receiving_version_change, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_receiving(current_frame_host()); | 
 |   GURL destination_url = | 
 |       embedded_test_server()->GetURL("a.com", "/title1.html"); | 
 |  | 
 |   ASSERT_TRUE(ExecJs(tab_receiving_version_change, | 
 |                      JsReplace("setupIndexedDBVersionChangeHandlerToCloseConnec" | 
 |                                "tionAndNavigateTo($1)", | 
 |                                destination_url.spec()))); | 
 |  | 
 |   // 2) Navigate the sending tab to A and use IndexedDB with higher version. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_sending_version_change, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   content::DOMMessageQueue queue_receiving_version_change( | 
 |       tab_receiving_version_change->web_contents()); | 
 |   std::string message_receiving_version_change; | 
 |   content::DOMMessageQueue queue_sending_version_change( | 
 |       tab_sending_version_change->web_contents()); | 
 |   std::string message_sending_version_change; | 
 |   ExecuteScriptAsync(tab_sending_version_change, | 
 |                      "setupNewIndexedDBConnectionWithHigherVersion()"); | 
 |  | 
 |   // 3) Wait until receiving tab receives the event and sending tab successfully | 
 |   // opens the connection. The receiving tab should navigate to another page in | 
 |   // the event handler. Before the navigation, the page should register a | 
 |   // corresponding feature handle and should not be eligible for BFCache, but it | 
 |   // will be removed when the connection is closed, making the page eligible for | 
 |   // BFCache. | 
 |   ASSERT_TRUE(queue_receiving_version_change.WaitForMessage( | 
 |       &message_receiving_version_change)); | 
 |   ASSERT_EQ("\"onversionchange\"", message_receiving_version_change); | 
 |  | 
 |   TestNavigationManager navigation_manager( | 
 |       tab_receiving_version_change->web_contents(), destination_url); | 
 |   ASSERT_TRUE(navigation_manager.WaitForRequestStart()); | 
 |   // Since the connection is closed, the tracked feature should be reset so | 
 |   // the page is allowed to enter BFCache again. | 
 |  | 
 |   ASSERT_FALSE(rfh_receiving.get()->GetBackForwardCacheDisablingFeatures().Has( | 
 |       blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent)); | 
 |  | 
 |   navigation_manager.ResumeNavigation(); | 
 |   ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 |  | 
 |   ASSERT_TRUE(queue_sending_version_change.WaitForMessage( | 
 |       &message_sending_version_change)); | 
 |   ASSERT_EQ("\"onsuccess\"", message_sending_version_change); | 
 |  | 
 |   // 4) Go back to the page A in the receiving tab, it should be restored from | 
 |   // BFCache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfIndexedDBConnectionClosedInPagehide) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to A and use IndexedDB, and close the connection on pagehide. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()")); | 
 |   // This registers a pagehide handler to close the IDB connection. This should | 
 |   // remove the bfcache blocking. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(rfh_a.get(), "registerPagehideToCloseIndexedDBConnection()")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page with IndexedDB. The connection is closed so it | 
 |   // should be restored from bfcache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfIndexedDBTransactionNotCommitted) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()")); | 
 |   // This registers a pagehide handler to start a new transaction. This will | 
 |   // block bfcache because there is an inflight transaction. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "registerPagehideToStartTransaction()")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // 3) Go back to the page with IndexedDB. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfIndexedDBConnectionTransactionCommit) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()")); | 
 |   // This registers a pagehide handler to start and commit the IDB transactions. | 
 |   // Since the transactions are ended inside the handler, the page is no longer | 
 |   // blocked for inflight IDB transactions. | 
 |   ASSERT_TRUE( | 
 |       ExecJs(rfh_a.get(), "registerPagehideToStartAndCommitTransaction()")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page with IndexedDB. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Verifies that transactions from a single client/render frame cannot disable | 
 | // BFCache for that client. Regression test for https://crbug.com/1517989 | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        IndexedDBClientDoesntBlockSelf) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Use IDB and spam transactions. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(shell(), "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE(ExecJs(shell(), "runInfiniteIndexedDBTransactionLoop()")); | 
 |   ASSERT_TRUE(ExecJs(shell(), "runInfiniteIndexedDBTransactionLoop()")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page with IndexedDB. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Verifies that transactions from a single client/render frame and a dedicated | 
 | // worker belonging to the frame cannot disable BFCache for that client. | 
 | // Regression test for https://crbug.com/343519262. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        IndexedDBClientWithDedicatedWorkerDoesntBlockSelf) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Use IDB and spam transactions. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", | 
 |                    "/back_forward_cache/" | 
 |                    "page_with_dedicated_worker_using_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // 1.a) Setup IndexedDB on the main page and a dedicated worker. | 
 |   ASSERT_TRUE(ExecJs(shell(), "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE( | 
 |       ExecJs(shell(), "sendMessageToWorker('setupIndexedDBConnection')")); | 
 |   // 1.b) Run infinite loops on the worker and the main page. | 
 |   ASSERT_TRUE(ExecJs( | 
 |       shell(), "sendMessageToWorker('runInfiniteIndexedDBTransactionLoop')")); | 
 |   ASSERT_TRUE(ExecJs(shell(), "runInfiniteIndexedDBTransactionLoop()")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page with IndexedDB. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Verifies that a RF will be evicted from the cache if one of its transactions | 
 | // attempts to start while the RF is already in the cache, assuming the | 
 | // transaction is blocking other clients. That is, the | 
 | // kIndexedDBTransactionIsStartingWhileBlockingOthers case. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        IndexedDBDoNotCacheIfInactiveAndBlockingActive) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   Shell* tab_holding_locks = CreateBrowser(); | 
 |   Shell* tab_waiting_for_locks = shell(); | 
 |   Shell* next_tab_waiting_for_locks = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate the tab holding locks to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_holding_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   ASSERT_TRUE(ExecJs(tab_holding_locks, "setupIndexedDBConnection()")); | 
 |   // Make sure the page keeps holding the lock by running infinite tasks on the | 
 |   // object store. | 
 |   ASSERT_TRUE( | 
 |       ExecJs(tab_holding_locks, "runInfiniteIndexedDBTransactionLoop()")); | 
 |  | 
 |   // 2) Navigate the tab waiting for locks to A as well and make it request | 
 |   // the same lock. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_waiting_for_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "startIndexedDBTransaction()")); | 
 |  | 
 |   // 3) Navigate away the tab that's waiting for locks. It should enter BFCache. | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(tab_waiting_for_locks, | 
 |                     embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 4) Go back to the page with IndexedDB. | 
 |   ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_FALSE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 5) Set up a third tab that's waiting for the same lock. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       next_tab_waiting_for_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   ASSERT_TRUE(ExecJs(next_tab_waiting_for_locks, "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE( | 
 |       ExecJs(next_tab_waiting_for_locks, "startIndexedDBTransaction()")); | 
 |   // Ensure that the transaction for the above is processed before continuing | 
 |   // by round-tripping a task through the browser IDB thread (this task happens | 
 |   // to be the opening of a new connection, which doesn't require acquiring | 
 |   // locks). Without this step, the above transaction may not be processed until | 
 |   // after the navigation below, which would affect the disallow activation | 
 |   // reason. | 
 |   { | 
 |     content::DOMMessageQueue queue(next_tab_waiting_for_locks->web_contents()); | 
 |     EXPECT_TRUE(ExecJs(next_tab_waiting_for_locks, | 
 |                        "setupNewIndexedDBConnectionWithSameVersion()")); | 
 |     std::string message; | 
 |     ASSERT_TRUE(queue.WaitForMessage(&message)); | 
 |     ASSERT_EQ("\"success_same_version\"", message); | 
 |   } | 
 |  | 
 |   // 6) Repeat step 3. Still enters BFCache. | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(tab_waiting_for_locks, | 
 |                     embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 7) Now navigate the tab holding the locks to a different site. Since the | 
 |   // locks are released, and the BFCached tab is next in line, but is blocking a | 
 |   // non-BFCached page, the BFCached tab should be evicted. | 
 |   ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL( | 
 |                                                    "b.com", "/title1.html"))); | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, | 
 |                     {DisallowActivationReasonId:: | 
 |                          kIndexedDBTransactionIsStartingWhileBlockingOthers}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     DoNotCacheIfIndexedDBTransactionHoldingLocksAndBlockingOthers) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   Shell* tab_holding_locks = shell(); | 
 |   Shell* tab_waiting_for_locks = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate the tab holding locks to A and use IndexedDB. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_holding_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(tab_holding_locks, "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE(ExecJs(tab_holding_locks, | 
 |                      "registerPagehideToCloseIndexedDBConnection()")); | 
 |   // Make sure the page keeps holding the lock by running infinite tasks on the | 
 |   // object store. | 
 |   ExecuteScriptAsync(tab_holding_locks, | 
 |                      "runInfiniteIndexedDBTransactionLoop()"); | 
 |  | 
 |   // 2) Navigate the tab waiting for locks to A as well and make it request for | 
 |   // the same lock. Since the other tab is holding the lock, this | 
 |   // tab will be blocked and waiting for the lock to be released. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_waiting_for_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "startIndexedDBTransaction()")); | 
 |  | 
 |   // 3) Navigate the tab holding locks away. | 
 |   // The page should be evicted by disallowing activation. | 
 |   ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL( | 
 |                                                    "b.com", "/title1.html"))); | 
 |  | 
 |   // 4) Go back to the page with IndexedDB from the tab holding the locks. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, | 
 |                     {DisallowActivationReasonId:: | 
 |                          kIndexedDBTransactionIsOngoingAndBlockingOthers}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        EvictCacheIfPageBlocksNewIndexedDBTransaction) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   Shell* tab_holding_locks = shell(); | 
 |   Shell* tab_acquiring_locks = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate the tab holding locks to A and use IndexedDB, it also register | 
 |   // a event on pagehide to run tasks that never ends to keep the IndexedDB | 
 |   // transaction locks. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_holding_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   content::DOMMessageQueue queue_holding_locks( | 
 |       tab_holding_locks->web_contents()); | 
 |   ASSERT_TRUE(ExecJs(tab_holding_locks, "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE( | 
 |       ExecJs(tab_holding_locks, "registerPagehideToStartTransaction()")); | 
 |  | 
 |   // 2) Navigate the tab holding locks away. | 
 |   ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL( | 
 |                                                    "b.com", "/title1.html"))); | 
 |  | 
 |   // 3) After confirming the transaction has been created from the tab holding | 
 |   // locks, navigate the tab acquiring locks to A that tries to acquire the same | 
 |   // lock. | 
 |   std::string message_holding_locks; | 
 |   ASSERT_TRUE(queue_holding_locks.WaitForMessage(&message_holding_locks)); | 
 |   ASSERT_EQ("\"transaction_created\"", message_holding_locks); | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       tab_acquiring_locks, | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/back_forward_cache/page_with_indexedDB.html"))); | 
 |  | 
 |   content::DOMMessageQueue queue_acquiring_locks( | 
 |       tab_acquiring_locks->web_contents()); | 
 |   ASSERT_TRUE(ExecJs(tab_acquiring_locks, "setupIndexedDBConnection()")); | 
 |   ASSERT_TRUE(ExecJs(tab_acquiring_locks, "startIndexedDBTransaction()")); | 
 |  | 
 |   // 4) After confirming that the transaction from the tab acquiring locks is | 
 |   // completed (which should evict the other tab if it's in BFCache), navigate | 
 |   // the tab holding locks back to the page with IndexedDB. | 
 |   std::string message_acquiring_locks; | 
 |   ASSERT_TRUE(queue_acquiring_locks.WaitForMessage(&message_acquiring_locks)); | 
 |   ASSERT_EQ("\"transaction_completed\"", message_acquiring_locks); | 
 |   // The page should be evicted by disallowing activation. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, | 
 |       {DisallowActivationReasonId::kIndexedDBTransactionIsAcquiringLocks}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | // The parameter is used for switching `kBFCacheOpenBroadcastChannel`. | 
 | class BackForwardCacheWithBroadcastChannelTest | 
 |     : public BackForwardCacheBrowserTest, | 
 |       public testing::WithParamInterface<bool> { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     if (IsBFCacheOpenBroadcastChannelEnabled()) { | 
 |       EnableFeatureAndSetParams(blink::features::kBFCacheOpenBroadcastChannel, | 
 |                                 "", ""); | 
 |     } else { | 
 |       DisableFeature(blink::features::kBFCacheOpenBroadcastChannel); | 
 |     } | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 |  | 
 |   bool IsBFCacheOpenBroadcastChannelEnabled() { return GetParam(); } | 
 | }; | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          BackForwardCacheWithBroadcastChannelTest, | 
 |                          testing::Bool()); | 
 |  | 
 | // Checks that a page with an open broadcast channel is eligible for BFCache. | 
 | // Expects it's not eligible if the flag is disabled. | 
 | IN_PROC_BROWSER_TEST_P(BackForwardCacheWithBroadcastChannelTest, | 
 |                        MaybeCacheIfBroadcastChannelStillOpen) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   GURL url_a(https_server()->GetURL( | 
 |       "a.test", "/back_forward_cache/page_with_broadcastchannel.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // 2) Use BroadcastChannel (a non-sticky blocklisted feature). | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(false);")); | 
 |  | 
 |   // 3) Navigate cross-site, browser-initiated. | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted feature. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   if (IsBFCacheOpenBroadcastChannelEnabled()) { | 
 |     ExpectRestored(FROM_HERE); | 
 |   } else { | 
 |     ExpectNotRestored( | 
 |         {NotRestoredReason::kBlocklistedFeatures}, | 
 |         {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, | 
 |         {}, {}, FROM_HERE); | 
 |   } | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CacheIfBroadcastChannelIsClosedInPagehide) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   GURL url_a(https_server()->GetURL( | 
 |       "a.test", "/back_forward_cache/page_with_broadcastchannel.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   // 2) Use BroadcastChannel (a non-sticky blocklisted feature). | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(true);")); | 
 |  | 
 |   // 3) Navigate cross-site, browser-initiated. | 
 |   // The previous page won't get into the back-forward cache because of the | 
 |   // blocklisted feature. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Checks that a page will be evicted from BFCache as soon as its broadcast | 
 | // channel receives a message. | 
 | IN_PROC_BROWSER_TEST_P(BackForwardCacheWithBroadcastChannelTest, | 
 |                        MaybeEvictOnMessage) { | 
 |   // No need to test for when the flag is disabled. In that case the page will | 
 |   // not enter BFCache if there's an open broadcast channel. | 
 |   if (!IsBFCacheOpenBroadcastChannelEnabled()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // Two same-origin pages and one empty page. | 
 |   GURL url_a_receiver(https_server()->GetURL( | 
 |       "a.test", "/back_forward_cache/page_with_broadcastchannel.html")); | 
 |   GURL url_a_sender(https_server()->GetURL( | 
 |       "a.test", "/back_forward_cache/page_with_broadcastchannel_sender.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // Navigate to a page which will receive message. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a_receiver)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   RenderFrameDeletedObserver receiver_rfh_deleted_observer( | 
 |       current_frame_host()); | 
 |   // Set up a broadcast channel. | 
 |   RenderFrameHostImpl* rfh_a_receiver = current_frame_host(); | 
 |   EXPECT_TRUE(ExecJs(rfh_a_receiver, "acquireBroadcastChannel();")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a_receiver, "setOnMessage();")); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_TRUE(rfh_a_receiver->IsInBackForwardCache()); | 
 |  | 
 |   // Open another tab and navigate to a page which will send message. | 
 |   Shell* shell2 = | 
 |       Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(), | 
 |                              url_a_sender, nullptr, gfx::Size()); | 
 |   ASSERT_TRUE(WaitForLoadStop(shell2->web_contents())); | 
 |   // Open a broadcast channel and cast a message. | 
 |   RenderFrameHostImplWrapper rfh_a_sender( | 
 |       shell2->web_contents()->GetPrimaryMainFrame()); | 
 |   EXPECT_TRUE(ExecJs(rfh_a_sender.get(), "acquireBroadcastChannel();")); | 
 |   EXPECT_TRUE(ExecJs(rfh_a_sender.get(), "sendMessageOnce();")); | 
 |  | 
 |   // The receiver page's rfh should be deleted. | 
 |   receiver_rfh_deleted_observer.WaitUntilDeleted(); | 
 |  | 
 |   // Navigate back from the empty page to the receiver page. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   // The receiver page should have been evicted upon message. | 
 |   ExpectNotRestored({NotRestoredReason::kBroadcastChannelOnMessage}, {}, {}, {}, | 
 |                     {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Disabled on Android, since we have problems starting up the websocket test | 
 | // server in the host | 
 | // TODO(crbug.com/40241677): Re-enable the test after solving the WS server. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_WebSocketCachedIfClosed DISABLED_WebSocketCachedIfClosed | 
 | #else | 
 | #define MAYBE_WebSocketCachedIfClosed WebSocketCachedIfClosed | 
 | #endif | 
 | // Pages with WebSocket should be cached if the connection is closed. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_WebSocketCachedIfClosed) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Open a WebSocket. | 
 |   const char script[] = R"( | 
 |       let socket; | 
 |       window.onpagehide = event => { | 
 |         socket.close(); | 
 |       } | 
 |       new Promise(resolve => { | 
 |         socket = new WebSocket($1); | 
 |         socket.addEventListener('open', () => resolve(42)); | 
 |       });)"; | 
 |   ASSERT_EQ(42, EvalJs(rfh_a.get(), | 
 |                        JsReplace(script, | 
 |                                  ws_server.GetURL("echo-with-no-extension")))); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Navigate back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | class WebTransportBackForwardCacheBrowserTest | 
 |     : public BackForwardCacheBrowserTest { | 
 |  public: | 
 |   WebTransportBackForwardCacheBrowserTest() { server_.Start(); } | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |     server_.SetUpCommandLine(command_line); | 
 |   } | 
 |   int port() const { return server_.server_address().port(); } | 
 |  | 
 |  private: | 
 |   WebTransportSimpleTestServer server_; | 
 | }; | 
 |  | 
 | // Pages with active WebTransport should not be cached. | 
 | // TODO(yhirano): Update this test once | 
 | // https://github.com/w3c/webtransport/issues/326 is resolved. | 
 | IN_PROC_BROWSER_TEST_F(WebTransportBackForwardCacheBrowserTest, | 
 |                        ActiveWebTransportEvictsPage) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Establish a WebTransport session. | 
 |   const char script[] = R"( | 
 |       let transport = new WebTransport('https://localhost:$1/echo'); | 
 |       )"; | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), JsReplace(script, port()))); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // Confirm A is evicted. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | // Pages with inactive WebTransport should be cached. | 
 | IN_PROC_BROWSER_TEST_F(WebTransportBackForwardCacheBrowserTest, | 
 |                        WebTransportCachedIfClosed) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Establish a WebTransport session. | 
 |   const char script[] = R"( | 
 |       let transport; | 
 |       window.onpagehide = event => { | 
 |         transport.close(); | 
 |       }; | 
 |       transport = new WebTransport('https://localhost:$1/echo'); | 
 |       )"; | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), JsReplace(script, port()))); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Navigate back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Disabled on Android, since we have problems starting up the websocket test | 
 | // server in the host | 
 | // TODO(crbug.com/40241677): Re-enable the test after solving the WS server. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_WebSocketNotCached DISABLED_WebSocketNotCached | 
 | #else | 
 | #define MAYBE_WebSocketNotCached WebSocketNotCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MAYBE_WebSocketNotCached) { | 
 |   net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS, | 
 |                                    net::GetWebSocketTestDataDirectory()); | 
 |   ASSERT_TRUE(ws_server.Start()); | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // Open a WebSocket. | 
 |   const char script[] = R"( | 
 |       new Promise(resolve => { | 
 |         const socket = new WebSocket($1); | 
 |         socket.addEventListener('open', () => resolve(42)); | 
 |       });)"; | 
 |   ASSERT_EQ( | 
 |       42, EvalJs(rfh_a, JsReplace(script, | 
 |                                   ws_server.GetURL("echo-with-no-extension")))); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // Confirm A is evicted. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | void RegisterServiceWorker(RenderFrameHostImpl* rfh) { | 
 |   EXPECT_EQ("success", EvalJs(rfh, R"( | 
 |     let controller_changed_promise = new Promise(resolve_controller_change => { | 
 |       navigator.serviceWorker.oncontrollerchange = resolve_controller_change; | 
 |     }); | 
 |  | 
 |     new Promise(async resolve => { | 
 |       try { | 
 |         await navigator.serviceWorker.register( | 
 |           "./service-worker.js", {scope: "./"}) | 
 |       } catch (e) { | 
 |         resolve("error: registration has failed"); | 
 |       } | 
 |  | 
 |       await controller_changed_promise; | 
 |  | 
 |       if (navigator.serviceWorker.controller) { | 
 |         resolve("success"); | 
 |       } else { | 
 |         resolve("error: not controlled by service worker"); | 
 |       } | 
 |     }); | 
 |   )")); | 
 | } | 
 |  | 
 | // Returns a unique script for each request, to test service worker update. | 
 | std::unique_ptr<net::test_server::HttpResponse> RequestHandlerForUpdateWorker( | 
 |     const net::test_server::HttpRequest& request) { | 
 |   if (request.relative_url != "/back_forward_cache/service-worker.js") { | 
 |     return nullptr; | 
 |   } | 
 |   static int counter = 0; | 
 |   auto http_response = std::make_unique<net::test_server::BasicHttpResponse>(); | 
 |   http_response->set_code(net::HTTP_OK); | 
 |   const char script[] = R"( | 
 |     // counter = $1 | 
 |     self.addEventListener('activate', function(event) { | 
 |       event.waitUntil(self.clients.claim()); | 
 |     }); | 
 |   )"; | 
 |   http_response->set_content(JsReplace(script, counter++)); | 
 |   http_response->set_content_type("text/javascript"); | 
 |   http_response->AddCustomHeader("Cache-Control", | 
 |                                  "no-cache, no-store, must-revalidate"); | 
 |   return http_response; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class TestVibrationManager : public device::mojom::VibrationManager { | 
 |  public: | 
 |   TestVibrationManager() { | 
 |     OverrideVibrationManagerBinderForTesting(base::BindRepeating( | 
 |         &TestVibrationManager::BindVibrationManager, base::Unretained(this))); | 
 |   } | 
 |  | 
 |   ~TestVibrationManager() override { | 
 |     OverrideVibrationManagerBinderForTesting(base::NullCallback()); | 
 |   } | 
 |  | 
 |   void BindVibrationManager( | 
 |       mojo::PendingReceiver<device::mojom::VibrationManager> receiver, | 
 |       mojo::PendingRemote<device::mojom::VibrationManagerListener> listener) { | 
 |     receiver_.Bind(std::move(receiver)); | 
 |   } | 
 |  | 
 |   bool TriggerVibrate(RenderFrameHostImpl* rfh, int duration) { | 
 |     return EvalJs(rfh, JsReplace("navigator.vibrate($1)", duration)) | 
 |         .ExtractBool(); | 
 |   } | 
 |  | 
 |   bool TriggerShortVibrationSequence(RenderFrameHostImpl* rfh) { | 
 |     return EvalJs(rfh, "navigator.vibrate([10] * 1000)").ExtractBool(); | 
 |   } | 
 |  | 
 |   bool WaitForCancel() { | 
 |     run_loop_.Run(); | 
 |     return IsCancelled(); | 
 |   } | 
 |  | 
 |   bool IsCancelled() { return cancelled_; } | 
 |  | 
 |  private: | 
 |   // device::mojom::VibrationManager: | 
 |   void Vibrate(int64_t milliseconds, VibrateCallback callback) override { | 
 |     cancelled_ = false; | 
 |     std::move(callback).Run(); | 
 |   } | 
 |  | 
 |   void Cancel(CancelCallback callback) override { | 
 |     cancelled_ = true; | 
 |     std::move(callback).Run(); | 
 |     run_loop_.Quit(); | 
 |   } | 
 |  | 
 |   bool cancelled_ = false; | 
 |   base::RunLoop run_loop_; | 
 |   mojo::Receiver<device::mojom::VibrationManager> receiver_{this}; | 
 | }; | 
 |  | 
 | // Tests that vibration stops after the page enters bfcache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        VibrationStopsAfterEnteringCache) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   TestVibrationManager vibration_manager; | 
 |  | 
 |   // 1) Navigate to a page with a long vibration. | 
 |   GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   ASSERT_TRUE(vibration_manager.TriggerVibrate(rfh_a, 10000)); | 
 |   EXPECT_FALSE(vibration_manager.IsCancelled()); | 
 |  | 
 |   // 2) Navigate away and expect the vibration to be canceled. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_NE(current_frame_host(), rfh_a); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(vibration_manager.WaitForCancel()); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Tests that the short vibration sequence on the page stops after it enters | 
 | // bfcache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        ShortVibrationSequenceStopsAfterEnteringCache) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   TestVibrationManager vibration_manager; | 
 |  | 
 |   // 1) Navigate to a page with a long vibration. | 
 |   GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   ASSERT_TRUE(vibration_manager.TriggerShortVibrationSequence(rfh_a)); | 
 |   EXPECT_FALSE(vibration_manager.IsCancelled()); | 
 |  | 
 |   // 2) Navigate away and expect the vibration to be canceled. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_NE(current_frame_host(), rfh_a); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(vibration_manager.WaitForCancel()); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CachedPagesWithServiceWorkers) { | 
 |   CreateHttpsServer(); | 
 |   SetupCrossSiteRedirector(https_server()); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/empty.html"))); | 
 |  | 
 |   // Register a service worker. | 
 |   RegisterServiceWorker(current_frame_host()); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to A. The navigation should be served from the cache. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        EvictIfCacheBlocksServiceWorkerVersionActivation) { | 
 |   CreateHttpsServer(); | 
 |   https_server()->RegisterRequestHandler( | 
 |       base::BindRepeating(&RequestHandlerForUpdateWorker)); | 
 |   SetupCrossSiteRedirector(https_server()); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |   Shell* tab_x = shell(); | 
 |   Shell* tab_y = CreateBrowser(); | 
 |   // 1) Navigate to A in tab X. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_x, | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/empty.html"))); | 
 |   // 2) Register a service worker. | 
 |   RegisterServiceWorker(current_frame_host()); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |   // 3) Navigate away to B in tab X. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(tab_x, https_server()->GetURL("b.test", "/title1.html"))); | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   // 4) Navigate to A in tab Y. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_y, | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/empty.html"))); | 
 |   // 5) Close tab Y to activate a service worker version. | 
 |   // This should evict |rfh_a| from the cache. | 
 |   tab_y->Close(); | 
 |   deleted.WaitUntilDeleted(); | 
 |   // 6) Navigate to A in tab X. | 
 |   ASSERT_TRUE(HistoryGoBack(tab_x->web_contents())); | 
 |   ExpectNotRestored( | 
 |       { | 
 |           NotRestoredReason::kServiceWorkerVersionActivation, | 
 |       }, | 
 |       {}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        EvictWithPostMessageToCachedClient) { | 
 |   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | 
 |   https_server.RegisterRequestHandler( | 
 |       base::BindRepeating(&RequestHandlerForUpdateWorker)); | 
 |   https_server.AddDefaultHandlers(GetTestDataFilePath()); | 
 |   https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); | 
 |   SetupCrossSiteRedirector(&https_server); | 
 |   ASSERT_TRUE(https_server.Start()); | 
 |   Shell* tab_to_execute_service_worker = shell(); | 
 |   Shell* tab_to_be_bfcached = CreateBrowser(); | 
 |  | 
 |   // Observe the new WebContents to trace the navigation ID. | 
 |   WebContentsObserver::Observe(tab_to_be_bfcached->web_contents()); | 
 |  | 
 |   // 1) Navigate to A in |tab_to_execute_service_worker|. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_to_execute_service_worker, | 
 |       https_server.GetURL( | 
 |           "a.test", "/back_forward_cache/service_worker_post_message.html"))); | 
 |  | 
 |   // 2) Register a service worker. | 
 |   EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, | 
 |                            "register('service_worker_post_message.js')")); | 
 |  | 
 |   // 3) Navigate to A in |tab_to_be_bfcached|. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_to_be_bfcached, | 
 |       https_server.GetURL( | 
 |           "a.test", "/back_forward_cache/service_worker_post_message.html"))); | 
 |   const std::string script_to_store = | 
 |       "executeCommandOnServiceWorker('StoreClients')"; | 
 |   EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, script_to_store)); | 
 |   RenderFrameHostImplWrapper rfh( | 
 |       tab_to_be_bfcached->web_contents()->GetPrimaryMainFrame()); | 
 |  | 
 |   // 4) Navigate away to B in |tab_to_be_bfcached|. | 
 |   EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, | 
 |                             https_server.GetURL("b.test", "/title1.html"))); | 
 |   EXPECT_FALSE(rfh.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh->IsInBackForwardCache()); | 
 |  | 
 |   // 5) Trigger client.postMessage via |tab_to_execute_service_worker|. Cache in | 
 |   // |tab_to_be_bfcached| will be evicted. | 
 |   const std::string script_to_post_message = | 
 |       "executeCommandOnServiceWorker('PostMessageToStoredClients')"; | 
 |   EXPECT_EQ("DONE", | 
 |             EvalJs(tab_to_execute_service_worker, script_to_post_message)); | 
 |   ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 6) Go back to A in |tab_to_be_bfcached|. | 
 |   ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kServiceWorkerPostMessage}, {}, {}, {}, | 
 |                     {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EvictOnServiceWorkerClaim) { | 
 |   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | 
 |   https_server.RegisterRequestHandler( | 
 |       base::BindRepeating(&RequestHandlerForUpdateWorker)); | 
 |   https_server.AddDefaultHandlers(GetTestDataFilePath()); | 
 |   https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); | 
 |   SetupCrossSiteRedirector(&https_server); | 
 |   ASSERT_TRUE(https_server.Start()); | 
 |  | 
 |   Shell* tab_to_be_bfcached = shell(); | 
 |   Shell* tab_to_execute_service_worker = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate to A in |tab_to_be_bfcached|. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_to_be_bfcached, | 
 |       https_server.GetURL( | 
 |           "a.test", "/back_forward_cache/service_worker_registration.html"))); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |  | 
 |   // 2) Navigate away to B in |tab_to_be_bfcached|. | 
 |   EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, | 
 |                             https_server.GetURL("b.test", "/title1.html"))); | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Navigate to A in |tab_to_execute_service_worker|. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_to_execute_service_worker, | 
 |       https_server.GetURL( | 
 |           "a.test", "/back_forward_cache/service_worker_registration.html"))); | 
 |  | 
 |   // 4) Register a service worker for |tab_to_execute_service_worker|. | 
 |   EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, | 
 |                            "register('service_worker_registration.js')")); | 
 |  | 
 |   // 5) The service worker calls clients.claim(). |rfh_a| would normally be | 
 |   //    claimed but because it's in bfcache, it is evicted from the cache. | 
 |   EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, "claim()")); | 
 |   deleted.WaitUntilDeleted(); | 
 |  | 
 |   // 6) Navigate to A in |tab_to_be_bfcached|. | 
 |   ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kServiceWorkerClaim}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        EvictOnServiceWorkerUnregistration) { | 
 |   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | 
 |   https_server.RegisterRequestHandler( | 
 |       base::BindRepeating(&RequestHandlerForUpdateWorker)); | 
 |   https_server.AddDefaultHandlers(GetTestDataFilePath()); | 
 |   https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); | 
 |   SetupCrossSiteRedirector(&https_server); | 
 |   ASSERT_TRUE(https_server.Start()); | 
 |  | 
 |   Shell* tab_to_be_bfcached = shell(); | 
 |   Shell* tab_to_unregister_service_worker = CreateBrowser(); | 
 |  | 
 |   // 1) Navigate to A in |tab_to_be_bfcached|. This tab will be controlled by a | 
 |   // service worker. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_to_be_bfcached, | 
 |       https_server.GetURL("a.test", | 
 |                           "/back_forward_cache/" | 
 |                           "service_worker_registration.html?to_be_bfcached"))); | 
 |  | 
 |   // 2) Register a service worker for |tab_to_be_bfcached|, but with a narrow | 
 |   // scope with URL param. This is to prevent |tab_to_unregister_service_worker| | 
 |   // from being controlled by the service worker. | 
 |   EXPECT_EQ("DONE", | 
 |             EvalJs(tab_to_be_bfcached, | 
 |                    "register('service_worker_registration.js', " | 
 |                    "'service_worker_registration.html?to_be_bfcached')")); | 
 |   EXPECT_EQ("DONE", EvalJs(tab_to_be_bfcached, "claim()")); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |  | 
 |   // 3) Navigate to A in |tab_to_unregister_service_worker|. This tab is not | 
 |   // controlled by the service worker. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       tab_to_unregister_service_worker, | 
 |       https_server.GetURL( | 
 |           "a.test", "/back_forward_cache/service_worker_registration.html"))); | 
 |  | 
 |   // 5) Navigate from A to B in |tab_to_be_bfcached|. Now |tab_to_be_bfcached| | 
 |   // should be in bfcache. | 
 |   EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, | 
 |                             https_server.GetURL("b.test", "/title1.html"))); | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 6) The service worker gets unregistered. Now |tab_to_be_bfcached| should be | 
 |   // notified of the unregistration and evicted from bfcache. | 
 |   EXPECT_EQ( | 
 |       "DONE", | 
 |       EvalJs(tab_to_unregister_service_worker, | 
 |              "unregister('service_worker_registration.html?to_be_bfcached')")); | 
 |   deleted.WaitUntilDeleted(); | 
 |   // 7) Navigate back to A in |tab_to_be_bfcached|. | 
 |   ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kServiceWorkerUnregistration}, {}, {}, | 
 |                     {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BeaconAndBfCache) { | 
 |   constexpr char kKeepalivePath[] = "/keepalive"; | 
 |  | 
 |   net::test_server::ControllableHttpResponse keepalive(embedded_test_server(), | 
 |                                                        kKeepalivePath); | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_ping(embedded_test_server()->GetURL("a.com", kKeepalivePath)); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a.get()); | 
 |  | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell(), JsReplace(R"(navigator.sendBeacon($1, "");)", url_ping))); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // Ensure that the keepalive request is sent. | 
 |   keepalive.WaitForRequest(); | 
 |   // Don't actually send the response. | 
 |  | 
 |   // Page A should be in the cache. | 
 |   EXPECT_FALSE(delete_observer_rfh_a.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 | } | 
 |  | 
 | class GeolocationBackForwardCacheBrowserTest | 
 |     : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   GeolocationBackForwardCacheBrowserTest() : geo_override_(0.0, 0.0) {} | 
 |  | 
 |   device::ScopedGeolocationOverrider geo_override_; | 
 | }; | 
 |  | 
 | // Test that a page which has queried geolocation in the past, but have no | 
 | // active geolocation query, can be bfcached. | 
 | IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest, | 
 |                        CacheAfterGeolocationRequest) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |  | 
 |   // Query current position, and wait for the query to complete. | 
 |   EXPECT_EQ("received", EvalJs(rfh_a, R"( | 
 |       new Promise(resolve => { | 
 |         navigator.geolocation.getCurrentPosition(() => resolve('received')); | 
 |       }); | 
 |   )")); | 
 |  | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // The page has no inflight geolocation request when we navigated away, | 
 |   // so it should have been cached. | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 | } | 
 |  | 
 | // Test that a page which has an in-flight geolocation query can be bfcached, | 
 | // and verify that the page does not observe any geolocation while the page | 
 | // was inside bfcache. | 
 | IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest, | 
 |                        CancelGeolocationRequestInFlight) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |     // If set, will be called by handleEvent. | 
 |     window.pending_resolve = null; | 
 |  | 
 |     window.longitude_log = []; | 
 |     window.err_log = []; | 
 |  | 
 |     // Returns a promise that will resolve when the `longitude` is recorded in | 
 |     // the `longitude_log`. The promise will resolve with the index. | 
 |     function waitForLongitudeRecorded(longitude) { | 
 |       let index = window.longitude_log.indexOf(longitude); | 
 |       if (index >= 0) { | 
 |         return Promise.resolve(index); | 
 |       } | 
 |       return new Promise(resolve => { | 
 |         window.pending_resolve = resolve; | 
 |       }).then(() => waitForLongitudeRecorded(longitude)); | 
 |     } | 
 |  | 
 |     // Continuously query current geolocation, if the longitude is different | 
 |     // from the last recorded value, update the result in the list, | 
 |     // and resolve the pending promises with the longitude value. | 
 |     navigator.geolocation.watchPosition( | 
 |       pos => { | 
 |         let new_longitude = pos.coords.longitude; | 
 |         let log_length = window.longitude_log.length; | 
 |         if (log_length == 0 || | 
 |             window.longitude_log[log_length - 1] != new_longitude) { | 
 |           window.longitude_log.push(pos.coords.longitude); | 
 |           if (window.pending_resolve != null) { | 
 |             window.pending_resolve(); | 
 |             window.pending_resolve = null; | 
 |           } | 
 |         } | 
 |       }, | 
 |       err => window.err_log.push(err) | 
 |     ); | 
 |   )")); | 
 |  | 
 |   // Wait for the initial value to be updated in the callback. | 
 |   EXPECT_EQ( | 
 |       0, EvalJs(rfh_a, "window.waitForLongitudeRecorded(0.0);").ExtractInt()); | 
 |  | 
 |   // Update the location and wait for the promise, this location should be | 
 |   // observed. | 
 |   geo_override_.UpdateLocation(10.0, 10.0); | 
 |   EXPECT_EQ( | 
 |       1, EvalJs(rfh_a, "window.waitForLongitudeRecorded(10.0);").ExtractInt()) | 
 |       << "Geoposition before the page is put into BFCache should be visible."; | 
 |  | 
 |   // Pause resolving Geoposition queries to keep the request in-flight. | 
 |   // This location should not be observed. | 
 |   geo_override_.Pause(); | 
 |   geo_override_.UpdateLocation(20.0, 20.0); | 
 |   EXPECT_EQ(1u, geo_override_.GetGeolocationInstanceCount()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   base::RunLoop loop_until_close; | 
 |   geo_override_.SetGeolocationCloseCallback(loop_until_close.QuitClosure()); | 
 |  | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   loop_until_close.Run(); | 
 |  | 
 |   // The page has no in-flight geolocation request when we navigated away, | 
 |   // so it should have been cached. | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Resume resolving Geoposition queries. | 
 |   geo_override_.Resume(); | 
 |  | 
 |   // We update the location while the page is BFCached, but this location should | 
 |   // not be observed. | 
 |   geo_override_.UpdateLocation(30.0, 30.0); | 
 |  | 
 |   // 3) Navigate back to A. | 
 |  | 
 |   // Pause resolving Geoposition queries to keep the request in-flight. | 
 |   // The location when navigated back can be observed | 
 |   geo_override_.Pause(); | 
 |   geo_override_.UpdateLocation(40.0, 40.0); | 
 |  | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 |   EXPECT_FALSE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Resume resolving Geoposition queries. | 
 |   geo_override_.Resume(); | 
 |  | 
 |   // Wait for an update after the user navigates back to A. | 
 |   EXPECT_EQ(2, | 
 |             EvalJs(rfh_a, "window.waitForLongitudeRecorded(40.0)").ExtractInt()) | 
 |       << "Geoposition when the page is restored from BFCache should be visible"; | 
 |  | 
 |   EXPECT_EQ("0,10,40", EvalJs(rfh_a, "window.longitude_log.toString();")) | 
 |       << "Geoposition while the page is put into BFCache should be invisible, " | 
 |          "so the log array should only contain 0, 10 and 40 but not 20 and 30"; | 
 |  | 
 |   EXPECT_EQ(0, EvalJs(rfh_a, "err_log.length;")) | 
 |       << "watchPosition API should have reported no errors"; | 
 | } | 
 |  | 
 | class BluetoothBrowserTestContentBrowserClient | 
 |     : public ContentBrowserTestContentBrowserClient { | 
 |  public: | 
 |   // ContentBrowserClient: | 
 |   BluetoothDelegate* GetBluetoothDelegate() override { return &delegate_; } | 
 |  | 
 |   MockBluetoothDelegate& delegate() { return delegate_; } | 
 |  | 
 |  private: | 
 |   testing::NiceMock<MockBluetoothDelegate> delegate_; | 
 | }; | 
 |  | 
 | class BackForwardCacheWebBluetoothTest : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   BackForwardCacheWebBluetoothTest() = default; | 
 |  | 
 |   ~BackForwardCacheWebBluetoothTest() override = default; | 
 |  | 
 |   void SetUpOnMainThread() override { | 
 |     BackForwardCacheBrowserTest::SetUpOnMainThread(); | 
 |     test_client_ = std::make_unique<BluetoothBrowserTestContentBrowserClient>(); | 
 |     ON_CALL(delegate(), MayUseBluetooth).WillByDefault(Return(true)); | 
 |   } | 
 |  | 
 |   void SetUp() override { | 
 |     // The test requires a mock Bluetooth adapter to perform WebBluetooth API | 
 |     // calls. To avoid conflicts with the default Bluetooth adapter, e.g. | 
 |     // Windows adapter, which is configured during Bluetooth initialization, the | 
 |     // mock adapter is configured in SetUp(). | 
 |     adapter_ = | 
 |         base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>(); | 
 |     BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter_); | 
 |     ON_CALL(*adapter_, IsPresent).WillByDefault(Return(true)); | 
 |  | 
 |     // Configure the mock adapter to return a scanning error to avoid leaking | 
 |     // the adapter after teardown due to an ongoing scanning session. | 
 |     ON_CALL(*adapter_, StartScanWithFilter_) | 
 |         .WillByDefault( | 
 |             [](const device::BluetoothDiscoveryFilter* filter, | 
 |                base::OnceCallback<void( | 
 |                    bool, device::UMABluetoothDiscoverySessionOutcome)>& | 
 |                    callback) { | 
 |               std::move(callback).Run( | 
 |                   /*is_error=*/true, | 
 |                   device::UMABluetoothDiscoverySessionOutcome::UNKNOWN); | 
 |             }); | 
 |  | 
 |     BackForwardCacheBrowserTest::SetUp(); | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     testing::Mock::VerifyAndClearExpectations(adapter_.get()); | 
 |     BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(nullptr); | 
 |     adapter_.reset(); | 
 |     BackForwardCacheBrowserTest::TearDown(); | 
 |   } | 
 |  | 
 |   MockBluetoothDelegate& delegate() { return test_client_->delegate(); } | 
 |  | 
 |   scoped_refptr<device::MockBluetoothAdapter> adapter_; | 
 |   std::unique_ptr<BluetoothBrowserTestContentBrowserClient> test_client_; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebBluetoothTest, | 
 |                        DoesNotCacheIfRequestDeviceWasCalled) { | 
 |   // WebBluetooth requires HTTPS. | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       web_contents(), | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/empty.html"))); | 
 |   RenderFrameHostWrapper rfh_wrapper(current_frame_host()); | 
 |  | 
 |   // Call requestDevice to open a permission request dialog. Cancel the dialog | 
 |   // once it is opened. | 
 |   EXPECT_CALL(delegate(), RunBluetoothChooser) | 
 |       .WillOnce([](RenderFrameHost* frame, | 
 |                    const BluetoothChooser::EventHandler& event_handler) { | 
 |         event_handler.Run(BluetoothChooserEvent::CANCELLED, std::string()); | 
 |         return std::make_unique<BluetoothChooser>(); | 
 |       }); | 
 |   EXPECT_EQ("device not found", EvalJs(current_frame_host(), R"( | 
 |     new Promise(resolve => { | 
 |       navigator.bluetooth.requestDevice({ | 
 |         filters: [ | 
 |           { services: [0x1802, 0x1803] }, | 
 |         ] | 
 |       }) | 
 |       .then(() => resolve("device found")) | 
 |       .catch(() => resolve("device not found")) | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), | 
 |                             https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   // The page called requestDevice so it should be deleted. | 
 |   EXPECT_TRUE(rfh_wrapper.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebBluetooth}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebBluetoothTest, | 
 |                        DoesNotCacheIfGetDevicesWasCalled) { | 
 |   // WebBluetooth requires HTTPS. | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       web_contents(), | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/empty.html"))); | 
 |   RenderFrameHostWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call getDevices to get a list of devices the page is allowed to access. | 
 |   EXPECT_TRUE(ExecJs(current_frame_host(), "navigator.bluetooth.getDevices()")); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), | 
 |                             https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   // The page called getDevices so it should be deleted. | 
 |   EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebBluetooth}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebBluetoothTest, | 
 |                        DoesNotCacheIfScanningWasStarted) { | 
 |   // WebBluetooth requires HTTPS. | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   ASSERT_TRUE(NavigateToURL( | 
 |       web_contents(), | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/empty.html"))); | 
 |   RenderFrameHostWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call requestLEScan to start scanning for nearby devices. | 
 |   EXPECT_EQ("scan error", EvalJs(current_frame_host(), | 
 |                                  R"( | 
 |     new Promise(resolve => { | 
 |       navigator.bluetooth.requestLEScan({acceptAllAdvertisements: true}) | 
 |       .then(() => resolve("scan started")) | 
 |       .catch(() => resolve("scan error")) | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), | 
 |                             https_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |   // The page started scanning so it should be deleted. | 
 |   EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebBluetooth}, {}, {}, {}, | 
 |       FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebBluetoothTest, | 
 |                        DoesCacheIfGetAvailabilityWasCalled) { | 
 |   // WebBluetooth requires HTTPS. | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   // Navigate to an empty page. | 
 |   GURL url(https_server()->GetURL("a.test", "/back_forward_cache/empty.html")); | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), url)); | 
 |   RenderFrameHostWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call getAvailability to check for a Bluetooth adapter. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(current_frame_host(), "navigator.bluetooth.getAvailability()")); | 
 |  | 
 |   // Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), | 
 |                             https_server()->GetURL("b.test", "/title1.html"))); | 
 |   ASSERT_FALSE(rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE( | 
 |       static_cast<RenderFrameHostImpl*>(rfh_a.get())->IsInBackForwardCache()); | 
 |  | 
 |   // Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(current_frame_host(), rfh_a.get()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | enum class SerialContext { | 
 |   kDocument, | 
 |   kWorker, | 
 |   kNestedWorker, | 
 | }; | 
 |  | 
 | enum class SerialType { | 
 |   kSerial, | 
 |   kWebUsb, | 
 | }; | 
 |  | 
 | class BackForwardCacheBrowserWebUsbTest | 
 |     : public BackForwardCacheBrowserTest, | 
 |       public ::testing::WithParamInterface< | 
 |           std::tuple<SerialContext, SerialType>> { | 
 |  public: | 
 |   std::string GetJsToUseSerial(SerialContext context, SerialType serial_type) { | 
 |     switch (serial_type) { | 
 |       case SerialType::kSerial: | 
 |         switch (context) { | 
 |           case SerialContext::kDocument: | 
 |             return R"( | 
 |               new Promise(async resolve => { | 
 |                 let ports = await navigator.serial.getPorts(); | 
 |                 resolve("Found " + ports.length + " ports"); | 
 |               }); | 
 |             )"; | 
 |           case SerialContext::kWorker: | 
 |             return R"( | 
 |               new Promise(async resolve => { | 
 |                 const worker = new Worker( | 
 |                     "/back_forward_cache/serial/worker.js"); | 
 |                 worker.onmessage = message => resolve(message.data); | 
 |                 worker.postMessage("Run"); | 
 |               }); | 
 |             )"; | 
 |           case SerialContext::kNestedWorker: | 
 |             return R"( | 
 |               new Promise(async resolve => { | 
 |                 const worker = new Worker( | 
 |                   "/back_forward_cache/serial/nested-worker.js"); | 
 |                 worker.onmessage = message => resolve(message.data); | 
 |                 worker.postMessage("Run"); | 
 |               }); | 
 |             )"; | 
 |         } | 
 |       case SerialType::kWebUsb: | 
 |         switch (context) { | 
 |           case SerialContext::kDocument: | 
 |             return R"( | 
 |               new Promise(async resolve => { | 
 |                 let devices = await navigator.usb.getDevices(); | 
 |                 resolve("Found " + devices.length + " devices"); | 
 |               }); | 
 |             )"; | 
 |           case SerialContext::kWorker: | 
 |             return R"( | 
 |               new Promise(async resolve => { | 
 |                 const worker = new Worker( | 
 |                     "/back_forward_cache/webusb/worker.js"); | 
 |                 worker.onmessage = message => resolve(message.data); | 
 |                 worker.postMessage("Run"); | 
 |               }); | 
 |             )"; | 
 |           case SerialContext::kNestedWorker: | 
 |             return R"( | 
 |               new Promise(async resolve => { | 
 |                 const worker = new Worker( | 
 |                   "/back_forward_cache/webusb/nested-worker.js"); | 
 |                 worker.onmessage = message => resolve(message.data); | 
 |                 worker.postMessage("Run"); | 
 |               }); | 
 |             )"; | 
 |         } | 
 |     } | 
 |   } | 
 | }; | 
 |  | 
 | // Check the BackForwardCache is disabled when the WebUSB feature is used. | 
 | // TODO(crbug.com/40849874): Consider testing in a subframe. This will | 
 | // require adjustments to Permissions Policy. | 
 | IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserWebUsbTest, Serials) { | 
 |   // WebUSB requires HTTPS. | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   SerialContext context; | 
 |   SerialType serial_type; | 
 |   std::tie(context, serial_type) = GetParam(); | 
 |  | 
 |   content::BackForwardCacheDisabledTester tester; | 
 |   GURL url(https_server()->GetURL( | 
 |       "a.test", "/cross_site_iframe_factory.html?a.test(a.test)")); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   // Check that the frames we care about are cacheable. | 
 |   RenderFrameHostImplWrapper main_rfh(current_frame_host()); | 
 |   RenderFrameHostImplWrapper sub_rfh( | 
 |       current_frame_host()->child_at(0)->current_frame_host()); | 
 |   ASSERT_FALSE(main_rfh->IsBackForwardCacheDisabled()); | 
 |   ASSERT_FALSE(sub_rfh->IsBackForwardCacheDisabled()); | 
 |  | 
 |   // Execute script to use WebUSB. | 
 |   ASSERT_EQ( | 
 |       serial_type == SerialType::kSerial ? "Found 0 ports" : "Found 0 devices", | 
 |       content::EvalJs(main_rfh.get(), GetJsToUseSerial(context, serial_type))); | 
 |  | 
 |   // Verify that the correct frames are now uncacheable. | 
 |   EXPECT_TRUE(main_rfh->IsBackForwardCacheDisabled()); | 
 |   EXPECT_FALSE(sub_rfh->IsBackForwardCacheDisabled()); | 
 |   auto expected_reason = | 
 |       serial_type == SerialType::kSerial | 
 |           ? BackForwardCacheDisable::DisabledReasonId::kSerial | 
 |           : BackForwardCacheDisable::DisabledReasonId::kWebUSB; | 
 |   EXPECT_TRUE(tester.IsDisabledForFrameWithReason( | 
 |       main_rfh->GetProcess()->GetDeprecatedID(), main_rfh->GetRoutingID(), | 
 |       BackForwardCacheDisable::DisabledReason(expected_reason))); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P( | 
 |     All, | 
 |     BackForwardCacheBrowserWebUsbTest, | 
 |     testing::Combine(testing::Values(SerialContext::kDocument, | 
 |                                      SerialContext::kWorker, | 
 |                                      SerialContext::kNestedWorker), | 
 |                      testing::Values(SerialType::kWebUsb | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 |                                      , | 
 |                                      SerialType::kSerial | 
 | #endif  // !BUILDFLAG(IS_ANDROID) | 
 |                                      ))); | 
 |  | 
 | // Check that an audio suspends when the page goes to the cache and can resume | 
 | // after restored. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, AudioSuspendAndResume) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |     var audio = document.createElement('audio'); | 
 |     document.body.appendChild(audio); | 
 |  | 
 |     audio.testObserverEvents = []; | 
 |     let event_list = [ | 
 |       'canplaythrough', | 
 |       'pause', | 
 |       'play', | 
 |       'error', | 
 |     ]; | 
 |     for (event_name of event_list) { | 
 |       let result = event_name; | 
 |       audio.addEventListener(event_name, event => { | 
 |         document.title = result; | 
 |         audio.testObserverEvents.push(result); | 
 |       }); | 
 |     } | 
 |  | 
 |     audio.src = 'media/bear-opus.ogg'; | 
 |  | 
 |     var timeOnFrozen = 0.0; | 
 |     audio.addEventListener('pause', () => { | 
 |       timeOnFrozen = audio.currentTime; | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // Load the media. | 
 |   { | 
 |     TitleWatcher title_watcher(shell()->web_contents(), u"canplaythrough"); | 
 |     title_watcher.AlsoWaitForTitle(u"error"); | 
 |     EXPECT_EQ(u"canplaythrough", title_watcher.WaitAndGetTitle()); | 
 |   } | 
 |  | 
 |   EXPECT_EQ(42, EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       audio.play(); | 
 |       while (audio.currentTime === 0) { | 
 |         await new Promise(r => setTimeout(r, 1)); | 
 |       } | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Navigate back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 |  | 
 |   // Check that the media position is not changed when the page is in cache. | 
 |   double duration1 = EvalJs(rfh_a, "timeOnFrozen;").ExtractDouble(); | 
 |   double duration2 = EvalJs(rfh_a, "audio.currentTime;").ExtractDouble(); | 
 |   EXPECT_LE(0.0, duration2 - duration1); | 
 |   EXPECT_GT(0.01, duration2 - duration1); | 
 |  | 
 |   // Resume the media. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "audio.play();")); | 
 |  | 
 |   // Confirm that the media pauses automatically when going to the cache. | 
 |   // TODO(hajimehoshi): Confirm that this media automatically resumes if | 
 |   // autoplay attribute exists. | 
 |   EXPECT_EQ(ListValueOf("canplaythrough", "play", "pause", "play"), | 
 |             EvalJs(rfh_a, "audio.testObserverEvents")); | 
 | } | 
 |  | 
 | // Check that a video suspends when the page goes to the cache and can resume | 
 | // after restored. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VideoSuspendAndResume) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |     var video = document.createElement('video'); | 
 |     document.body.appendChild(video); | 
 |  | 
 |     video.testObserverEvents = []; | 
 |     let event_list = [ | 
 |       'canplaythrough', | 
 |       'pause', | 
 |       'play', | 
 |       'error', | 
 |     ]; | 
 |     for (event_name of event_list) { | 
 |       let result = event_name; | 
 |       video.addEventListener(event_name, event => { | 
 |         document.title = result; | 
 |         // Ignore 'canplaythrough' event as we can randomly get extra | 
 |         // 'canplaythrough' events after playing here. | 
 |         if (result != 'canplaythrough') | 
 |           video.testObserverEvents.push(result); | 
 |       }); | 
 |     } | 
 |  | 
 |     video.src = 'media/bear.webm'; | 
 |  | 
 |     // Android bots can be very slow and the video is only 1s long. | 
 |     // This gives the first part of the test time to run before reaching | 
 |     // the end of the video. | 
 |     video.playbackRate = 0.1; | 
 |  | 
 |     var timeOnPagehide; | 
 |     window.addEventListener('pagehide', () => { | 
 |       timeOnPagehide = video.currentTime; | 
 |     }); | 
 |     var timeOnPageshow; | 
 |     window.addEventListener('pageshow', () => { | 
 |       timeOnPageshow = video.currentTime; | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // Load the media. | 
 |   { | 
 |     TitleWatcher title_watcher(shell()->web_contents(), u"canplaythrough"); | 
 |     title_watcher.AlsoWaitForTitle(u"error"); | 
 |     EXPECT_EQ(u"canplaythrough", title_watcher.WaitAndGetTitle()); | 
 |   } | 
 |  | 
 |   EXPECT_EQ(42, EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       video.play(); | 
 |       while (video.currentTime == 0) | 
 |         await new Promise(r => setTimeout(r, 1)); | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Sleep for 1s so that playing in BFCache can be detected. | 
 |   base::PlatformThread::Sleep(base::Seconds(1)); | 
 |  | 
 |   // Navigate back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 |  | 
 |   const double timeOnPagehide = | 
 |       EvalJs(rfh_a, "timeOnPagehide;").ExtractDouble(); | 
 |   const double timeOnPageshow = EvalJs(rfh_a, "timeOnPageshow").ExtractDouble(); | 
 |  | 
 |   // Make sure the video did not reach the end. If it did, our test is not | 
 |   // reliable. | 
 |   ASSERT_GT(1.0, timeOnPageshow); | 
 |  | 
 |   // Check that the duration of video played between pagehide and pageshow is | 
 |   // small. We waited for 1s so if it didn't stop in BFCache, it should be much | 
 |   // longer than this. | 
 |   const double playedDuration = timeOnPageshow - timeOnPagehide; | 
 |   EXPECT_LE(0.0, playedDuration); | 
 |   EXPECT_GT(0.02, playedDuration); | 
 |  | 
 |   // Resume the media. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |       // Ensure that the video does not auto-pause when it completes as that | 
 |       // would add an unexpected pause event. | 
 |       video.loop = true; | 
 |       video.play(); | 
 |     )")); | 
 |  | 
 |   // Confirm that the media pauses automatically when going to the cache. | 
 |   // TODO(hajimehoshi): Confirm that this media automatically resumes if | 
 |   // autoplay attribute exists. | 
 |   EXPECT_EQ(ListValueOf("play", "pause", "play"), | 
 |             EvalJs(rfh_a, "video.testObserverEvents")); | 
 | } | 
 |  | 
 | class SensorBackForwardCacheBrowserTest | 
 |     : public BackForwardCacheBrowserTest, | 
 |       public testing::WithParamInterface<bool> { | 
 |  protected: | 
 |   SensorBackForwardCacheBrowserTest() { | 
 |     WebContentsSensorProviderProxy::OverrideSensorProviderBinderForTesting( | 
 |         base::BindRepeating( | 
 |             &SensorBackForwardCacheBrowserTest::BindSensorProvider, | 
 |             base::Unretained(this))); | 
 |   } | 
 |  | 
 |   ~SensorBackForwardCacheBrowserTest() override { | 
 |     WebContentsSensorProviderProxy::OverrideSensorProviderBinderForTesting( | 
 |         base::NullCallback()); | 
 |   } | 
 |  | 
 |   void SetUpOnMainThread() override { | 
 |     provider_ = std::make_unique<device::FakeSensorProvider>(); | 
 |     provider_->SetAccelerometerData(1.0, 2.0, 3.0); | 
 |  | 
 |     BackForwardCacheBrowserTest::SetUpOnMainThread(); | 
 |   } | 
 |  | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     EnableFeatureAndSetParams(features::kAllowSensorsToEnterBfcache, "", ""); | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 |  | 
 |   std::unique_ptr<device::FakeSensorProvider> provider_; | 
 |  | 
 |  private: | 
 |   void BindSensorProvider( | 
 |       mojo::PendingReceiver<device::mojom::SensorProvider> receiver) { | 
 |     provider_->Bind(std::move(receiver)); | 
 |   } | 
 |  | 
 |   base::OnceClosure quit_closure_; | 
 | }; | 
 |  | 
 | // Tests that Accelerometer sensor is suspended while in bfcache. Note that | 
 | // we are only testing FakeSensor::Suspend() and FakeSensor::Resume() are | 
 | // called, and they have no implementation. | 
 | // | 
 | // TODO(crbug.com/364143617): Focus not retrieved on Android bots and thus | 
 | // sensors are not automatically resumed. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_AccelerometerPausedWhileCached \ | 
 |   DISABLED_AccelerometerPausedWhileCached | 
 | #else | 
 | #define MAYBE_AccelerometerPausedWhileCached AccelerometerPausedWhileCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, | 
 |                        MAYBE_AccelerometerPausedWhileCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a( | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/sensor.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // JS to cause a page to listen to, capture and validate accelerometer events. | 
 |   const std::string accelerometer_js = R"( | 
 |     sensor = new Accelerometer({ frequency: 60 }); | 
 |     sensor.addEventListener('reading', handleEvent); | 
 |     sensor.start(); | 
 |   )"; | 
 |   provider_->SetAccelerometerData(1.0, 2.0, 3.0); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), accelerometer_js)); | 
 |   ASSERT_EQ(1, EvalJs(rfh_a.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateAccelerometerData(1.0, 2.0, 3.1); | 
 |   ASSERT_EQ(2, EvalJs(rfh_a.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateAccelerometerData(1.0, 2.0, 3.2); | 
 |   ASSERT_EQ(3, EvalJs(rfh_a.get(), "waitForEventsPromise(3)")); | 
 |  | 
 |   // We should have 3 events with x=1.0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(1.0)")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   ASSERT_NE(rfh_a.get(), rfh_b.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   ASSERT_TRUE(provider_->WaitForAccelerometerSuspend(/*suspend=*/true)); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_EQ(rfh_a.get(), current_frame_host()); | 
 |  | 
 |   // Sensor must be activated once coming back to the page. | 
 |   ASSERT_TRUE(provider_->WaitForAccelerometerSuspend(/*suspend=*/false)); | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "sensor.activated")); | 
 |   // New update should arrive. | 
 |   provider_->UpdateAccelerometerData(1.0, 2.0, 3.4); | 
 |   // 4 to 5 events should arrive. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "waitForEventsPromise(4)")); | 
 | } | 
 |  | 
 | // Tests that Ambient Light sensor is suspended while in bfcache. Note that | 
 | // we are only testing FakeSensor::Suspend() and FakeSensor::Resume() are | 
 | // called, and they have no implementation. | 
 | // | 
 | // TODO(crbug.com/364143617): Focus not retrieved on Android bots and thus | 
 | // sensors are not automatically resumed. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_AmbientLightPausedWhileCached \ | 
 |   DISABLED_AmbientLightPausedWhileCached | 
 | #else | 
 | #define MAYBE_AmbientLightPausedWhileCached AmbientLightPausedWhileCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, | 
 |                        MAYBE_AmbientLightPausedWhileCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a( | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/sensor.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   const std::string ambient_light_js = R"( | 
 |     sensor = new AmbientLightSensor(); | 
 |     sensor.addEventListener('reading', handleEvent); | 
 |     sensor.start(); | 
 |   )"; | 
 |   provider_->SetAmbientLightSensorData(1.0); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), ambient_light_js)); | 
 |   ASSERT_EQ(1, EvalJs(rfh_a.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateAmbientLightSensorData(1.0); | 
 |   ASSERT_EQ(2, EvalJs(rfh_a.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateAmbientLightSensorData(1.0); | 
 |   ASSERT_EQ(3, EvalJs(rfh_a.get(), "waitForEventsPromise(3)")); | 
 |  | 
 |   // We should have 3 events with value=1.0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(1.0)")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   ASSERT_NE(rfh_a.get(), rfh_b.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   ASSERT_TRUE(provider_->WaitForAmbientLightSensorSuspend(/*suspend=*/true)); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ASSERT_TRUE(provider_->WaitForAmbientLightSensorSuspend(/*suspend=*/false)); | 
 |  | 
 |   // Sensor must be activated once coming back to the page. | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "sensor.activated")); | 
 |   // New update should arrive. | 
 |   provider_->UpdateAmbientLightSensorData(1.0); | 
 |   // 4 to 5 events should arrive. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "waitForEventsPromise(4)")); | 
 | } | 
 |  | 
 | // Tests that Linear Acceleration sensor is suspended while in bfcache. | 
 | // Note that we are only testing FakeSensor::Suspend() and | 
 | // FakeSensor::Resume() are called, and they have no implementation. | 
 | // | 
 | // TODO(crbug.com/364143617): Focus not retrieved on Android bots and thus | 
 | // sensors are not automatically resumed. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_LinearAccelerationPausedWhileCached \ | 
 |   DISABLED_LinearAccelerationPausedWhileCached | 
 | #else | 
 | #define MAYBE_LinearAccelerationPausedWhileCached \ | 
 |   LinearAccelerationPausedWhileCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, | 
 |                        MAYBE_LinearAccelerationPausedWhileCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a( | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/sensor.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   const std::string la_js = R"( | 
 |     sensor = new LinearAccelerationSensor({ frequency: 60 }); | 
 |     sensor.addEventListener('reading', handleEvent); | 
 |     sensor.start(); | 
 |   )"; | 
 |   provider_->SetLinearAccelerationSensorData(1.0, 2.0, 3.0); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), la_js)); | 
 |   ASSERT_EQ(1, EvalJs(rfh_a.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateLinearAccelerationSensorData(1.0, 2.0, 3.1); | 
 |   ASSERT_EQ(2, EvalJs(rfh_a.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateLinearAccelerationSensorData(1.0, 2.0, 3.2); | 
 |   ASSERT_EQ(3, EvalJs(rfh_a.get(), "waitForEventsPromise(3)")); | 
 |  | 
 |   // We should have 3 events with value=1.0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(1.0)")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   ASSERT_NE(rfh_a.get(), rfh_b.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   ASSERT_TRUE( | 
 |       provider_->WaitForLinearAccelerationSensorSuspend(/*suspend=*/true)); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ASSERT_TRUE( | 
 |       provider_->WaitForLinearAccelerationSensorSuspend(/*suspend=*/false)); | 
 |  | 
 |   // Sensor must be activated once coming back to the page. | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "sensor.activated")); | 
 |   // New update should arrive. | 
 |   provider_->UpdateLinearAccelerationSensorData(1.0, 2.0, 3.4); | 
 |   // 4 to 5 events should arrive. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "waitForEventsPromise(4)")); | 
 | } | 
 |  | 
 | // Tests that Gravity sensor is suspended while in bfcache. | 
 | // | 
 | // TODO(crbug.com/364143617): Focus not retrieved on Android bots and thus | 
 | // sensors are not automatically resumed. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_GravityPausedWhileCached DISABLED_GravityPausedWhileCached | 
 | #else | 
 | #define MAYBE_GravityPausedWhileCached GravityPausedWhileCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, | 
 |                        MAYBE_GravityPausedWhileCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a( | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/sensor.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   const std::string gravity_js = R"( | 
 |     sensor = new GravitySensor({ frequency: 60 }); | 
 |     sensor.addEventListener('reading', handleEvent); | 
 |     sensor.start(); | 
 |   )"; | 
 |   provider_->SetGravitySensorData(1.0, 2.0, 3.0); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), gravity_js)); | 
 |   ASSERT_EQ(1, EvalJs(rfh_a.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateGravitySensorData(1.0, 2.0, 3.1); | 
 |   ASSERT_EQ(2, EvalJs(rfh_a.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateGravitySensorData(1.0, 2.0, 3.2); | 
 |   ASSERT_EQ(3, EvalJs(rfh_a.get(), "waitForEventsPromise(3)")); | 
 |  | 
 |   // We should have 3 events with value=1.0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(1.0)")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   ASSERT_NE(rfh_a.get(), rfh_b.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   ASSERT_TRUE(provider_->WaitForGravitySensorSuspend(/*suspend=*/true)); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ASSERT_TRUE(provider_->WaitForGravitySensorSuspend(/*suspend=*/false)); | 
 |  | 
 |   // Sensor must be activated once coming back to the page. | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "sensor.activated")); | 
 |   // New update should arrive. | 
 |   provider_->UpdateGravitySensorData(1.0, 2.0, 3.4); | 
 |   // 4 to 5 events should arrive. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "waitForEventsPromise(4)")); | 
 | } | 
 |  | 
 | // Tests that Gyroscope sensor is suspended while in bfcache. Note that | 
 | // we are only testing FakeSensor::Suspend() and FakeSensor::Resume() are | 
 | // called, and they have no implementation. | 
 | // | 
 | // TODO(crbug.com/364143617): Focus not retrieved on Android bots and thus | 
 | // sensors are not automatically resumed. | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #define MAYBE_GyroscopePausedWhileCached DISABLED_GyroscopePausedWhileCached | 
 | #else | 
 | #define MAYBE_GyroscopePausedWhileCached GyroscopePausedWhileCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, | 
 |                        MAYBE_GyroscopePausedWhileCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a( | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/sensor.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   const std::string gyro_js = R"( | 
 |     sensor = new Gyroscope({ frequency: 60 }); | 
 |     sensor.addEventListener('reading', handleEvent); | 
 |     sensor.start(); | 
 |   )"; | 
 |   provider_->SetGyroscopeData(1.0, 2.0, 3.0); | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), gyro_js)); | 
 |   ASSERT_EQ(1, EvalJs(rfh_a.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateGyroscopeData(1.0, 2.0, 3.1); | 
 |   ASSERT_EQ(2, EvalJs(rfh_a.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateGyroscopeData(1.0, 2.0, 3.2); | 
 |   ASSERT_EQ(3, EvalJs(rfh_a.get(), "waitForEventsPromise(3)")); | 
 |  | 
 |   // We should have 3 events with value=1.0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(1.0)")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   ASSERT_NE(rfh_a.get(), rfh_b.get()); | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   ASSERT_TRUE(provider_->WaitForGyroscopeSuspend(/*suspend=*/true)); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ASSERT_TRUE(provider_->WaitForGyroscopeSuspend(/*suspend=*/false)); | 
 |  | 
 |   // Sensor must be activated once coming back to the page. | 
 |   ASSERT_EQ(true, EvalJs(rfh_a.get(), "sensor.activated")); | 
 |   // New update should arrive. | 
 |   provider_->UpdateGyroscopeData(1.0, 2.0, 3.4); | 
 |   // 4 to 5 events should arrive. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), "waitForEventsPromise(4)")); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, OrientationCached) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |     window.addEventListener("deviceorientation", () => {}); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   EXPECT_FALSE(delete_observer_rfh_a.deleted()); | 
 |   EXPECT_THAT(rfh_a, InBackForwardCache()); | 
 | } | 
 |  | 
 | // Tests that the orientation sensor is suspended while in bfcache. | 
 | // | 
 | // This sets some JS functions in the pages to enable the sensors, capture and | 
 | // validate the events. The a-page should only receive events with alpha=0, the | 
 | // b-page is allowed to receive any alpha value. The test captures 3 events in | 
 | // the a-page, then navigates to the b-page and changes the reading to have | 
 | // alpha=1. While on the b-page it captures 3 more events. If the a-page is | 
 | // still receiving events it should receive one or more of these. Finally it | 
 | // resets the reading back to have alpha=0 and navigates back to the a-page and | 
 | // captures 3 more events and verifies that all events on the a-page have | 
 | // alpha=0. | 
 | // TODO(crbug.com/330801676): Flaky on macOS. | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_SensorPausedWhileCached DISABLED_SensorPausedWhileCached | 
 | #else | 
 | #define MAYBE_SensorPausedWhileCached SensorPausedWhileCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, | 
 |                        MAYBE_SensorPausedWhileCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a( | 
 |       https_server()->GetURL("a.test", "/back_forward_cache/sensor.html")); | 
 |   GURL url_b( | 
 |       https_server()->GetURL("b.test", "/back_forward_cache/sensor.html")); | 
 |  | 
 |   provider_->SetRelativeOrientationSensorData(0, 0, 0); | 
 |  | 
 |   const std::string orientation_js = R"( | 
 |     // Override the function. | 
 |     function handleEvent(event) { | 
 |         values.push(event.alpha); | 
 |         if (pendingResolve !== null) { | 
 |           pendingResolve('event'); | 
 |           pendingResolve = null; | 
 |         } | 
 |     } | 
 |     window.addEventListener('deviceorientation', handleEvent); | 
 |   )"; | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a.get()); | 
 |  | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), orientation_js)); | 
 |  | 
 |   // Collect 3 orientation events. | 
 |   ASSERT_EQ(1, EvalJs(rfh_a.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2); | 
 |   ASSERT_EQ(2, EvalJs(rfh_a.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4); | 
 |   ASSERT_EQ(3, EvalJs(rfh_a.get(), "waitForEventsPromise(3)")); | 
 |   // We should have 3 events with alpha=0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(0)")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   ASSERT_FALSE(delete_observer_rfh_a.deleted()); | 
 |   ASSERT_THAT(rfh_a.get(), InBackForwardCache()); | 
 |   ASSERT_NE(rfh_a.get(), rfh_b.get()); | 
 |  | 
 |   // Change the orientation data before executing |orientation_js|, otherwise a | 
 |   // deviceorientation event might be fired before the call below and the first | 
 |   // registered event will have the previous data (0 0 0.4). | 
 |   provider_->SetRelativeOrientationSensorData(1, 0, 0); | 
 |   ASSERT_TRUE(ExecJs(rfh_b.get(), orientation_js)); | 
 |  | 
 |   // Collect 3 orientation events. | 
 |   ASSERT_EQ(1, EvalJs(rfh_b.get(), "waitForEventsPromise(1)")); | 
 |   provider_->UpdateRelativeOrientationSensorData(1, 0, 0.2); | 
 |   ASSERT_EQ(2, EvalJs(rfh_b.get(), "waitForEventsPromise(2)")); | 
 |   provider_->UpdateRelativeOrientationSensorData(1, 0, 0.4); | 
 |   ASSERT_EQ(3, EvalJs(rfh_b.get(), "waitForEventsPromise(3)")); | 
 |   // We should have 3 events with alpha=1. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_b.get(), "validateEvents(1)")); | 
 |  | 
 |   // 3) Go back to A. | 
 |   provider_->UpdateRelativeOrientationSensorData(0, 0, 0); | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ASSERT_EQ(rfh_a.get(), current_frame_host()); | 
 |  | 
 |   // Collect 3 orientation events. | 
 |   provider_->UpdateRelativeOrientationSensorData(0, 0, 0); | 
 |   // There are 2 processes so, it's possible that more events crept in. So we | 
 |   // capture how many there are at this point and uses to wait for at least 3 | 
 |   // more. | 
 |   int count = EvalJs(rfh_a.get(), "waitForEventsPromise(4)").ExtractInt(); | 
 |   provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2); | 
 |   count++; | 
 |   ASSERT_EQ(count, EvalJs(rfh_a.get(), base::StringPrintf( | 
 |                                            "waitForEventsPromise(%d)", count))); | 
 |   provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4); | 
 |   count++; | 
 |   ASSERT_EQ(count, EvalJs(rfh_a.get(), base::StringPrintf( | 
 |                                            "waitForEventsPromise(%d)", count))); | 
 |  | 
 |   // We should have the earlier 3 plus another 3 events with alpha=0. | 
 |   ASSERT_EQ("pass", EvalJs(rfh_a.get(), "validateEvents(0)")); | 
 | } | 
 |  | 
 | // This tests that even if a page initializes WebRTC, tha page can be cached as | 
 | // long as it doesn't make a connection. | 
 | // On the Android test environments, the test might fail due to IP restrictions. | 
 | // See the discussion at http://crrev.com/c/2564926. | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 |  | 
 | // TODO(crbug.com/40183520): The test is consistently failing on some Mac | 
 | // bots. | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_TrivialRTCPeerConnectionCached \ | 
 |   DISABLED_TrivialRTCPeerConnectionCached | 
 | #else | 
 | #define MAYBE_TrivialRTCPeerConnectionCached TrivialRTCPeerConnectionCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_TrivialRTCPeerConnectionCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |  | 
 |   // Create an RTCPeerConnection without starting a connection. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "const pc1 = new RTCPeerConnection()")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // RTCPeerConnection object, that is created before being put into the cache, | 
 |   // is still available. | 
 |   EXPECT_EQ("success", EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       const pc1 = new RTCPeerConnection(); | 
 |       const pc2 = new RTCPeerConnection(); | 
 |       pc1.onicecandidate = e => { | 
 |         if (e.candidate) | 
 |           pc2.addIceCandidate(e.candidate); | 
 |       } | 
 |       pc2.onicecandidate = e => { | 
 |         if (e.candidate) | 
 |           pc1.addIceCandidate(e.candidate); | 
 |       } | 
 |       pc1.addTransceiver("audio"); | 
 |       const connectionEstablished = new Promise((resolve, reject) => { | 
 |         pc1.oniceconnectionstatechange = () => { | 
 |           const state = pc1.iceConnectionState; | 
 |           switch (state) { | 
 |           case "connected": | 
 |           case "completed": | 
 |             resolve(); | 
 |             break; | 
 |           case "failed": | 
 |           case "disconnected": | 
 |           case "closed": | 
 |             reject(state); | 
 |             break; | 
 |           } | 
 |         } | 
 |       }); | 
 |       await pc1.setLocalDescription(); | 
 |       await pc2.setRemoteDescription(pc1.localDescription); | 
 |       await pc2.setLocalDescription(); | 
 |       await pc1.setRemoteDescription(pc2.localDescription); | 
 |       try { | 
 |         await connectionEstablished; | 
 |       } catch (e) { | 
 |         resolve("fail " + e); | 
 |         return; | 
 |       } | 
 |       resolve("success"); | 
 |     }); | 
 |   )")); | 
 | } | 
 | #endif  // !BUILDFLAG(IS_ANDROID) | 
 |  | 
 | // This tests that a page using WebRTC and creating actual connections cannot be | 
 | // cached. | 
 | // On the Android test environments, the test might fail due to IP restrictions. | 
 | // See the discussion at http://crrev.com/c/2564926. | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 |  | 
 | // TODO(crbug.com/40183520): The test is consistently failing on some Mac | 
 | // bots. | 
 | // This test uses MediaStreamTrack, so the test class is | 
 | // `BackForwardCacheMediaTest`. | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_NonTrivialRTCPeerConnectionNotCached \ | 
 |   DISABLED_NonTrivialRTCPeerConnectionNotCached | 
 | #else | 
 | #define MAYBE_NonTrivialRTCPeerConnectionNotCached \ | 
 |   NonTrivialRTCPeerConnectionNotCached | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_NonTrivialRTCPeerConnectionNotCached) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // Create an RTCPeerConnection with starting a connection. | 
 |   EXPECT_EQ("success", EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       const pc1 = new RTCPeerConnection(); | 
 |       const pc2 = new RTCPeerConnection(); | 
 |       pc1.onicecandidate = e => { | 
 |         if (e.candidate) | 
 |           pc2.addIceCandidate(e.candidate); | 
 |       } | 
 |       pc2.onicecandidate = e => { | 
 |         if (e.candidate) | 
 |           pc1.addIceCandidate(e.candidate); | 
 |       } | 
 |       pc1.addTransceiver("audio"); | 
 |       const connectionEstablished = new Promise(resolve => { | 
 |         pc1.oniceconnectionstatechange = () => { | 
 |           const state = pc1.iceConnectionState; | 
 |           switch (state) { | 
 |           case "connected": | 
 |           case "completed": | 
 |             resolve(); | 
 |             break; | 
 |           case "failed": | 
 |           case "disconnected": | 
 |           case "closed": | 
 |             reject(state); | 
 |             break; | 
 |           } | 
 |         } | 
 |       }); | 
 |       await pc1.setLocalDescription(); | 
 |       await pc2.setRemoteDescription(pc1.localDescription); | 
 |       await pc2.setLocalDescription(); | 
 |       await pc1.setRemoteDescription(pc2.localDescription); | 
 |       await connectionEstablished; | 
 |       try { | 
 |         await connectionEstablished; | 
 |       } catch (e) { | 
 |         resolve("fail " + e); | 
 |         return; | 
 |       } | 
 |       resolve("success"); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // - Page A should not be in the cache. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // A live MediaStreamTrack blocks BFCache. | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebRTC, | 
 |        blink::scheduler::WebSchedulerTrackedFeature::kLiveMediaStreamTrack}, | 
 |       {}, {}, {}, FROM_HERE); | 
 | } | 
 | #endif  // !BUILDFLAG(IS_ANDROID) | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebLocksNotCached) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // Wait for the page to acquire a lock and ensure that it continues to do so. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, R"( | 
 |     const never_resolved = new Promise(resolve => {}); | 
 |     new Promise(continue_test => { | 
 |       navigator.locks.request('test', async () => { | 
 |         continue_test(); | 
 |         await never_resolved; | 
 |       }); | 
 |     }) | 
 |   )")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // - Page A should not be in the cache. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebLocks}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | enum TestAuthenticatorBehavior { | 
 |   kErrorOut, | 
 |   kStallRequest, | 
 | }; | 
 |  | 
 | // An implementation of blink::mojom::Authenticator that errors all requests, | 
 | // this can be modified to stall all requests through SetBehavior. | 
 | class TestAuthenticator : public blink::mojom::Authenticator { | 
 |  public: | 
 |   TestAuthenticator() = default; | 
 |   ~TestAuthenticator() override = default; | 
 |  | 
 |   void BindAuthenticator( | 
 |       mojo::PendingReceiver<blink::mojom::Authenticator> receiver) { | 
 |     receiver_.Bind(std::move(receiver)); | 
 |   } | 
 |  | 
 |   void SetBehavior(TestAuthenticatorBehavior behavior) { behavior_ = behavior; } | 
 |  | 
 |  private: | 
 |   // blink::mojom::Authenticator: | 
 |   void MakeCredential( | 
 |       blink::mojom::PublicKeyCredentialCreationOptionsPtr options, | 
 |       MakeCredentialCallback callback) override { | 
 |     if (behavior_ == kStallRequest) { | 
 |       pending_make_credential_callback_ = std::move(callback); | 
 |     } else { | 
 |       std::move(callback).Run(blink::mojom::AuthenticatorStatus::ABORT_ERROR, | 
 |                               nullptr, nullptr); | 
 |     } | 
 |   } | 
 |   void GetCredential(blink::mojom::PublicKeyCredentialRequestOptionsPtr options, | 
 |                      GetCredentialCallback callback) override { | 
 |     if (behavior_ == kStallRequest) { | 
 |       pending_get_credential_callback_ = std::move(callback); | 
 |     } else { | 
 |       auto get_assertion_response = blink::mojom::GetAssertionResponse::New( | 
 |           blink::mojom::AuthenticatorStatus::ABORT_ERROR, nullptr, nullptr); | 
 |       auto get_credential_response = | 
 |           blink::mojom::GetCredentialResponse::NewGetAssertionResponse( | 
 |               std::move(get_assertion_response)); | 
 |       std::move(callback).Run(std::move(get_credential_response)); | 
 |     } | 
 |   } | 
 |   void GetClientCapabilities(GetClientCapabilitiesCallback callback) override {} | 
 |   void Report(blink::mojom::PublicKeyCredentialReportOptionsPtr options, | 
 |               ReportCallback callback) override {} | 
 |   void IsUserVerifyingPlatformAuthenticatorAvailable( | 
 |       IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) override { | 
 |   } | 
 |   void IsConditionalMediationAvailable( | 
 |       IsConditionalMediationAvailableCallback callback) override {} | 
 |   void Cancel() override {} | 
 |  | 
 |   MakeCredentialCallback pending_make_credential_callback_; | 
 |   GetCredentialCallback pending_get_credential_callback_; | 
 |   TestAuthenticatorBehavior behavior_ = TestAuthenticatorBehavior::kErrorOut; | 
 |   mojo::Receiver<blink::mojom::Authenticator> receiver_{this}; | 
 | }; | 
 |  | 
 | class TestAuthenticatorContentBrowserClient | 
 |     : public ContentBrowserTestContentBrowserClient { | 
 |  public: | 
 |   TestAuthenticatorContentBrowserClient() = default; | 
 |   ~TestAuthenticatorContentBrowserClient() override = default; | 
 |  | 
 |   void RegisterBrowserInterfaceBindersForFrame( | 
 |       content::RenderFrameHost* render_frame_host, | 
 |       mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override { | 
 |     ContentBrowserTestContentBrowserClient:: | 
 |         RegisterBrowserInterfaceBindersForFrame(render_frame_host, map); | 
 |     // Override binding for blink::mojom::Authenticator. | 
 |     map->Add<blink::mojom::Authenticator>( | 
 |         base::BindRepeating(&TestAuthenticatorContentBrowserClient::Bind, | 
 |                             weak_factory_.GetWeakPtr())); | 
 |   } | 
 |  | 
 |   void Bind(content::RenderFrameHost* render_frame_host, | 
 |             mojo::PendingReceiver<blink::mojom::Authenticator> receiver) { | 
 |     authenticator_.BindAuthenticator(std::move(receiver)); | 
 |   } | 
 |  | 
 |   void SetBehavior(TestAuthenticatorBehavior behavior) { | 
 |     authenticator_.SetBehavior(behavior); | 
 |   } | 
 |  | 
 |  private: | 
 |   TestAuthenticator authenticator_; | 
 |   base::WeakPtrFactory<TestAuthenticatorContentBrowserClient> weak_factory_{ | 
 |       this}; | 
 | }; | 
 |  | 
 | class BackForwardCacheWebAuthnBrowserTest : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   void SetUpOnMainThread() override { | 
 |     BackForwardCacheBrowserTest::SetUpOnMainThread(); | 
 |     browser_client_ = std::make_unique<TestAuthenticatorContentBrowserClient>(); | 
 |     ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |     // The default test shell() is created and bound in SetUp.  The | 
 |     // ContentBrowserTestContentBrowserClient requires that | 
 |     // GetShellContentBrowserClientInstances().size() > 1.  Therefore, the only | 
 |     // work around is to either perform an initial navigation or create a new | 
 |     // window. | 
 |     GURL initial_url(https_server()->GetURL("initial.com", "/title1.html")); | 
 |     ASSERT_TRUE(NavigateToURL(shell(), initial_url)); | 
 |   } | 
 |  | 
 |   void SetBehavior(TestAuthenticatorBehavior behavior) { | 
 |     browser_client_->SetBehavior(behavior); | 
 |   } | 
 |  | 
 |  private: | 
 |   std::unique_ptr<TestAuthenticatorContentBrowserClient> browser_client_; | 
 | }; | 
 |  | 
 | // Tests that an ongoing WebAuthn get assertion request disables BFcache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebAuthnBrowserTest, | 
 |                        GetAssertion_NoCachingDuringRequest) { | 
 |   SetBehavior(kStallRequest); | 
 |   GURL url_a(https_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Leave a WebAuthn get assertion request pending. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     navigator.credentials.get({ publicKey: { | 
 |       challenge: new TextEncoder().encode("speedrun a game"), | 
 |       userVerification: "discouraged", | 
 |       allowCredentials: [{type: "public-key", id: Uint8Array.from([1, 2, 3])}], | 
 |     }}); | 
 |   )", | 
 |                      EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // - Page A should not be in the cache. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebAuthentication}, {}, | 
 |       {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests that after a WebAuthn get assertion request completes, BFcache is not | 
 | // disabled. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebAuthnBrowserTest, | 
 |                        GetAssertion_CacheAfterRequest) { | 
 |   GURL url_a(https_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Complete a WebAuthn get assertion request. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     navigator.credentials.get({ publicKey: { | 
 |       challenge: new TextEncoder().encode("speedrun a game"), | 
 |       userVerification: "discouraged", | 
 |       allowCredentials: [{type: "public-key", id: Uint8Array.from([1, 2, 3])}], | 
 |     }}).catch(() => {}); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // Tests that an ongoing WebAuthn make credential request disables BFcache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebAuthnBrowserTest, | 
 |                        MakeCredential_NoCachingDuringRequest) { | 
 |   SetBehavior(kStallRequest); | 
 |   GURL url_a(https_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Leave a WebAuthn make credential request pending. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     navigator.credentials.create({ publicKey: { | 
 |       challenge: new TextEncoder().encode("speedrun a game"), | 
 |       userVerification: "discouraged", | 
 |       rp: { name: "Acme"}, | 
 |       user: { | 
 |         id: new TextEncoder().encode("1234"), | 
 |         name: "fox", | 
 |         displayName: "Fox McCloud" | 
 |       }, | 
 |       pubKeyCredParams: [{ type: "public-key", alg: -7}], | 
 |     }}); | 
 |   )", | 
 |                      EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // - Page A should not be in the cache. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kWebAuthentication}, {}, | 
 |       {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Tests that after a WebAuthn make credential request completes, BFcache is not | 
 | // disabled. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheWebAuthnBrowserTest, | 
 |                        MakeCredential_CacheAfterRequest) { | 
 |   GURL url_a(https_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Leave a WebAuthn make credential request pending. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     navigator.credentials.create({ publicKey: { | 
 |       challenge: new TextEncoder().encode("speedrun a game"), | 
 |       userVerification: "discouraged", | 
 |       rp: { name: "Acme"}, | 
 |       user: { | 
 |         id: new TextEncoder().encode("1234"), | 
 |         name: "fox", | 
 |         displayName: "Fox McCloud" | 
 |       }, | 
 |       pubKeyCredParams: [{ type: "public-key", alg: -7}], | 
 |     }}).catch(() => {}); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // TODO(crbug.com/40937711): Reenable. This is flaky because we block on | 
 | // the permission request, not on API usage. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DISABLED_WebMidiNotCached) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // Request access to MIDI. This should prevent the page from entering the | 
 |   // BackForwardCache. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "navigator.requestMIDIAccess()", | 
 |                      EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // - Page A should not be in the cache. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kRequestedMIDIPermission}, | 
 |       {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // https://crbug.com/1410441 | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DISABLED_PresentationConnectionClosed) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |   GURL url_a(https_server()->GetURL( | 
 |       "a.test", "/back_forward_cache/presentation_controller.html")); | 
 |  | 
 |   // Navigate to A (presentation controller page). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   auto* rfh_a = current_frame_host(); | 
 |   // Start a presentation connection in A. | 
 |   MockPresentationServiceDelegate mock_presentation_service_delegate; | 
 |   auto& presentation_service = rfh_a->GetPresentationServiceForTesting(); | 
 |   presentation_service.SetControllerDelegateForTesting( | 
 |       &mock_presentation_service_delegate); | 
 |   EXPECT_CALL(mock_presentation_service_delegate, StartPresentation(_, _, _)); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "presentationRequest.start().then(setConnection)", | 
 |                      EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); | 
 |   // Ensure that the above script runs before continuing. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "var foo = 42;")); | 
 |  | 
 |   // Send a mock connection to the renderer. | 
 |   MockPresentationConnection mock_controller_connection; | 
 |   mojo::Receiver<PresentationConnection> controller_connection_receiver( | 
 |       &mock_controller_connection); | 
 |   mojo::Remote<PresentationConnection> receiver_connection; | 
 |   const std::string presentation_connection_id = "foo"; | 
 |   presentation_service.OnStartPresentationSucceeded( | 
 |       presentation_service.start_presentation_request_id_, | 
 |       PresentationConnectionResult::New( | 
 |           blink::mojom::PresentationInfo::New(GURL("fake-url"), | 
 |                                               presentation_connection_id), | 
 |           controller_connection_receiver.BindNewPipeAndPassRemote(), | 
 |           receiver_connection.BindNewPipeAndPassReceiver())); | 
 |  | 
 |   // Navigate to B, make sure that the connection started in A is closed. | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |   EXPECT_CALL( | 
 |       mock_controller_connection, | 
 |       DidClose(blink::mojom::PresentationConnectionCloseReason::WENT_AWAY)); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_FALSE(delete_observer_rfh_a.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Navigate back to A. Ensure that connection state has been updated | 
 |   // accordingly. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_FALSE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_EQ(presentation_connection_id, EvalJs(rfh_a, "connection.id")); | 
 |   EXPECT_EQ("closed", EvalJs(rfh_a, "connection.state")); | 
 |   EXPECT_TRUE(EvalJs(rfh_a, "connectionClosed").ExtractBool()); | 
 |  | 
 |   // Try to start another connection, should successfully reach the browser side | 
 |   // PresentationServiceDelegate. | 
 |   EXPECT_CALL(mock_presentation_service_delegate, | 
 |               ReconnectPresentation(_, presentation_connection_id, _, _)); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "presentationRequest.reconnect(connection.id);", | 
 |                      EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   // Reset |presentation_service|'s controller delegate so that it won't try to | 
 |   // call Reset() on it on destruction time. | 
 |   presentation_service.OnDelegateDestroyed(); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheIfSpeechRecognitionIsStarted) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to url_a. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // 2) Start SpeechRecognition. | 
 |   EXPECT_EQ(42, EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       var r = new webkitSpeechRecognition(); | 
 |       r.start(); | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 3) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 4) The page uses SpeechRecognition so it should be deleted. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // 5) Go back to the page with SpeechRecognition. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kSpeechRecognizer}, {}, {}, | 
 |       {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        CanCacheIfSpeechRecognitionIsNotStarted) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to url_a. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); | 
 |  | 
 |   // 2) Initialise SpeechRecognition but don't start it yet. | 
 |   EXPECT_EQ(42, EvalJs(rfh_a, R"( | 
 |     new Promise(async resolve => { | 
 |       var r = new webkitSpeechRecognition(); | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 3) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 4) The page didn't start using SpeechRecognition so it shouldn't be deleted | 
 |   // and enter BackForwardCache. | 
 |   EXPECT_FALSE(delete_observer_rfh_a.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 5) Go back to the page with SpeechRecognition. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 |  | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | // This test is not important for Chrome OS if TTS is called in content. For | 
 | // more details refer (content/browser/speech/tts_platform_impl.cc). | 
 | #if BUILDFLAG(IS_CHROMEOS) | 
 | #define MAYBE_CacheIfUsingSpeechSynthesis DISABLED_CacheIfUsingSpeechSynthesis | 
 | #else | 
 | #define MAYBE_CacheIfUsingSpeechSynthesis CacheIfUsingSpeechSynthesis | 
 | #endif  // BUILDFLAG(IS_CHROMEOS) | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_CacheIfUsingSpeechSynthesis) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to a page and start using SpeechSynthesis. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   EXPECT_EQ(42, EvalJs(rfh_a.get(), R"( | 
 |     new Promise(async resolve => { | 
 |       var u = new SpeechSynthesisUtterance(" "); | 
 |       speechSynthesis.speak(u); | 
 |       resolve(42); | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // 3) Go back to the page with SpeechSynthesis and ensure the page is | 
 |   // restored if the flag is on. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   // TODO(crbug.com/40254716): Test that onend callback is fired upon restore. | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        DoesNotCacheIfRunFileChooserIsInvoked) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to url_a and open file chooser. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted_rfh_a(rfh_a); | 
 |   content::BackForwardCacheDisabledTester tester; | 
 |  | 
 |   // 2) Bind FileChooser to RenderFrameHost. | 
 |   mojo::Remote<blink::mojom::FileChooser> chooser = | 
 |       FileChooserImpl::CreateBoundForTesting(rfh_a); | 
 |  | 
 |   auto quit_run_loop = [](base::OnceClosure callback, | 
 |                           blink::mojom::FileChooserResultPtr result) { | 
 |     std::move(callback).Run(); | 
 |   }; | 
 |  | 
 |   // 3) Run OpenFileChooser and wait till its run. | 
 |   base::RunLoop run_loop; | 
 |   chooser->OpenFileChooser( | 
 |       blink::mojom::FileChooserParams::New(), | 
 |       base::BindOnce(quit_run_loop, run_loop.QuitClosure())); | 
 |   run_loop.Run(); | 
 |  | 
 |   // 4) rfh_a should be disabled for BackForwardCache after opening file | 
 |   // chooser. | 
 |   EXPECT_TRUE(rfh_a->IsBackForwardCacheDisabled()); | 
 |   auto reason = BackForwardCacheDisable::DisabledReason( | 
 |       BackForwardCacheDisable::DisabledReasonId::kFileChooser); | 
 |   EXPECT_TRUE(tester.IsDisabledForFrameWithReason( | 
 |       rfh_a->GetProcess()->GetDeprecatedID(), rfh_a->GetRoutingID(), reason)); | 
 |  | 
 |   // 5) Navigate to B having the file chooser open. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |  | 
 |   // The page uses FileChooser so it should be deleted. | 
 |   deleted_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // 6) Go back to the page with FileChooser. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {}, | 
 |                     {}, {reason}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // TODO(crbug.com/40285326): This fails with the field trial testing config. | 
 | class BackForwardCacheBrowserTestNoTestingConfig | 
 |     : public BackForwardCacheBrowserTest { | 
 |  public: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |     command_line->AppendSwitch("disable-field-trial-config"); | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestNoTestingConfig, | 
 |                        CacheWithMediaSession) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page using MediaSession. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |     navigator.mediaSession.metadata = new MediaMetadata({ | 
 |       artwork: [ | 
 |         {src: "test_image.jpg", sizes: "1x1", type: "image/jpeg"}, | 
 |         {src: "test_image.jpg", sizes: "10x10", type: "image/jpeg"} | 
 |       ] | 
 |     }); | 
 |   )")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 |   // Check the media session state is reserved. | 
 |   EXPECT_EQ("10x10", EvalJs(rfh_a.get(), R"( | 
 |     navigator.mediaSession.metadata.artwork[1].sizes; | 
 |   )")); | 
 | } | 
 |  | 
 | class BackForwardCacheBrowserTestWithSupportedFeatures | 
 |     : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features", | 
 |                               "broadcastchannel,keyboardlock"); | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSupportedFeatures, | 
 |                        CacheWithSpecifiedFeatures) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to the page A with BroadcastChannel. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted(rfh_a); | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back to the page A | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // 4) Use KeyboardLock. | 
 |   AcquireKeyboardLock(rfh_a); | 
 |  | 
 |   // 5) Navigate away again. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_FALSE(deleted.deleted()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 6) Go back to the page A again. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a, current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | class BackForwardCacheBrowserTestWithNoSupportedFeatures | 
 |     : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     // Specify empty supported features explicitly. | 
 |     EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features", | 
 |                               ""); | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithNoSupportedFeatures, | 
 |                        DontCache) { | 
 |   ASSERT_TRUE(CreateHttpsServer()->Start()); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", kBlockingPagePath)); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to the page A with a blocking feature. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a1 = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted_a1(rfh_a1); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   deleted_a1.WaitUntilDeleted(); | 
 |  | 
 |   // 3) Go back to the page A | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {kBlockingReasonEnum}, {}, {}, {}, FROM_HERE); | 
 |  | 
 |   RenderFrameHostImpl* rfh_a2 = current_frame_host(); | 
 |   RenderFrameDeletedObserver deleted_a2(rfh_a2); | 
 |  | 
 |   // 4) Use KeyboardLock. | 
 |   AcquireKeyboardLock(rfh_a2); | 
 |  | 
 |   // 5) Navigate away again. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   deleted_a2.WaitUntilDeleted(); | 
 |  | 
 |   // 6) Go back to the page A again. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kBlocklistedFeatures}, | 
 |       {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock, | 
 |        kBlockingReasonEnum}, | 
 |       {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | class BackForwardCacheBrowserTestWithMediaSession | 
 |     : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   void PlayVideoNavigateAndGoBack() { | 
 |     MediaSession* media_session = MediaSession::Get(shell()->web_contents()); | 
 |     ASSERT_TRUE(media_session); | 
 |  | 
 |     content::MediaStartStopObserver start_observer( | 
 |         shell()->web_contents(), MediaStartStopObserver::Type::kStart); | 
 |     EXPECT_TRUE(ExecJs(current_frame_host(), | 
 |                        "document.querySelector('#long-video').play();")); | 
 |     start_observer.Wait(); | 
 |  | 
 |     content::MediaStartStopObserver stop_observer( | 
 |         shell()->web_contents(), MediaStartStopObserver::Type::kStop); | 
 |     media_session->Suspend(MediaSession::SuspendType::kSystem); | 
 |     stop_observer.Wait(); | 
 |  | 
 |     // Navigate away. | 
 |     EXPECT_TRUE(NavigateToURL( | 
 |         shell(), embedded_test_server()->GetURL("b.test", "/title1.html"))); | 
 |  | 
 |     // Go back. | 
 |     ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession, | 
 |                        CacheWhenMediaSessionPlaybackStateIsChanged) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.test", "/title1.html"))); | 
 |  | 
 |   // 2) Update the playback state change. | 
 |   EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), R"( | 
 |     navigator.mediaSession.playbackState = 'playing'; | 
 |   )")); | 
 |  | 
 |   // 3) Navigate away. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // 4) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |  | 
 |   // The page is restored since a MediaSession service is not used. | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession, | 
 |                        CacheWhenMediaSessionServiceIsNotUsed) { | 
 |   // There are sometimes unexpected messages from a renderer to the browser, | 
 |   // which caused test flakiness. | 
 |   // TODO(crbug.com/40793577): Fix the test flakiness. | 
 |   DoNotFailForUnexpectedMessagesWhileCached(); | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |  | 
 |   // 1) Navigate to a page using MediaSession. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.test", "/media/session/media-session.html"))); | 
 |  | 
 |   // Play the media once, but without setting any callbacks to the MediaSession. | 
 |   // In this case, a MediaSession service is not used. | 
 |   PlayVideoNavigateAndGoBack(); | 
 |  | 
 |   // The page is restored since a MediaSession service is not used. | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | #if BUILDFLAG(ENABLE_VR) | 
 |  | 
 | class BackForwardCacheBrowserTestWithWebXr | 
 |     : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     EnableFeatureAndSetParams(features::kWebXr, "", ""); | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithWebXr, | 
 |                        DoesCacheIfXrAttributeWasAccessed) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("a.com", "/title1.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Access navigator.xr without calling any methods. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), "navigator.xr")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(rfh_a.get(), current_frame_host()); | 
 |   ExpectRestored(FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithWebXr, | 
 |                        DoesNotCacheIfXrIsSessionSupportedWasCalled) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("a.com", "/title1.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call isSessionSupported. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), "navigator.xr.isSessionSupported('inline')")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page called a WebXR method so it should be deleted. | 
 |   EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebXR}, {}, | 
 |                     {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithWebXr, | 
 |                        DoesNotCacheIfXrRequestSessionWasCalled) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |  | 
 |   // 1) Navigate to an empty page. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("a.com", "/title1.html"))); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // Call requestSession. | 
 |   EXPECT_TRUE(ExecJs(rfh_a.get(), "navigator.xr.requestSession('inline')")); | 
 |  | 
 |   // 2) Navigate away. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), https_server()->GetURL("b.com", "/title1.html"))); | 
 |  | 
 |   // The page called a WebXR method so it should be deleted. | 
 |   EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // 3) Go back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, | 
 |                     {blink::scheduler::WebSchedulerTrackedFeature::kWebXR}, {}, | 
 |                     {}, {}, FROM_HERE); | 
 | } | 
 | #endif | 
 |  | 
 | }  // namespace content |