| // 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 "content/browser/back_forward_cache_browsertest.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "content/browser/generic_sensor/sensor_provider_proxy_impl.h" |
| #include "content/browser/presentation/presentation_test_utils.h" |
| #include "content/browser/renderer_host/back_forward_cache_disable.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/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_utils.h" |
| #include "content/public/test/media_start_stop_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/bluetooth_adapter_factory.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_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 "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 |
| |
| using testing::_; |
| using testing::Each; |
| using testing::ElementsAre; |
| using testing::Not; |
| using testing::UnorderedElementsAreArray; |
| |
| namespace content { |
| |
| using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason; |
| |
| class BackForwardCacheDedicatedWorkerFlagBrowserTest |
| : public BackForwardCacheBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| EnableFeatureAndSetParams(blink::features::kBackForwardCacheDedicatedWorker, |
| "", ""); |
| if (IsDedicatedWorkerEnabled()) { |
| EnableFeatureAndSetParams( |
| blink::features::kBackForwardCacheDedicatedWorker, "", ""); |
| } else { |
| DisableFeature(blink::features::kBackForwardCacheDedicatedWorker); |
| } |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| bool IsDedicatedWorkerEnabled() { return GetParam(); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| BackForwardCacheDedicatedWorkerFlagBrowserTest, |
| testing::Bool()); |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheDedicatedWorkerFlagBrowserTest, |
| 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. |
| if (IsDedicatedWorkerEnabled()) { |
| EXPECT_EQ(rfh.get(), current_frame_host()); |
| ExpectRestored(FROM_HERE); |
| } else { |
| // The page with the dedicated worker should be deleted (not cached). |
| ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted()); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature:: |
| kDedicatedWorkerOrWorklet}, |
| {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| // The bool parameter is used for switching PlzDedicatedWorker. |
| class BackForwardCacheWithDedicatedWorkerBrowserTest |
| : public BackForwardCacheBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| const int kMaxBufferedBytesPerProcess = 10000; |
| const base::TimeDelta kGracePeriodToFinishLoading = base::Seconds(5); |
| |
| BackForwardCacheWithDedicatedWorkerBrowserTest() { server_.Start(); } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| EnableFeatureAndSetParams(blink::features::kBackForwardCacheDedicatedWorker, |
| "", ""); |
| if (IsPlzDedicatedWorkerEnabled()) |
| EnableFeatureAndSetParams(blink::features::kPlzDedicatedWorker, "", ""); |
| 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); |
| } |
| |
| bool IsPlzDedicatedWorkerEnabled() { return GetParam(); } |
| |
| 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_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| BackForwardCacheWithDedicatedWorkerBrowserTest, |
| testing::Bool()); |
| |
| // Confirms that a page using a dedicated worker is cached. |
| IN_PROC_BROWSER_TEST_P(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_P( |
| 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_P(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_P(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(https://crbug.com/1299018): Flaky on Linux. |
| #if BUILDFLAG(IS_LINUX) |
| #define MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel \ |
| DISABLED_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel |
| #else |
| #define MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel \ |
| DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel |
| #endif |
| IN_PROC_BROWSER_TEST_P( |
| BackForwardCacheWithDedicatedWorkerBrowserTest, |
| MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel) { |
| 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 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 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, |
| blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // TODO(https://crbug.com/1296306): Disabled due to being flaky. |
| IN_PROC_BROWSER_TEST_P( |
| 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_P(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_P(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_P( |
| 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_P( |
| 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_P(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_P( |
| 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_P(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_P( |
| 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_P(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. If the PlzDedicatedWorker is enabled, the |
| // number of worker clients should be 1. If PlzDedicatedWorker is disabled, |
| // worker clients are not supported, so the number should be 0. |
| int expected_number = IsPlzDedicatedWorkerEnabled() ? 1 : 0; |
| 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(expected_number), |
| 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(expected_number, 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_P(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. If the PlzDedicatedWorker is enabled, the |
| // number of worker clients should be 2. If PlzDedicatedWorker is disabled, |
| // worker clients are not supported, so the number should be 0. |
| int expected_number = IsPlzDedicatedWorkerEnabled() ? 2 : 0; |
| 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(expected_number), |
| 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(expected_number, 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_P(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"); |
| }); |
| )"))); |
| |
| // If the PlzDedicatedWorker is enabled, the number of worker clients should |
| // be 1. If PlzDedicatedWorker is disabled, worker clients are not supported, |
| // so the number should be 0. |
| EXPECT_EQ(IsPlzDedicatedWorkerEnabled() ? 1 : 0, |
| CountWorkerClients(current_frame_host())); |
| } |
| |
| // TODO(https://crbug.com/154571): 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()); |
| rfh_b->DidChangeBackForwardCacheDisablingFeatures(0); |
| |
| // 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)); |
| int process_id = current_frame_host()->GetProcess()->GetID(); |
| int routing_id = current_frame_host()->GetRoutingID(); |
| |
| // 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. |
| shell()->LoadURL(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. Note that the reason for kWasGrantedMediaAccess occurs after |
| // MediaDevicesDispatcherHost is called, hence, both are reasons for the page |
| // not being restored. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| auto reason = BackForwardCacheDisable::DisabledReason( |
| BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost); |
| ExpectNotRestored({NotRestoredReason::kWasGrantedMediaAccess, |
| NotRestoredReason::kDisableForRenderFrameHostCalled}, |
| {}, {}, {reason}, {}, FROM_HERE); |
| EXPECT_TRUE( |
| tester.IsDisabledForFrameWithReason(process_id, routing_id, reason)); |
| } |
| |
| 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(); |
| int process_id = |
| rfh->child_at(0)->current_frame_host()->GetProcess()->GetID(); |
| int routing_id = rfh->child_at(0)->current_frame_host()->GetRoutingID(); |
| |
| // 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. |
| shell()->LoadURL(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. Note that the reason for kWasGrantedMediaAccess occurs after |
| // MediaDevicesDispatcherHost is called, hence, both are reasons for the page |
| // not being restored. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| auto reason = BackForwardCacheDisable::DisabledReason( |
| BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost); |
| |
| ExpectNotRestored({NotRestoredReason::kWasGrantedMediaAccess, |
| NotRestoredReason::kDisableForRenderFrameHostCalled}, |
| {}, {}, {reason}, {}, FROM_HERE); |
| EXPECT_TRUE( |
| tester.IsDisabledForFrameWithReason(process_id, routing_id, reason)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheIfMediaDeviceSubscribed) { |
| 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(); |
| int process_id = |
| rfh->child_at(0)->current_frame_host()->GetProcess()->GetID(); |
| int routing_id = rfh->child_at(0)->current_frame_host()->GetRoutingID(); |
| |
| 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. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page was subscribed to media devices when we navigated away, so it |
| // shouldn't have been cached. |
| deleted.WaitUntilDeleted(); |
| |
| // 3) Go back. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| auto reason = BackForwardCacheDisable::DisabledReason( |
| BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost); |
| ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {}, |
| {}, {reason}, {}, FROM_HERE); |
| EXPECT_TRUE( |
| tester.IsDisabledForFrameWithReason(process_id, routing_id, reason)); |
| } |
| |
| 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) |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebHID) { |
| 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)); |
| |
| // Request for HID devices. |
| EXPECT_EQ("success", EvalJs(current_frame_host(), R"( |
| new Promise(resolve => { |
| navigator.hid.getDevices() |
| .then(m => { resolve("success"); }) |
| .catch(() => { resolve("error"); }); |
| }); |
| )")); |
| |
| RenderFrameDeletedObserver deleted(current_frame_host()); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page uses WebHID so it should be deleted. |
| deleted.WaitUntilDeleted(); |
| |
| // 3) Go back. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kWebHID}, {}, |
| {}, {}, 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_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| let idleDetector = new IdleDetector(); |
| idleDetector.start(); |
| resolve(); |
| }); |
| )")); |
| |
| // 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_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| const registration = await navigator.serviceWorker.getRegistration( |
| '/payments/payment_app.js'); |
| await registration.paymentManager.enableDelegations(['shippingAddress']); |
| resolve(); |
| }); |
| )")); |
| |
| // 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::Sample sample = base::HistogramBase::Sample( |
| 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); |
| } |
| |
| // If pages released keyboard lock during pagehide, they can enter |
| // BackForwardCache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CacheIfKeyboardLockReleasedInPagehide) { |
| 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()); |
| // Register a pagehide handler to release keyboard lock. |
| EXPECT_TRUE(ExecJs(rfh_a.get(), R"( |
| window.onpagehide = function(e) { |
| new Promise(resolve => { |
| navigator.keyboard.unlock(); |
| resolve(); |
| }); |
| }; |
| )")); |
| |
| // 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); |
| } |
| |
| 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", "/title1.html")); |
| 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 BroadcastChannel (non-sticky) and a dummy sticky blocklisted |
| // features. |
| EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');")); |
| 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())); |
| |
| // Only sticky features are recorded because they're tracked in |
| // RenderFrameHostManager::UnloadOldFrame. |
| 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 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", "/title1.html")); |
| 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 BroadcastChannel (non-sticky) and Dummy sticky blocklisted |
| // features. |
| EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');")); |
| 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())); |
| |
| if (AreStrictSiteInstancesEnabled()) { |
| // Only sticky features are recorded because they're tracked in |
| // RenderFrameHostManager::UnloadOldFrame. |
| ExpectNotRestored( |
| {NotRestoredReason::kRelatedActiveContentsExist, |
| NotRestoredReason::kBlocklistedFeatures, |
| NotRestoredReason::kBrowsingInstanceNotSwapped}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kDummy}, |
| {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); |
| } else { |
| // Non-sticky reasons are not recorded here. |
| ExpectNotRestored( |
| { |
| NotRestoredReason::kBlocklistedFeatures, |
| NotRestoredReason::kBrowsingInstanceNotSwapped, |
| }, |
| {blink::scheduler::WebSchedulerTrackedFeature::kDummy}, |
| {ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {}, {}, |
| 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("/title1.html")); |
| 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 BroadcastChannel (non-sticky) and dummy sticky blocklisted features. |
| EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');")); |
| 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())); |
| |
| // Non-sticky reasons are not recorded here. |
| ExpectNotRestored( |
| { |
| NotRestoredReason::kBlocklistedFeatures, |
| NotRestoredReason::kBrowsingInstanceNotSwapped, |
| }, |
| {blink::scheduler::WebSchedulerTrackedFeature::kDummy}, |
| {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))); |
| } |
| |
| // 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 an empty page. |
| GURL url_a(https_server()->GetURL("a.test", "/title1.html")); |
| GURL url_b(https_server()->GetURL("b.test", "/title2.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, "window.foo = new BroadcastChannel('foo');")); |
| scoped_refptr<SiteInstanceImpl> site_instance_a = |
| static_cast<SiteInstanceImpl*>( |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| |
| // 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())); |
| // 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}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {}, |
| {}, 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 empty page. |
| GURL url_a(https_server()->GetURL("a.test", "/title1.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, "window.foo = new BroadcastChannel('foo');")); |
| 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}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {}, |
| {}, 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("/title1.html")); |
| GURL url_2(https_server()->GetURL("/title2.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url_1)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| RenderFrameHostImpl* rfh_1 = current_frame_host(); |
| // 2) Use BroadcastChannel (a non-sticky blocklisted feature). |
| EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');")); |
| scoped_refptr<SiteInstanceImpl> site_instance_1 = |
| static_cast<SiteInstanceImpl*>( |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| |
| // 3) 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())); |
| |
| // 4) Go back. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // Because the RenderFrameHostManager changed, the blocklisted features will |
| // be tracked in RenderFrameHostManager::UnloadOldFrame. |
| ExpectNotRestored( |
| {NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {}, |
| {}, FROM_HERE); |
| } |
| |
| // TODO(crbug.com/1317431): WebSQL does not work on Fuchsia. |
| #if BUILDFLAG(IS_FUCHSIA) |
| #define MAYBE_DoesNotCacheIfWebDatabase DISABLED_DoesNotCacheIfWebDatabase |
| #else |
| #define MAYBE_DoesNotCacheIfWebDatabase DoesNotCacheIfWebDatabase |
| #endif |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MAYBE_DoesNotCacheIfWebDatabase) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page with WebDatabase usage. |
| GURL url(embedded_test_server()->GetURL("/simple_database.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver deleted(rfh_a); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| // The page uses WebDatabase so it should be deleted. |
| deleted.WaitUntilDeleted(); |
| |
| // 3) Go back to the page with WebDatabase. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kWebDatabase}, {}, {}, {}, |
| FROM_HERE); |
| } |
| |
| class BackForwardCacheBrowserTestWithFlagForIndexedDB |
| : public BackForwardCacheBrowserTest, |
| public ::testing::WithParamInterface<int32_t> { |
| public: |
| // Different level of BFCache support for document with IndexedDB usage. This |
| // will affect the feature's value in the browser tests. |
| enum class IndexedDBBackForwardCacheEligibilityLevel { |
| // Do not cache if IndexedDB is used. |
| kNoCache = 0, |
| // Allow BFCache if the document has open connections, but without ongoing |
| // IndexedDB transactions. |
| kCacheConnectionOnly = 1, |
| // Allow BFCache if the document has open connections and/or ongoing |
| // IndexedDB transactions. |
| kCacheConnectionAndTransaction = 2, |
| |
| kMinLevel = kNoCache, |
| kMaxLevel = kCacheConnectionAndTransaction, |
| }; |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| EnableFeatureAndSetParams( |
| blink::features::kAllowPageWithIDBConnectionInBFCache, "", "true"); |
| } else { |
| DisableFeature(blink::features::kAllowPageWithIDBConnectionInBFCache); |
| } |
| if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) { |
| EnableFeatureAndSetParams( |
| blink::features::kAllowPageWithIDBTransactionInBFCache, "", "true"); |
| } else { |
| DisableFeature(blink::features::kAllowPageWithIDBTransactionInBFCache); |
| } |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| bool ShouldAllowPageWithIndexedDBConnectionInBFCache() { |
| return GetParam() >= |
| int(IndexedDBBackForwardCacheEligibilityLevel::kCacheConnectionOnly); |
| } |
| |
| bool ShouldAllowPageWithIndexedDBTransactionInBFCache() { |
| return GetParam() >= int(IndexedDBBackForwardCacheEligibilityLevel:: |
| kCacheConnectionAndTransaction); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| ::testing::Range( |
| int(BackForwardCacheBrowserTestWithFlagForIndexedDB:: |
| IndexedDBBackForwardCacheEligibilityLevel::kMinLevel), |
| int(BackForwardCacheBrowserTestWithFlagForIndexedDB:: |
| IndexedDBBackForwardCacheEligibilityLevel::kMaxLevel) + |
| 1)); |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| DoesNotCacheIfOpenIndexedDBConnection) { |
| 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"))); |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| } |
| |
| // 3) Go back to the page with IndexedDB. |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| // If the flag indicates that the page with open IndexedDB connection is |
| // eligible for back/forward cache, after navigating back, the page should |
| // be restored. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectRestored(FROM_HERE); |
| } else { |
| // If the flag indicates that the page with open IndexedDB connection is not |
| // eligible for back/forward cache, the document should be deleted. |
| ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); |
| |
| // It should not be restored from the back/forward cache, and the reason |
| // should indicate that it was blocked due to `kIndexedDBConnection`. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection}, |
| {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| 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"))); |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| 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())); |
| |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| // If this feature is enabled, 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); |
| } else { |
| // If this feature is disabled, the page should not be put into back/forward |
| // cache at all, and the recorded blocklisted feature should be |
| // `kIndexedDBConnection`. |
| ExpectNotRestored( |
| {NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection}, |
| {}, {}, {}, 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_P( |
| BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| 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 if the feature |
| // is on, otherwise, the reason should be open IndexedDB connection instead. |
| blink::scheduler::WebSchedulerTrackedFeature tracked_feature; |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| tracked_feature = |
| blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent; |
| } else { |
| tracked_feature = |
| blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection; |
| } |
| |
| 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( |
| tracked_feature)); |
| navigation_manager.ResumeNavigation(); |
| 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 the `tracked_feature`. |
| ASSERT_TRUE(rfh_receiving.WaitUntilRenderFrameDeleted()); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {tracked_feature}, {}, {}, {}, 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_P( |
| BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| 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. |
| |
| blink::scheduler::WebSchedulerTrackedFeature tracked_feature; |
| if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) { |
| tracked_feature = |
| blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent; |
| } else { |
| tracked_feature = |
| blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection; |
| } |
| |
| ASSERT_FALSE(rfh_receiving.get()->GetBackForwardCacheDisablingFeatures().Has( |
| tracked_feature)); |
| |
| navigation_manager.ResumeNavigation(); |
| 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_P(BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| DoNotCacheIfIndexedDBTransactionNotCommitted) { |
| 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. |
| if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) { |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectRestored(FROM_HERE); |
| } else { |
| ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature:: |
| kOutstandingIndexedDBTransaction}, |
| {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| DoNotCacheIfIndexedDBTransactionIsAcquiringTheLock) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| Shell* tab_holding_locks = CreateBrowser(); |
| Shell* tab_waiting_for_locks = shell(); |
| |
| // 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. |
| ExecuteScriptAsync(tab_holding_locks, |
| "runInfiniteIndexedDBTransactionLoop()"); |
| |
| // 2) Navigate the tab waiting for locks to A as well and make it requesting |
| // for the same lock on pagehide. 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"))); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "setupIndexedDBConnection()")); |
| ASSERT_TRUE( |
| ExecJs(tab_waiting_for_locks, "registerPagehideToStartTransaction()")); |
| |
| // 3) Navigate the tab waiting for locks away. |
| ASSERT_TRUE( |
| NavigateToURL(tab_waiting_for_locks, |
| embedded_test_server()->GetURL("b.com", "/title1.html"))); |
| |
| // 4) Go back to the page with IndexedDB. |
| if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) { |
| // If the flag that enables a page with IndexedDB features to enter BFCache |
| // is toggled on, the page should be evicted by disallowing activation. |
| ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); |
| ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents())); |
| ExpectNotRestored( |
| {NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, |
| {DisallowActivationReasonId::kIndexedDBTransactionIsAcquiringLocks}, |
| FROM_HERE); |
| } else { |
| // If the flag is not toggled on, the page will not be eligible for BFCache |
| // because of the registered feature. |
| ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); |
| ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature:: |
| kOutstandingIndexedDBTransaction}, |
| {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| 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"))); |
| 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 on pagehide. 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"))); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "setupIndexedDBConnection()")); |
| ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "startIndexedDBTransaction()")); |
| |
| // 3) Navigate the tab holding locks away. |
| 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. |
| if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) { |
| // If the flag that enables a page with IndexedDB features to enter BFCache |
| // is toggled on, 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::kIndexedDBTransactionIsBlockingOthers}, |
| FROM_HERE); |
| } else { |
| // If the flag is not toggled on, the page will not be eligible for BFCache |
| // because of the registered feature. |
| ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); |
| ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature:: |
| kOutstandingIndexedDBTransaction}, |
| {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB, |
| EvictCacheIfPageBlocksNewTransaction) { |
| 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()); |
| std::string message_holding_locks; |
| 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. |
| 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()); |
| std::string message_acquiring_locks; |
| 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. |
| ASSERT_TRUE(queue_acquiring_locks.WaitForMessage(&message_acquiring_locks)); |
| ASSERT_EQ("\"transaction_completed\"", message_acquiring_locks); |
| if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) { |
| // If the flag that enables a page with IndexedDB features to enter BFCache |
| // is toggled on, 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::kIndexedDBTransactionIsBlockingOthers}, |
| FROM_HERE); |
| } else { |
| // If the flag is not toggled on, the page will not be eligible for BFCache |
| // because of the registered feature. |
| ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); |
| ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature:: |
| kOutstandingIndexedDBTransaction}, |
| {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheIfBroadcastChannelStillOpen) { |
| 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())); |
| |
| // Because the RenderFrameHostManager changed, the blocklisted features will |
| // be tracked in RenderFrameHostManager::UnloadOldFrame. |
| 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); |
| } |
| |
| // Disabled on Android, since we have problems starting up the websocket test |
| // server in the host |
| // TODO(crbug.com/1372291): 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()); |
| });)"; |
| ASSERT_TRUE( |
| ExecJs(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/1372291): 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()); |
| });)"; |
| ASSERT_TRUE(ExecJs( |
| 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) { |
| 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 BluetoothForwardCacheBrowserTest : public BackForwardCacheBrowserTest { |
| protected: |
| BluetoothForwardCacheBrowserTest() = default; |
| |
| ~BluetoothForwardCacheBrowserTest() override = default; |
| |
| void SetUp() override { |
| // Fake the BluetoothAdapter to say it's present. |
| // Used in WebBluetooth test. |
| adapter_ = |
| base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>(); |
| device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // In CHROMEOS build, even when |adapter_| object is released at TearDown() |
| // it causes the test to fail on exit with an error indicating |adapter_| is |
| // leaked. |
| testing::Mock::AllowLeak(adapter_.get()); |
| #endif |
| |
| BackForwardCacheBrowserTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| testing::Mock::VerifyAndClearExpectations(adapter_.get()); |
| adapter_.reset(); |
| BackForwardCacheBrowserTest::TearDown(); |
| } |
| |
| scoped_refptr<device::MockBluetoothAdapter> adapter_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothForwardCacheBrowserTest, WebBluetooth) { |
| // The test requires a mock Bluetooth adapter to perform a |
| // WebBluetooth API call. 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(). |
| |
| // WebBluetooth requires HTTPS. |
| ASSERT_TRUE(CreateHttpsServer()->Start()); |
| GURL url(https_server()->GetURL("a.test", "/back_forward_cache/empty.html")); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents(), url)); |
| BackForwardCacheDisabledTester tester; |
| |
| 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")) |
| }); |
| )")); |
| auto reason = BackForwardCacheDisable::DisabledReason( |
| BackForwardCacheDisable::DisabledReasonId::kWebBluetooth); |
| EXPECT_TRUE(tester.IsDisabledForFrameWithReason( |
| current_frame_host()->GetProcess()->GetID(), |
| current_frame_host()->GetRoutingID(), reason)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents(), |
| https_server()->GetURL("b.test", "/title1.html"))); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {}, |
| {}, {reason}, {}, 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(https://crbug.com/1339720): 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()->GetID(), 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_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| audio.play(); |
| while (audio.currentTime === 0) |
| await new Promise(r => setTimeout(r, 1)); |
| resolve(); |
| }); |
| )")); |
| |
| // 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_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| video.play(); |
| while (video.currentTime == 0) |
| await new Promise(r => setTimeout(r, 1)); |
| resolve(); |
| }); |
| )")); |
| |
| // 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 { |
| protected: |
| SensorBackForwardCacheBrowserTest() { |
| SensorProviderProxyImpl::OverrideSensorProviderBinderForTesting( |
| base::BindRepeating( |
| &SensorBackForwardCacheBrowserTest::BindSensorProvider, |
| base::Unretained(this))); |
| } |
| |
| ~SensorBackForwardCacheBrowserTest() override { |
| SensorProviderProxyImpl::OverrideSensorProviderBinderForTesting( |
| base::NullCallback()); |
| } |
| |
| void SetUpOnMainThread() override { |
| provider_ = std::make_unique<device::FakeSensorProvider>(); |
| provider_->SetAccelerometerData(1.0, 2.0, 3.0); |
| |
| BackForwardCacheBrowserTest::SetUpOnMainThread(); |
| } |
| |
| std::unique_ptr<device::FakeSensorProvider> provider_; |
| |
| private: |
| void BindSensorProvider( |
| mojo::PendingReceiver<device::mojom::SensorProvider> receiver) { |
| provider_->Bind(std::move(receiver)); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, |
| AccelerometerNotCached) { |
| 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"( |
| new Promise(resolve => { |
| const sensor = new Accelerometer(); |
| sensor.addEventListener('reading', () => { resolve(); }); |
| sensor.start(); |
| }) |
| )")); |
| |
| // 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:: |
| kRequestedBackForwardCacheBlockedSensors}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| 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's events are not delivered to a page in the |
| // back-forward cache. |
| // |
| // 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 reasing 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=1. |
| IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, |
| SensorPausedWhileCached) { |
| ASSERT_TRUE(CreateHttpsServer()->Start()); |
| GURL url_a(https_server()->GetURL("a.test", "/title1.html")); |
| GURL url_b(https_server()->GetURL("b.test", "/title1.html")); |
| |
| provider_->SetRelativeOrientationSensorData(0, 0, 0); |
| |
| // JS to cause a page to listen to, capture and validate orientation events. |
| const std::string sensor_js = R"( |
| // Collects events that have happened so far. |
| var events = []; |
| // If set, will be called by handleEvent. |
| var pendingResolve = null; |
| |
| // Handles one event, pushing it to |events| and calling |pendingResolve| if |
| // set. |
| function handleEvent(event) { |
| events.push(event); |
| if (pendingResolve !== null) { |
| pendingResolve('event'); |
| pendingResolve = null; |
| } |
| } |
| |
| // Returns a promise that will resolve when the events array has at least |
| // |eventCountMin| elements. Returns the number of elements. |
| function waitForEventsPromise(eventCountMin) { |
| if (events.length >= eventCountMin) { |
| return Promise.resolve(events.length); |
| } |
| return new Promise(resolve => { |
| pendingResolve = resolve; |
| }).then(() => waitForEventsPromise(eventCountMin)); |
| } |
| |
| // Pretty print an orientation event. |
| function eventToString(event) { |
| return `${event.alpha} ${event.beta} ${event.gamma}`; |
| } |
| |
| // Ensure that that |expectedAlpha| matches the alpha of all events. |
| function validateEvents(expectedAlpha = null) { |
| if (expectedAlpha !== null) { |
| let count = 0; |
| for (event of events) { |
| count++; |
| if (Math.abs(event.alpha - expectedAlpha) > 0.01) { |
| return `fail - ${count}/${events.length}: ` + |
| `${expectedAlpha} != ${event.alpha} (${eventToString(event)})`; |
| } |
| } |
| } |
| return 'pass'; |
| } |
| |
| window.addEventListener('deviceorientation', handleEvent); |
| )"; |
| |
| // 1) Navigate to A. |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| ASSERT_TRUE(ExecJs(rfh_a, sensor_js)); |
| |
| // Collect 3 orientation events. |
| ASSERT_EQ(1, EvalJs(rfh_a, "waitForEventsPromise(1)")); |
| provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2); |
| ASSERT_EQ(2, EvalJs(rfh_a, "waitForEventsPromise(2)")); |
| provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4); |
| ASSERT_EQ(3, EvalJs(rfh_a, "waitForEventsPromise(3)")); |
| // We should have 3 events with alpha=0. |
| ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)")); |
| |
| // 2) Navigate to B. |
| ASSERT_TRUE(NavigateToURL(shell(), url_b)); |
| ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| ASSERT_THAT(rfh_a, InBackForwardCache()); |
| ASSERT_NE(rfh_a, rfh_b); |
| |
| ASSERT_TRUE(ExecJs(rfh_b, sensor_js)); |
| |
| // Collect 3 orientation events. |
| provider_->SetRelativeOrientationSensorData(1, 0, 0); |
| ASSERT_EQ(1, EvalJs(rfh_b, "waitForEventsPromise(1)")); |
| provider_->UpdateRelativeOrientationSensorData(1, 0, 0.2); |
| ASSERT_EQ(2, EvalJs(rfh_b, "waitForEventsPromise(2)")); |
| provider_->UpdateRelativeOrientationSensorData(1, 0, 0.4); |
| ASSERT_EQ(3, EvalJs(rfh_b, "waitForEventsPromise(3)")); |
| // We should have 3 events with alpha=1. |
| ASSERT_EQ("pass", EvalJs(rfh_b, "validateEvents()")); |
| |
| // 3) Go back to A. |
| provider_->UpdateRelativeOrientationSensorData(0, 0, 0); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ASSERT_EQ(rfh_a, 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, "waitForEventsPromise(4)").ExtractInt(); |
| provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2); |
| count++; |
| ASSERT_EQ(count, EvalJs(rfh_a, base::StringPrintf("waitForEventsPromise(%d)", |
| count))); |
| provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4); |
| count++; |
| ASSERT_EQ(count, EvalJs(rfh_a, base::StringPrintf("waitForEventsPromise(%d)", |
| count))); |
| |
| // We should have the earlier 3 plus another 3 events with alpha=0. |
| ASSERT_EQ("pass", EvalJs(rfh_a, "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(https://crbug.com/1213145): 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(https://crbug.com/1213145): The test is consistently failing on some Mac |
| // bots. |
| #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())); |
| ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kWebRTC}, {}, |
| {}, {}, 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| 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_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| var r = new webkitSpeechRecognition(); |
| r.start(); |
| resolve(); |
| }); |
| )")); |
| |
| // 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_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| var r = new webkitSpeechRecognition(); |
| resolve(); |
| }); |
| )")); |
| |
| // 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_DoesNotCacheIfUsingSpeechSynthesis \ |
| DISABLED_DoesNotCacheIfUsingSpeechSynthesis |
| #else |
| #define MAYBE_DoesNotCacheIfUsingSpeechSynthesis \ |
| DoesNotCacheIfUsingSpeechSynthesis |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MAYBE_DoesNotCacheIfUsingSpeechSynthesis) { |
| 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)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver rhf_a_deleted(rfh_a); |
| |
| EXPECT_TRUE(ExecJs(rfh_a, R"( |
| new Promise(async resolve => { |
| var u = new SpeechSynthesisUtterance(" "); |
| speechSynthesis.speak(u); |
| resolve(); |
| }); |
| )")); |
| |
| // 2) Navigate away. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // The page uses SpeechSynthesis so it should be deleted. |
| rhf_a_deleted.WaitUntilDeleted(); |
| |
| // 3) Go back to the page with SpeechSynthesis. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kSpeechSynthesis}, {}, {}, |
| {}, FROM_HERE); |
| } |
| |
| 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()->GetID(), 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, 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", "/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_a1 = current_frame_host(); |
| RenderFrameDeletedObserver deleted_a1(rfh_a1); |
| EXPECT_TRUE(ExecJs(rfh_a1, "window.foo = new BroadcastChannel('foo');")); |
| |
| // 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}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {}, |
| {}, 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}, {}, {}, {}, |
| 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/1253200): 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession, |
| DontCacheWhenMediaSessionServiceIsUsed) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to a page using MediaSession. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL( |
| "a.test", "/media/session/media-session.html"))); |
| RenderFrameHostWrapper rfh_a(current_frame_host()); |
| // Register a callback explicitly to use a MediaSession service. |
| EXPECT_TRUE(ExecJs(rfh_a.get(), R"( |
| navigator.mediaSession.setActionHandler('play', () => {}); |
| )")); |
| |
| PlayVideoNavigateAndGoBack(); |
| |
| // The page is not restored since a MediaSession service is used. |
| auto reason = BackForwardCacheDisable::DisabledReason( |
| BackForwardCacheDisable::DisabledReasonId::kMediaSessionService); |
| ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {}, |
| {}, {reason}, {}, FROM_HERE); |
| } |
| |
| } // namespace content |