#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/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/worker_host/dedicated_worker_hosts_for_document.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/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"
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());
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
if (IsDedicatedWorkerEnabled()) {
EXPECT_EQ(rfh.get(), current_frame_host());
ExpectRestored(FROM_HERE);
} else {
ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::
kDedicatedWorkerOrWorklet},
{}, {}, {}, FROM_HERE);
}
}
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());
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"));
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
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")));
EXPECT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise"));
EXPECT_FALSE(
web_contents()->GetPrimaryFrameTree().root()->navigation_request());
}
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")));
EXPECT_EQ("opened",
EvalJs(current_frame_host(),
JsReplace("window.testOpenWebTransport($1);", port())));
RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
delete_observer_rfh.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}, {}, {}, {},
FROM_HERE);
}
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")));
EXPECT_EQ("opened",
EvalJs(current_frame_host(),
JsReplace("window.testOpenWebTransport($1);", port())));
EXPECT_EQ("closed",
EvalJs(current_frame_host(), "window.testCloseWebTransport();"));
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
#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")));
EXPECT_EQ("opened",
EvalJs(current_frame_host(),
JsReplace("window.testOpenWebTransport($1);", port())));
EXPECT_EQ(42, EvalJs(current_frame_host(), "42;"));
EXPECT_TRUE(
DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(
current_frame_host())
->GetBackForwardCacheDisablingFeatures()
.HasAll(
{blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}));
EXPECT_TRUE(ExecJs(current_frame_host(),
"window.foo = new BroadcastChannel('foo');"));
RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
delete_observer_rfh.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kWebTransport,
blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
{}, {}, {}, FROM_HERE);
}
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")));
EXPECT_EQ("opened",
EvalJs(current_frame_host(),
JsReplace("window.testOpenWebTransport($1);", port())));
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())));
EXPECT_EQ(42, EvalJs(current_frame_host(), "42;"));
EXPECT_TRUE(DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(
current_frame_host())
->GetBackForwardCacheDisablingFeatures()
.Empty());
EXPECT_TRUE(ExecJs(current_frame_host(),
"window.foo = new BroadcastChannel('foo');"));
RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
delete_observer_rfh.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
{}, FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
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();
PageLifecycleStateManagerTestDelegate delegate(
rfh_a->render_view_host()->GetPageLifecycleStateManager());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
fetch1_response.Send(
"HTTP/1.1 302 Moved Temporarily\r\n"
"Location: /fetch2\r\n\r\n");
fetch1_response.Done();
base::RunLoop loop1;
base::OneShotTimer timer1;
timer1.Start(FROM_HERE, base::Seconds(3), loop1.QuitClosure());
loop1.Run();
EXPECT_EQ(nullptr, fetch2_response.http_request());
delete_observer_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kNetworkRequestRedirected}, {}, {}, {},
{}, FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
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();
PageLifecycleStateManagerTestDelegate delegate(
rfh_a->render_view_host()->GetPageLifecycleStateManager());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
fetch1_response.Send(
"HTTP/1.1 302 Moved Temporarily\r\n"
"Location: /fetch2\r\n\r\n");
fetch1_response.Done();
base::RunLoop loop2;
base::OneShotTimer timer2;
timer2.Start(FROM_HERE, base::Seconds(3), loop2.QuitClosure());
loop2.Run();
EXPECT_EQ(nullptr, fetch2_response.http_request());
delete_observer_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kNetworkRequestRedirected}, {}, {}, {},
{}, FROM_HERE);
}
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());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html")));
RenderFrameHostImpl* rfh_a = current_frame_host();
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)));
image_response.WaitForRequest();
PageLifecycleStateManagerTestDelegate delegate(
rfh_a->render_view_host()->GetPageLifecycleStateManager());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title2.html")));
ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
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();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kNetworkExceedsBufferLimit}, {}, {}, {},
{}, FROM_HERE);
}
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());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html")));
RenderFrameHostImpl* rfh_a = current_frame_host();
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)));
image_response.WaitForRequest();
PageLifecycleStateManagerTestDelegate delegate(
rfh_a->render_view_host()->GetPageLifecycleStateManager());
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title2.html")));
ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
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();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kNetworkExceedsBufferLimit}, {}, {}, {},
{}, FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
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)));
fetch_response.WaitForRequest();
fetch_response.Send(net::HTTP_OK, "text/plain");
fetch_response.Send("body");
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kNetworkRequestDatapipeDrainedAsBytesConsumer}, {},
{}, {}, {}, FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
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)));
fetch_response.WaitForRequest();
fetch_response.Send(net::HTTP_OK, "text/plain");
fetch_response.Send("body");
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kNetworkRequestDatapipeDrainedAsBytesConsumer}, {},
{}, {}, {}, FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
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)));
image_response.WaitForRequest();
PageLifecycleStateManagerTestDelegate delegate(
rfh_a->render_view_host()->GetPageLifecycleStateManager());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
image_response.Send(net::HTTP_OK, "image/png");
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kNetworkRequestTimeout}, {}, {}, {}, {},
FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
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)));
image_response.WaitForRequest();
PageLifecycleStateManagerTestDelegate delegate(
rfh_a->render_view_host()->GetPageLifecycleStateManager());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
image_response.Send(net::HTTP_OK, "image/png");
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kNetworkRequestTimeout}, {}, {}, {}, {},
FROM_HERE);
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
EXPECT_EQ(
"DONE",
EvalJs(current_frame_host(),
"register('/service_worker/fetch_event_worker_clients.js');"));
web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_EQ(0, CountWorkerClients(rfh_a.get()));
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)));
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(0, CountWorkerClients(current_frame_host()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(expected_number, CountWorkerClients(current_frame_host()));
}
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
EXPECT_EQ(
"DONE",
EvalJs(current_frame_host(),
"register('/service_worker/fetch_event_worker_clients.js');"));
web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_EQ(0, CountWorkerClients(rfh_a.get()));
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)));
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(0, CountWorkerClients(current_frame_host()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(expected_number, CountWorkerClients(current_frame_host()));
}
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
ServiceWorkerClientMatchAll_LoadWorkerAfterRestoring) {
CreateHttpsServer();
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
EXPECT_EQ(
"DONE",
EvalJs(current_frame_host(),
"register('/service_worker/fetch_event_worker_clients.js');"));
web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_EQ(0, CountWorkerClients(rfh_a.get()));
EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
window.dedicatedWorkerUsingServiceWorker = new Worker(
'/service_worker/dedicated_worker_using_service_worker.js');
)"));
dedicated_worker_response.WaitForRequest();
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
dedicated_worker_response.Send(net::HTTP_OK, "text/javascript");
dedicated_worker_response.Send(R"(
onmessage = e => {
postMessage(e.data);
};
)");
dedicated_worker_response.Done();
EXPECT_EQ(0, CountWorkerClients(current_frame_host()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
EXPECT_EQ("foo", EvalJs(current_frame_host(), JsReplace(R"(
new Promise(async (resolve) => {
window.dedicatedWorkerUsingServiceWorker.onmessage = e => {
resolve(e.data);
};
window.dedicatedWorkerUsingServiceWorker.postMessage("foo");
});
)")));
EXPECT_EQ(IsPlzDedicatedWorkerEnabled() ? 1 : 0,
CountWorkerClients(current_frame_host()));
}
#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());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
delete_observer_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kSharedWorker}, {}, {}, {},
FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
AllowedFeaturesForSubframesDoNotEvict) {
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"));
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);
ASSERT_TRUE(NavigateToURL(shell(), url_c));
ASSERT_FALSE(delete_observer_rfh_b.deleted());
RenderFrameHostImpl::BackForwardCacheBlockingDetails empty_vector;
rfh_b->DidChangeBackForwardCacheDisablingFeatures(std::move(empty_vector));
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;
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();
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());
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
deleted.WaitUntilDeleted();
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;
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.getUserMedia({audio: true})
.then(m => { resolve("success"); })
.catch(() => { resolve("error"); });
});
)"));
RenderFrameDeletedObserver deleted(current_frame_host());
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
deleted.WaitUntilDeleted();
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;
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());
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
deleted.WaitUntilDeleted();
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());
GURL url(embedded_test_server()->GetURL(
"example.com", "/back_forward_cache/page_with_webgl.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebHID) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
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());
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
deleted.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kWebHID}, {},
{}, {}, FROM_HERE);
}
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
WakeLockReleasedUponEnteringBfcache) {
ASSERT_TRUE(CreateHttpsServer()->Start());
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();
EXPECT_EQ("DONE", EvalJs(rfh_a, "acquireWakeLock()"));
EXPECT_FALSE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool());
shell()->LoadURL(https_server()->GetURL("b.test", "/title1.html"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
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());
GURL url(embedded_test_server()->GetURL("a.test", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* rfh_a = current_frame_host();
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);
});
)"));
shell()->LoadURL(embedded_test_server()->GetURL("b.test", "/title1.html"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
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; }
};
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIdleManager) {
ASSERT_TRUE(embedded_test_server()->Start());
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();
});
)"));
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
deleted.WaitUntilDeleted();
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());
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));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
rfh_a_deleted.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
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();
}
}
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(
SkBitmap(), service_worker_javascript_file_url,
service_worker_javascript_file_url.GetWithoutFilename(),
url::Origin::Create(service_worker_javascript_file_url).Serialize(),
base::BindOnce(&OnInstallPaymentApp, run_loop.QuitClosure(),
&success));
run_loop.Run();
ASSERT_TRUE(success);
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);
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();
});
)"));
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
rfh_a_deleted.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager}, {}, {},
{}, FROM_HERE);
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));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
DoesNotCacheOnKeyboardLock) {
ASSERT_TRUE(embedded_test_server()->Start());
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);
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
rfh_a_deleted.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {},
FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CacheIfKeyboardLockReleasedMultipleRestores) {
ASSERT_TRUE(embedded_test_server()->Start());
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());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
RenderFrameHostImplWrapper rfh_b(current_frame_host());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
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);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
DoNotCacheIfKeyboardLockIsHeldAfterRelease) {
ASSERT_TRUE(embedded_test_server()->Start());
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());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {},
FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CacheIfKeyboardLockReleased) {
ASSERT_TRUE(embedded_test_server()->Start());
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());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CacheIfKeyboardLockReleasedInPagehide) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
AcquireKeyboardLock(rfh_a.get());
EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
window.onpagehide = function(e) {
new Promise(resolve => {
navigator.keyboard.unlock();
resolve();
});
};
)"));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
DoesNotCacheWithDummyStickyFeature) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kDummy}, {},
{}, {}, FROM_HERE);
}
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"));
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);
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
rfh_a_deleted.WaitUntilDeleted();
EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kDummy,
blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
{}, {}, {}, FROM_HERE);
}
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"));
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());
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
EXPECT_TRUE(site_instance_a->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
if (AreStrictSiteInstancesEnabled()) {
ExpectNotRestored(
{NotRestoredReason::kRelatedActiveContentsExist,
NotRestoredReason::kBlocklistedFeatures,
NotRestoredReason::kBrowsingInstanceNotSwapped},
{blink::scheduler::WebSchedulerTrackedFeature::kDummy,
blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
{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 {
ExpectNotRestored(
{
NotRestoredReason::kBlocklistedFeatures,
NotRestoredReason::kBrowsingInstanceNotSwapped,
},
{blink::scheduler::WebSchedulerTrackedFeature::kDummy,
blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
{ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {}, {},
FROM_HERE);
}
}
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"));
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);
EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
rfh_1->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
EXPECT_TRUE(NavigateToURL(shell(), url_2));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_TRUE(site_instance_1->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{
NotRestoredReason::kBlocklistedFeatures,
NotRestoredReason::kBrowsingInstanceNotSwapped,
},
{blink::scheduler::WebSchedulerTrackedFeature::kDummy,
blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
{ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {}, {},
FROM_HERE);
EXPECT_THAT(
GetTreeResult()->GetDocumentResult(),
MatchesDocumentResult(
NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures,
NotRestoredReason::kBrowsingInstanceNotSwapped),
BlockListedFeatures(
blink::scheduler::WebSchedulerTrackedFeature::kDummy,
blink::scheduler::WebSchedulerTrackedFeature::
kBroadcastChannel)));
}
IN_PROC_BROWSER_TEST_F(
BackForwardCacheBrowserTest,
BlocklistedFeaturesTracking_CrossSite_BrowserInitiated_NonSticky) {
ASSERT_TRUE(CreateHttpsServer()->Start());
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();
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
scoped_refptr<SiteInstanceImpl> site_instance_a =
static_cast<SiteInstanceImpl*>(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
{}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(
BackForwardCacheBrowserTest,
BlocklistedFeaturesTracking_CrossSite_RendererInitiated_NonSticky) {
ASSERT_TRUE(CreateHttpsServer()->Start());
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();
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
scoped_refptr<SiteInstanceImpl> site_instance_a =
static_cast<SiteInstanceImpl*>(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
{}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
BlocklistedFeaturesTracking_SameSite_NonSticky) {
ASSERT_TRUE(CreateHttpsServer()->Start());
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();
EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(NavigateToURL(shell(), url_2));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
{}, FROM_HERE);
}
#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());
GURL url(embedded_test_server()->GetURL("/simple_database.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver deleted(rfh_a);
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
deleted.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kWebDatabase}, {}, {}, {},
FROM_HERE);
}
class BackForwardCacheBrowserTestWithFlagForIndexedDB
: public BackForwardCacheBrowserTest,
public ::testing::WithParamInterface<int32_t> {
public:
enum class IndexedDBBackForwardCacheEligibilityLevel {
kNoCache = 0,
kCacheConnectionOnly = 1,
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());
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()"));
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
}
if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
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::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();
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());
ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()"));
ASSERT_TRUE(
ExecJs(rfh_a.get(), "setupNewIndexedDBConnectionWithSameVersion()"));
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")));
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);
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
{DisallowActivationReasonId::kIndexedDBEvent}, FROM_HERE);
} else {
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection},
{}, {}, {}, FROM_HERE);
}
}
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();
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())));
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()");
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();
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
ASSERT_TRUE(queue_sending_version_change.WaitForMessage(
&message_sending_version_change));
ASSERT_EQ("\"onsuccess\"", message_sending_version_change);
ASSERT_TRUE(rfh_receiving.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
{tracked_feature}, {}, {}, {}, FROM_HERE);
}
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();
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())));
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()");
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());
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();
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
ASSERT_TRUE(queue_sending_version_change.WaitForMessage(
&message_sending_version_change));
ASSERT_EQ("\"onsuccess\"", message_sending_version_change);
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CacheIfIndexedDBConnectionClosedInPagehide) {
ASSERT_TRUE(embedded_test_server()->Start());
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()"));
EXPECT_TRUE(
ExecJs(rfh_a.get(), "registerPagehideToCloseIndexedDBConnection()"));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
DoNotCacheIfIndexedDBTransactionNotCommitted) {
ASSERT_TRUE(embedded_test_server()->Start());
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()"));
ASSERT_TRUE(ExecJs(rfh_a.get(), "registerPagehideToStartTransaction()"));
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
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());
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()"));
ASSERT_TRUE(
ExecJs(rfh_a.get(), "registerPagehideToStartAndCommitTransaction()"));
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(rfh_a->IsInBackForwardCache());
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();
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()"));
ExecuteScriptAsync(tab_holding_locks,
"runInfiniteIndexedDBTransactionLoop()");
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()"));
ASSERT_TRUE(
NavigateToURL(tab_waiting_for_locks,
embedded_test_server()->GetURL("b.com", "/title1.html")));
if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents()));
ExpectNotRestored(
{NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
{DisallowActivationReasonId::kIndexedDBTransactionIsAcquiringLocks},
FROM_HERE);
} else {
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();
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()"));
ExecuteScriptAsync(tab_holding_locks,
"runInfiniteIndexedDBTransactionLoop()");
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()"));
ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL(
"b.com", "/title1.html")));
if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents()));
ExpectNotRestored(
{NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
{DisallowActivationReasonId::kIndexedDBTransactionIsBlockingOthers},
FROM_HERE);
} else {
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();
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()"));
ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL(
"b.com", "/title1.html")));
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()"));
ASSERT_TRUE(queue_acquiring_locks.WaitForMessage(&message_acquiring_locks));
ASSERT_EQ("\"transaction_completed\"", message_acquiring_locks);
if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents()));
ExpectNotRestored(
{NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
{DisallowActivationReasonId::kIndexedDBTransactionIsBlockingOthers},
FROM_HERE);
} else {
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());
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();
EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();"));
EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(false);"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
{}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CacheIfBroadcastChannelIsClosedInPagehide) {
ASSERT_TRUE(CreateHttpsServer()->Start());
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();
EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();"));
EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(true);"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebSocketCachedIfClosed DISABLED_WebSocketCachedIfClosed
#else
#define MAYBE_WebSocketCachedIfClosed WebSocketCachedIfClosed
#endif
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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
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"))));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
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_;
};
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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
const char script[] = R"(
let transport = new WebTransport('https://localhost:$1/echo');
)";
ASSERT_TRUE(ExecJs(rfh_a.get(), JsReplace(script, port())));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}, {}, {}, {},
FROM_HERE);
}
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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
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())));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
#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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
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"))));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
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");
}
});
)"));
}
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;
}
}
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:
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};
};
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
VibrationStopsAfterEnteringCache) {
ASSERT_TRUE(embedded_test_server()->Start());
TestVibrationManager vibration_manager;
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());
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());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
ShortVibrationSequenceStopsAfterEnteringCache) {
ASSERT_TRUE(embedded_test_server()->Start());
TestVibrationManager vibration_manager;
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());
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());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CachedPagesWithServiceWorkers) {
CreateHttpsServer();
SetupCrossSiteRedirector(https_server());
ASSERT_TRUE(https_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(),
https_server()->GetURL("a.test", "/back_forward_cache/empty.html")));
RegisterServiceWorker(current_frame_host());
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver deleted(rfh_a);
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
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();
EXPECT_TRUE(NavigateToURL(
tab_x,
https_server()->GetURL("a.test", "/back_forward_cache/empty.html")));
RegisterServiceWorker(current_frame_host());
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver deleted(rfh_a);
EXPECT_TRUE(
NavigateToURL(tab_x, https_server()->GetURL("b.test", "/title1.html")));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(NavigateToURL(
tab_y,
https_server()->GetURL("a.test", "/back_forward_cache/empty.html")));
tab_y->Close();
deleted.WaitUntilDeleted();
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();
WebContentsObserver::Observe(tab_to_be_bfcached->web_contents());
EXPECT_TRUE(NavigateToURL(
tab_to_execute_service_worker,
https_server.GetURL(
"a.test", "/back_forward_cache/service_worker_post_message.html")));
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker,
"register('service_worker_post_message.js')"));
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());
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
https_server.GetURL("b.test", "/title1.html")));
EXPECT_FALSE(rfh.IsDestroyed());
EXPECT_TRUE(rfh->IsInBackForwardCache());
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());
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();
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);
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
https_server.GetURL("b.test", "/title1.html")));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(NavigateToURL(
tab_to_execute_service_worker,
https_server.GetURL(
"a.test", "/back_forward_cache/service_worker_registration.html")));
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker,
"register('service_worker_registration.js')"));
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, "claim()"));
deleted.WaitUntilDeleted();
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();
EXPECT_TRUE(NavigateToURL(
tab_to_be_bfcached,
https_server.GetURL("a.test",
"/back_forward_cache/"
"service_worker_registration.html?to_be_bfcached")));
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);
EXPECT_TRUE(NavigateToURL(
tab_to_unregister_service_worker,
https_server.GetURL(
"a.test", "/back_forward_cache/service_worker_registration.html")));
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
https_server.GetURL("b.test", "/title1.html")));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(
"DONE",
EvalJs(tab_to_unregister_service_worker,
"unregister('service_worker_registration.html?to_be_bfcached')"));
deleted.WaitUntilDeleted();
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));
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)));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
keepalive.WaitForRequest();
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_;
};
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_EQ("received", EvalJs(rfh_a, R"(
new Promise(resolve => {
navigator.geolocation.getCurrentPosition(() => resolve('received'));
});
)"));
RenderFrameDeletedObserver deleted(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
}
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"));
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)
);
)"));
EXPECT_EQ(
0, EvalJs(rfh_a, "window.waitForLongitudeRecorded(0.0);").ExtractInt());
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.";
geo_override_.Pause();
geo_override_.UpdateLocation(20.0, 20.0);
EXPECT_EQ(1u, geo_override_.GetGeolocationInstanceCount());
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();
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
geo_override_.Resume();
geo_override_.UpdateLocation(30.0, 30.0);
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());
geo_override_.Resume();
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 {
adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
#if BUILDFLAG(IS_CHROMEOS_ASH)
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) {
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");
});
)";
}
}
}
};
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserWebUsbTest, Serials) {
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));
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());
ASSERT_EQ(
serial_type == SerialType::kSerial ? "Found 0 ports" : "Found 0 devices",
content::EvalJs(main_rfh.get(), GetJsToUseSerial(context, serial_type)));
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
)));
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"));
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;
});
)"));
{
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();
});
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
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);
EXPECT_TRUE(ExecJs(rfh_a, "audio.play();"));
EXPECT_EQ(ListValueOf("canplaythrough", "play", "pause", "play"),
EvalJs(rfh_a, "audio.testObserverEvents"));
}
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"));
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;
});
)"));
{
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();
});
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
base::PlatformThread::Sleep(base::Seconds(1));
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();
ASSERT_GT(1.0, timeOnPageshow);
const double playedDuration = timeOnPageshow - timeOnPagehide;
EXPECT_LE(0.0, playedDuration);
EXPECT_GT(0.02, playedDuration);
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();
)"));
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"));
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();
})
)"));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
delete_observer_rfh_a.WaitUntilDeleted();
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"));
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", () => {});
)"));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_THAT(rfh_a, InBackForwardCache());
}
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);
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);
)";
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));
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)"));
ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)"));
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));
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)"));
ASSERT_EQ("pass", EvalJs(rfh_b, "validateEvents()"));
provider_->UpdateRelativeOrientationSensorData(0, 0, 0);
ASSERT_TRUE(HistoryGoBack(web_contents()));
ASSERT_EQ(rfh_a, current_frame_host());
provider_->UpdateRelativeOrientationSensorData(0, 0, 0);
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)));
ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)"));
}
#if !BUILDFLAG(IS_ANDROID)
#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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_TRUE(ExecJs(rfh_a, "const pc1 = new RTCPeerConnection()"));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
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
#if !BUILDFLAG(IS_ANDROID)
#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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
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");
});
)"));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
delete_observer_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kWebRTC}, {},
{}, {}, FROM_HERE);
}
#endif
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"));
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"(
const never_resolved = new Promise(resolve => {});
new Promise(continue_test => {
navigator.locks.request('test', async () => {
continue_test();
await never_resolved;
});
})
)"));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
delete_observer_rfh_a.WaitUntilDeleted();
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"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(ExecJs(rfh_a, "navigator.requestMIDIAccess()",
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
ASSERT_TRUE(NavigateToURL(shell(), url_b));
delete_observer_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored(
{NotRestoredReason::kBlocklistedFeatures},
{blink::scheduler::WebSchedulerTrackedFeature::kRequestedMIDIPermission},
{}, {}, {}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
DISABLED_PresentationConnectionClosed) {
ASSERT_TRUE(CreateHttpsServer()->Start());
GURL url_a(https_server()->GetURL(
"a.test", "/back_forward_cache/presentation_controller.html"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
auto* rfh_a = current_frame_host();
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));
EXPECT_TRUE(ExecJs(rfh_a, "var foo = 42;"));
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()));
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());
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());
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();
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"));
EXPECT_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(async resolve => {
var r = new webkitSpeechRecognition();
r.start();
resolve();
});
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
delete_observer_rfh_a.WaitUntilDeleted();
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"));
EXPECT_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(async resolve => {
var r = new webkitSpeechRecognition();
resolve();
});
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
ExpectRestored(FROM_HERE);
}
class BackForwardCacheBrowserTestWithSpeechSynthesis
: public BackForwardCacheBrowserTest,
public testing::WithParamInterface<bool> {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
if (IsSpeechSynthesisSupported()) {
EnableFeatureAndSetParams(features::kUnblockSpeechSynthesisForBFCache, "",
"");
} else {
DisableFeature(features::kUnblockSpeechSynthesisForBFCache);
}
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
}
bool IsSpeechSynthesisSupported() { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(All,
BackForwardCacheBrowserTestWithSpeechSynthesis,
testing::Bool());
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_CacheIfUsingSpeechSynthesis DISABLED_CacheIfUsingSpeechSynthesis
#else
#define MAYBE_CacheIfUsingSpeechSynthesis CacheIfUsingSpeechSynthesis
#endif
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithSpeechSynthesis,
MAYBE_CacheIfUsingSpeechSynthesis) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
new Promise(async resolve => {
var u = new SpeechSynthesisUtterance(" ");
speechSynthesis.speak(u);
resolve();
});
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(HistoryGoBack(web_contents()));
if (IsSpeechSynthesisSupported()) {
ExpectRestored(FROM_HERE);
} else {
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"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver deleted_rfh_a(rfh_a);
content::BackForwardCacheDisabledTester tester;
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();
};
base::RunLoop run_loop;
chooser->OpenFileChooser(
blink::mojom::FileChooserParams::New(),
base::BindOnce(quit_run_loop, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(rfh_a->IsBackForwardCacheDisabled());
auto reason = BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kFileChooser);
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
rfh_a->GetProcess()->GetID(), rfh_a->GetRoutingID(), reason));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
deleted_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {},
{}, {reason}, {}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheWithMediaSession) {
ASSERT_TRUE(embedded_test_server()->Start());
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"}
]
});
)"));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(rfh_a.get(), current_frame_host());
ExpectRestored(FROM_HERE);
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"));
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');"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
ExpectRestored(FROM_HERE);
AcquireKeyboardLock(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_FALSE(deleted.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
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 {
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"));
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');"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
deleted_a1.WaitUntilDeleted();
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);
AcquireKeyboardLock(rfh_a2);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
deleted_a2.WaitUntilDeleted();
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();
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.test", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
}
};
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
CacheWhenMediaSessionPlaybackStateIsChanged) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("a.test", "/title1.html")));
EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), R"(
navigator.mediaSession.playbackState = 'playing';
)"));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
CacheWhenMediaSessionServiceIsNotUsed) {
DoNotFailForUnexpectedMessagesWhileCached();
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"a.test", "/media/session/media-session.html")));
PlayVideoNavigateAndGoBack();
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
DontCacheWhenMediaSessionServiceIsUsed) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"a.test", "/media/session/media-session.html")));
RenderFrameHostWrapper rfh_a(current_frame_host());
EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
navigator.mediaSession.setActionHandler('play', () => {});
)"));
PlayVideoNavigateAndGoBack();
auto reason = BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kMediaSessionService);
ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {},
{}, {reason}, {}, FROM_HERE);
}
}