// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/back_forward_cache_browsertest.h"

#include "base/containers/contains.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/generic_sensor/sensor_provider_proxy_impl.h"
#include "content/browser/presentation/presentation_test_utils.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/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"

// This file contains back-/forward-cache tests for web-platform features and
// APIs. It was forked from
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/back_forward_cache_browsertest.cc;drc=1288c1bd6a81785cd85b965d61820a7cd87a0e9c
//
// When adding tests for new features please also add WPTs. See
// third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/README.md

using testing::_;
using testing::Each;
using testing::ElementsAre;
using testing::Not;
using testing::UnorderedElementsAreArray;

namespace content {

using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason;

class BackForwardCacheDedicatedWorkerFlagBrowserTest
    : public BackForwardCacheBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(blink::features::kBackForwardCacheDedicatedWorker,
                              "", "");
    if (IsDedicatedWorkerEnabled()) {
      EnableFeatureAndSetParams(
          blink::features::kBackForwardCacheDedicatedWorker, "", "");
    } else {
      DisableFeature(blink::features::kBackForwardCacheDedicatedWorker);
    }
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

  bool IsDedicatedWorkerEnabled() { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(All,
                         BackForwardCacheDedicatedWorkerFlagBrowserTest,
                         testing::Bool());

IN_PROC_BROWSER_TEST_P(BackForwardCacheDedicatedWorkerFlagBrowserTest,
                       PageWithDedicatedWorkerCachedOrNot) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(NavigateToURL(
      shell(),
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_dedicated_worker.html")));
  ASSERT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise"));
  RenderFrameHostWrapper rfh(current_frame_host());

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // Go back
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Check the outcome.
  if (IsDedicatedWorkerEnabled()) {
    EXPECT_EQ(rfh.get(), current_frame_host());
    ExpectRestored(FROM_HERE);
  } else {
    // The page with the dedicated worker should be deleted (not cached).
    ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
    ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                      {blink::scheduler::WebSchedulerTrackedFeature::
                           kDedicatedWorkerOrWorklet},
                      {}, {}, {}, FROM_HERE);
  }
}

// The bool parameter is used for switching PlzDedicatedWorker.
class BackForwardCacheWithDedicatedWorkerBrowserTest
    : public BackForwardCacheBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  const int kMaxBufferedBytesPerProcess = 10000;
  const base::TimeDelta kGracePeriodToFinishLoading = base::Seconds(5);

  BackForwardCacheWithDedicatedWorkerBrowserTest() { server_.Start(); }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(blink::features::kBackForwardCacheDedicatedWorker,
                              "", "");
    if (IsPlzDedicatedWorkerEnabled())
      EnableFeatureAndSetParams(blink::features::kPlzDedicatedWorker, "", "");
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
    feature_list_.InitWithFeaturesAndParameters(
        {{blink::features::kLoadingTasksUnfreezable,
          {{"max_buffered_bytes_per_process",
            base::NumberToString(kMaxBufferedBytesPerProcess)},
           {"grace_period_to_finish_loading_in_seconds",
            base::NumberToString(kGracePeriodToFinishLoading.InSeconds())}}}},
        {});

    server_.SetUpCommandLine(command_line);
  }

  bool IsPlzDedicatedWorkerEnabled() { return GetParam(); }

  int port() const { return server_.server_address().port(); }

  int CountWorkerClients(RenderFrameHostImpl* rfh) {
    return EvalJs(rfh, JsReplace(R"(
      new Promise(async (resolve) => {
        const resp = await fetch('/service_worker/count_worker_clients');
        resolve(parseInt(await resp.text(), 10));
      });
    )"))
        .ExtractInt();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
  WebTransportSimpleTestServer server_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         BackForwardCacheWithDedicatedWorkerBrowserTest,
                         testing::Bool());

// Confirms that a page using a dedicated worker is cached.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       CacheWithDedicatedWorker) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(),
      https_server()->GetURL(
          "a.test", "/back_forward_cache/page_with_dedicated_worker.html")));
  EXPECT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise"));

  // Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));

  // Go back to the original page.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Confirms that an active page using a dedicated worker that calls
// importScripts won't trigger an eviction IPC, causing the page to reload.
// Regression test for https://crbug.com/1305041.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    PageWithDedicatedWorkerAndImportScriptsWontTriggerReload) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(), https_server()->GetURL(
                   "a.test",
                   "/back_forward_cache/"
                   "page_with_dedicated_worker_and_importscripts.html")));
  // Wait until the importScripts() call finished running.
  EXPECT_EQ(42, EvalJs(current_frame_host(), "window.receivedMessagePromise"));

  // If the importScripts() call triggered an eviction, a reload will be
  // triggered due to the "evict after docment is restored" will be hit, as the
  // page is not in back/forward cache.
  EXPECT_FALSE(
      web_contents()->GetPrimaryFrameTree().root()->navigation_request());
}

// Confirms that a page using a dedicated worker with WebTransport is not
// cached.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       DoNotCacheWithDedicatedWorkerWithWebTransport) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(), https_server()->GetURL(
                   "a.test",
                   "/back_forward_cache/"
                   "page_with_dedicated_worker_and_webtransport.html")));
  // Open a WebTransport.
  EXPECT_EQ("opened",
            EvalJs(current_frame_host(),
                   JsReplace("window.testOpenWebTransport($1);", port())));
  RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());

  // Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
  delete_observer_rfh.WaitUntilDeleted();

  // Go back to the original page. The page was not cached as the worker used
  // WebTransport.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}, {}, {}, {},
      FROM_HERE);
}

// Confirms that a page using a dedicated worker with a closed WebTransport is
// cached as WebTransport is not a sticky feature.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       CacheWithDedicatedWorkerWithWebTransportClosed) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(), https_server()->GetURL(
                   "a.test",
                   "/back_forward_cache/"
                   "page_with_dedicated_worker_and_webtransport.html")));
  // Open and close a WebTransport.
  EXPECT_EQ("opened",
            EvalJs(current_frame_host(),
                   JsReplace("window.testOpenWebTransport($1);", port())));
  EXPECT_EQ("closed",
            EvalJs(current_frame_host(), "window.testCloseWebTransport();"));

  // Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));

  // Go back to the original page. The page was cached. Even though WebTransport
  // is used once, the page is eligible for back-forward cache as the feature is
  // not sticky.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// TODO(https://crbug.com/1299018): Flaky on Linux.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel \
  DISABLED_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel
#else
#define MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel \
  DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel
#endif
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    MAYBE_DoNotCacheWithDedicatedWorkerWithWebTransportAndDocumentWithBroadcastChannel) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(), https_server()->GetURL(
                   "a.test",
                   "/back_forward_cache/"
                   "page_with_dedicated_worker_and_webtransport.html")));

  // Open a WebTransport in the dedicated worker.
  EXPECT_EQ("opened",
            EvalJs(current_frame_host(),
                   JsReplace("window.testOpenWebTransport($1);", port())));
  // testOpenWebTransport sends the IPC (BackForwardCacheController.
  // DidChangeBackForwardCacheDisablingFeatures) from a renderer. Run a script
  // to wait for the IPC reaching to the browser.
  EXPECT_EQ(42, EvalJs(current_frame_host(), "42;"));
  EXPECT_TRUE(
      DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(
          current_frame_host())
          ->GetBackForwardCacheDisablingFeatures()
          .HasAll(
              {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}));

  // Use a broadcast channel in the frame.
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     "window.foo = new BroadcastChannel('foo');"));
  RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());

  // Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
  delete_observer_rfh.WaitUntilDeleted();

  // Go back to the original page. The page was not cached due to WebTransport
  // and a broadcast channel, which came from the dedicated worker and the frame
  // respectively. Confirm both are recorded.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport,
       blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
      {}, {}, {}, FROM_HERE);
}

// TODO(https://crbug.com/1296306): Disabled due to being flaky.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    DISABLED_DoNotCacheWithDedicatedWorkerWithClosedWebTransportAndDocumentWithBroadcastChannel) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(), https_server()->GetURL(
                   "a.test",
                   "/back_forward_cache/"
                   "page_with_dedicated_worker_and_webtransport.html")));

  // Open and close a WebTransport in the dedicated worker.
  EXPECT_EQ("opened",
            EvalJs(current_frame_host(),
                   JsReplace("window.testOpenWebTransport($1);", port())));
  // testOpenWebTransport sends the IPC (BackForwardCacheController.
  // DidChangeBackForwardCacheDisablingFeatures) from a renderer. Run a script
  // to wait for the IPC reaching to the browser.
  EXPECT_EQ(42, EvalJs(current_frame_host(), "42;"));
  EXPECT_TRUE(
      DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(
          current_frame_host())
          ->GetBackForwardCacheDisablingFeatures()
          .HasAll(
              {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}));

  EXPECT_EQ("closed",
            EvalJs(current_frame_host(),
                   JsReplace("window.testCloseWebTransport($1);", port())));
  // testOpenWebTransport sends the IPC (BackForwardCacheController.
  // DidChangeBackForwardCacheDisablingFeatures) from a renderer. Run a script
  // to wait for the IPC reaching to the browser.
  EXPECT_EQ(42, EvalJs(current_frame_host(), "42;"));
  EXPECT_TRUE(DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(
                  current_frame_host())
                  ->GetBackForwardCacheDisablingFeatures()
                  .Empty());

  // Use a broadcast channel in the frame.
  EXPECT_TRUE(ExecJs(current_frame_host(),
                     "window.foo = new BroadcastChannel('foo');"));
  RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());

  // Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));
  delete_observer_rfh.WaitUntilDeleted();

  // Go back to the original page. The page was not cached due to a broadcast
  // channel, which came from the frame. WebTransport was used once in the
  // dedicated worker but was closed, then this doesn't affect the cache usage.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
      {}, FROM_HERE);
}

// Tests the case when the page starts fetching in a dedicated worker, goes to
// BFcache, and then a redirection happens. The cached page should evicted in
// this case.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       FetchRedirectedWhileStoring) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse fetch1_response(https_server(),
                                                             "/fetch1");
  net::test_server::ControllableHttpResponse fetch2_response(https_server(),
                                                             "/fetch2");
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // Trigger a fetch in a dedicated worker.
  std::string worker_script =
      JsReplace(R"(
    fetch($1);
  )",
                https_server()->GetURL("a.test", "/fetch1"));
  EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                      worker_script)));

  fetch1_response.WaitForRequest();

  // Navigate to B.
  PageLifecycleStateManagerTestDelegate delegate(
      rfh_a->render_view_host()->GetPageLifecycleStateManager());
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());

  // Page A is initially stored in the back-forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Respond the fetch with a redirect.
  fetch1_response.Send(
      "HTTP/1.1 302 Moved Temporarily\r\n"
      "Location: /fetch2\r\n\r\n");
  fetch1_response.Done();

  // Ensure that the request to /fetch2 was never sent (because the page is
  // immediately evicted) by checking after 3 seconds.
  base::RunLoop loop1;
  base::OneShotTimer timer1;
  timer1.Start(FROM_HERE, base::Seconds(3), loop1.QuitClosure());
  loop1.Run();
  EXPECT_EQ(nullptr, fetch2_response.http_request());

  // Page A should be evicted from the back-forward cache.
  delete_observer_rfh_a.WaitUntilDeleted();

  // Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kNetworkRequestRedirected}, {}, {}, {},
                    {}, FROM_HERE);
}

// Tests the case when the page starts fetching in a nested dedicated worker,
// goes to BFcache, and then a redirection happens. The cached page should
// evicted in this case.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       FetchRedirectedWhileStoring_Nested) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse fetch1_response(https_server(),
                                                             "/fetch1");
  net::test_server::ControllableHttpResponse fetch2_response(https_server(),
                                                             "/fetch2");

  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // Trigger a fetch in a nested dedicated worker.
  std::string child_worker_script =
      JsReplace(R"(
    fetch($1);
  )",
                https_server()->GetURL("a.test", "/fetch1"));
  std::string parent_worker_script = JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                               child_worker_script);
  EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
    worker.onmessage = () => { resolve(); }
  )",
                                      parent_worker_script)));

  fetch1_response.WaitForRequest();

  // Navigate to B.
  PageLifecycleStateManagerTestDelegate delegate(
      rfh_a->render_view_host()->GetPageLifecycleStateManager());
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());

  // Page A is initially stored in the back-forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Respond the fetch with a redirect.
  fetch1_response.Send(
      "HTTP/1.1 302 Moved Temporarily\r\n"
      "Location: /fetch2\r\n\r\n");
  fetch1_response.Done();

  // Ensure that the request to /fetch2 was never sent (because the page is
  // immediately evicted) by checking after 3 seconds.
  base::RunLoop loop2;
  base::OneShotTimer timer2;
  timer2.Start(FROM_HERE, base::Seconds(3), loop2.QuitClosure());
  loop2.Run();
  EXPECT_EQ(nullptr, fetch2_response.http_request());

  // Page A should be evicted from the back-forward cache.
  delete_observer_rfh_a.WaitUntilDeleted();

  // Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kNetworkRequestRedirected}, {}, {}, {},
                    {}, FROM_HERE);
}

// Tests the case when the page starts fetching in a dedicated worker, goes to
// BFcache, and then the response amount reaches the threshold. The cached page
// should evicted in this case.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    FetchStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse image_response(https_server(),
                                                            "/image.png");
  ASSERT_TRUE(https_server()->Start());

  // Navigate to a page.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html")));
  RenderFrameHostImpl* rfh_a = current_frame_host();

  // Trigger a fetch in a dedicated worker.
  std::string worker_script =
      JsReplace(R"(
    fetch($1);
  )",
                https_server()->GetURL("a.test", "/image.png"));
  EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                      worker_script)));

  // Wait for the image request, but don't send anything yet.
  image_response.WaitForRequest();

  // Navigate away.
  PageLifecycleStateManagerTestDelegate delegate(
      rfh_a->render_view_host()->GetPageLifecycleStateManager());
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title2.html")));
  ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());

  // The worker was still loading when we navigated away, but it's still
  // eligible for back-forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
  // Start sending the image response while in the back-forward cache.
  image_response.Send(net::HTTP_OK, "image/png");
  std::string body(kMaxBufferedBytesPerProcess + 1, '*');
  image_response.Send(body);
  image_response.Done();
  delete_observer_rfh_a.WaitUntilDeleted();

  // Go back to the first page. We should not restore the page from the
  // back-forward cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kNetworkExceedsBufferLimit}, {}, {}, {},
                    {}, FROM_HERE);
}

// Tests the case when the page starts fetching in a nested dedicated worker,
// goes to BFcache, and then the response amount reaches the threshold. The
// cached page should evicted in this case.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    FetchStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit_Nested) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse image_response(https_server(),
                                                            "/image.png");
  ASSERT_TRUE(https_server()->Start());

  // Navigate to a page.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html")));
  RenderFrameHostImpl* rfh_a = current_frame_host();

  // Trigger a fetch in a nested dedicated worker.
  std::string child_worker_script =
      JsReplace(R"(
    fetch($1);
  )",
                https_server()->GetURL("a.test", "/image.png"));
  std::string parent_worker_script = JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                               child_worker_script);
  EXPECT_TRUE(ExecJs(rfh_a, JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                      parent_worker_script)));

  // Wait for the image request, but don't send anything yet.
  image_response.WaitForRequest();

  // Navigate away.
  PageLifecycleStateManagerTestDelegate delegate(
      rfh_a->render_view_host()->GetPageLifecycleStateManager());
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title2.html")));
  ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
  // The worker was still loading when we navigated away, but it's still
  // eligible for back-forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
  // Start sending the image response while in the back-forward cache.
  image_response.Send(net::HTTP_OK, "image/png");
  std::string body(kMaxBufferedBytesPerProcess + 1, '*');
  image_response.Send(body);
  image_response.Done();
  delete_observer_rfh_a.WaitUntilDeleted();

  // Go back to the first page. We should not restore the page from the
  // back-forward cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kNetworkExceedsBufferLimit}, {}, {}, {},
                    {}, FROM_HERE);
}

// Tests the case when fetching started in a dedicated worker and the header was
// received before the page is frozen, but parts of the response body is
// received when the page is frozen.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       PageWithDrainedDatapipeRequestsForFetchShouldBeEvicted) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse fetch_response(https_server(),
                                                            "/fetch");

  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Call fetch in a dedicated worker before navigating away.
  std::string worker_script =
      JsReplace("fetch($1)", https_server()->GetURL("a.test", "/fetch"));
  EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                            worker_script)));
  // Send response header and a piece of the body. This receiving the response
  // doesn't end (i.e. Done is not called) before navigating away. In this case,
  // the page will be evicted when the page is frozen.
  fetch_response.WaitForRequest();
  fetch_response.Send(net::HTTP_OK, "text/plain");
  fetch_response.Send("body");

  // Navigate to B.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back to A. kNetworkRequestDatapipeDrainedAsBytesConsumer is recorded
  // since receiving the response body started but this didn't end before the
  // navigation to B.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kNetworkRequestDatapipeDrainedAsBytesConsumer}, {},
      {}, {}, {}, FROM_HERE);
}

// Tests the case when fetching started in a nested dedicated worker and the
// header was received before the page is frozen, but parts of the response body
// is received when the page is frozen.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    PageWithDrainedDatapipeRequestsForFetchShouldBeEvicted_Nested) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse fetch_response(https_server(),
                                                            "/fetch");

  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Call fetch in a nested dedicated worker before navigating away.
  std::string child_worker_script =
      JsReplace("fetch($1)", https_server()->GetURL("a.test", "/fetch"));
  std::string parent_worker_script = JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                               child_worker_script);
  EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                            parent_worker_script)));
  // Send response header and a piece of the body. This receiving the response
  // doesn't end (i.e. Done is not called) before navigating away. In this case,
  // the page will be evicted when the page is frozen.
  fetch_response.WaitForRequest();
  fetch_response.Send(net::HTTP_OK, "text/plain");
  fetch_response.Send("body");

  // Navigate to B.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back to A. kNetworkRequestDatapipeDrainedAsBytesConsumer is recorded
  // since receiving the response body started but this didn't end before the
  // navigation to B.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kNetworkRequestDatapipeDrainedAsBytesConsumer}, {},
      {}, {}, {}, FROM_HERE);
}

// Tests the case when fetch started in a dedicated worker, but the response
// never ends after the page is frozen. This should result in an eviction due to
// timeout.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       ImageStillLoading_ResponseStartedWhileFrozen_Timeout) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse image_response(https_server(),
                                                            "/image.png");
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Call fetch in a dedicated worker before navigating away.
  std::string worker_script =
      JsReplace(R"(
    fetch($1);
  )",
                https_server()->GetURL("a.test", "/image.png"));
  EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                            worker_script)));

  // Wait for the image request, but don't send anything yet.
  image_response.WaitForRequest();

  // Navigate away.
  PageLifecycleStateManagerTestDelegate delegate(
      rfh_a->render_view_host()->GetPageLifecycleStateManager());
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
  // The page was still loading when we navigated away, but it's still eligible
  // for back-forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Start sending the image response while in the back-forward cache, but never
  // finish the request. Eventually the page will get deleted due to network
  // request timeout.
  image_response.Send(net::HTTP_OK, "image/png");
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back to the first page. We should not restore the page from the
  // back-forward cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kNetworkRequestTimeout}, {}, {}, {}, {},
                    FROM_HERE);
}

// Tests the case when fetch started in a nested dedicated worker, but the
// response never ends after the page is frozen. This should result in an
// eviction due to timeout.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithDedicatedWorkerBrowserTest,
    ImageStillLoading_ResponseStartedWhileFrozen_Timeout_Nested) {
  CreateHttpsServer();

  net::test_server::ControllableHttpResponse image_response(https_server(),
                                                            "/image.png");
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Call fetch in a dedicated worker before navigating away.
  std::string child_worker_script =
      JsReplace(R"(
    fetch($1);
  )",
                https_server()->GetURL("a.test", "/image.png"));
  std::string parent_worker_script = JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                               child_worker_script);
  EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"(
    const blob = new Blob([$1]);
    const blobURL = URL.createObjectURL(blob);
    const worker = new Worker(blobURL);
  )",
                                            parent_worker_script)));

  // Wait for the image request, but don't send anything yet.
  image_response.WaitForRequest();

  // Navigate away.
  PageLifecycleStateManagerTestDelegate delegate(
      rfh_a->render_view_host()->GetPageLifecycleStateManager());
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(delegate.WaitForInBackForwardCacheAck());
  // The page was still loading when we navigated away, but it's still eligible
  // for back-forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Start sending the image response while in the back-forward cache, but never
  // finish the request. Eventually the page will get deleted due to network
  // request timeout.
  image_response.Send(net::HTTP_OK, "image/png");
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back to the first page. We should not restore the page from the
  // back-forward cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kNetworkRequestTimeout}, {}, {}, {}, {},
                    FROM_HERE);
}

// Tests that dedicated workers in back/forward cache are not visible to a
// service worker.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       ServiceWorkerClientMatchAll) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  GURL url_a1(https_server()->GetURL(
      "a.test", "/service_worker/create_service_worker.html"));
  GURL url_a2(https_server()->GetURL("a.test", "/service_worker/empty.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
  EXPECT_EQ(
      "DONE",
      EvalJs(current_frame_host(),
             "register('/service_worker/fetch_event_worker_clients.js');"));

  // Reload the page to enable fetch to be hooked by the service worker.
  web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Confirm there is no worker client.
  EXPECT_EQ(0, CountWorkerClients(rfh_a.get()));

  // Call fetch in a dedicated worker. If the PlzDedicatedWorker is enabled, the
  // number of worker clients should be 1. If PlzDedicatedWorker is disabled,
  // worker clients are not supported, so the number should be 0.
  int expected_number = IsPlzDedicatedWorkerEnabled() ? 1 : 0;
  std::string dedicated_worker_script = JsReplace(
      R"(
    (async() => {
      const response = await fetch($1);
      postMessage(await response.text());
    })();
  )",
      https_server()->GetURL("a.test", "/service_worker/count_worker_clients"));
  EXPECT_EQ(base::NumberToString(expected_number),
            EvalJs(rfh_a.get(), JsReplace(R"(
    new Promise(async (resolve) => {
      const blobURL = URL.createObjectURL(new Blob([$1]));
      const dedicatedWorker = new Worker(blobURL);
      dedicatedWorker.addEventListener('message', e => {
        resolve(e.data);
      });
    });
  )",
                                          dedicated_worker_script)));

  // Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Confirm that the worker in back/forward cache is invisible from the service
  // worker.
  EXPECT_EQ(0, CountWorkerClients(current_frame_host()));

  // Restore from the back/forward cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(expected_number, CountWorkerClients(current_frame_host()));
}

// Tests that dedicated workers, including a nested dedicated workers, in
// back/forward cache are not visible to a service worker.
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       ServiceWorkerClientMatchAll_Nested) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  GURL url_a1(https_server()->GetURL(
      "a.test", "/service_worker/create_service_worker.html"));
  GURL url_a2(https_server()->GetURL("a.test", "/service_worker/empty.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
  EXPECT_EQ(
      "DONE",
      EvalJs(current_frame_host(),
             "register('/service_worker/fetch_event_worker_clients.js');"));

  // Reload the page to enable fetch to be hooked by the service worker.
  web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Confirm there is no worker client.
  EXPECT_EQ(0, CountWorkerClients(rfh_a.get()));

  // Call fetch in a dedicated worker. If the PlzDedicatedWorker is enabled, the
  // number of worker clients should be 2. If PlzDedicatedWorker is disabled,
  // worker clients are not supported, so the number should be 0.
  int expected_number = IsPlzDedicatedWorkerEnabled() ? 2 : 0;
  std::string child_worker_script = JsReplace(
      R"(
    (async() => {
      const response = await fetch($1);
      postMessage(await response.text());
    })();
  )",
      https_server()->GetURL("a.test", "/service_worker/count_worker_clients"));
  std::string parent_worker_script = JsReplace(
      R"(
    const blobURL = URL.createObjectURL(new Blob([$1]));
    const dedicatedWorker = new Worker(blobURL);
    dedicatedWorker.addEventListener('message', e => {
      postMessage(e.data);
    });
  )",
      child_worker_script);
  EXPECT_EQ(base::NumberToString(expected_number),
            EvalJs(rfh_a.get(), JsReplace(R"(
    new Promise(async (resolve) => {
      const blobURL = URL.createObjectURL(new Blob([$1]));
      const dedicatedWorker = new Worker(blobURL);
      dedicatedWorker.addEventListener('message', e => {
        resolve(e.data);
      });
    });
  )",
                                          parent_worker_script)));

  // Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Confirm that the worker in back/forward cache is invisible from the service
  // worker.
  EXPECT_EQ(0, CountWorkerClients(current_frame_host()));

  // Restore from the back/forward cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(expected_number, CountWorkerClients(current_frame_host()));
}

// Tests that dedicated workers in back/forward cache are not visible to a
// service worker. This works correctly even if a dedicated worker is not loaded
// completely when the page is put into back/forward cache,
IN_PROC_BROWSER_TEST_P(BackForwardCacheWithDedicatedWorkerBrowserTest,
                       ServiceWorkerClientMatchAll_LoadWorkerAfterRestoring) {
  CreateHttpsServer();

  // Prepare a controllable HTTP response for a dedicated worker. Use
  // /service_worker path to match with the service worker's scope.
  net::test_server::ControllableHttpResponse dedicated_worker_response(
      https_server(),
      "/service_worker/dedicated_worker_using_service_worker.js");

  ASSERT_TRUE(https_server()->Start());

  GURL url_a1(https_server()->GetURL(
      "a.test", "/service_worker/create_service_worker.html"));
  GURL url_a2(https_server()->GetURL("a.test", "/service_worker/empty.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
  EXPECT_EQ(
      "DONE",
      EvalJs(current_frame_host(),
             "register('/service_worker/fetch_event_worker_clients.js');"));

  // Reload the page to enable fetch to be hooked by the service worker.
  web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Confirm there is no worker client.
  EXPECT_EQ(0, CountWorkerClients(rfh_a.get()));

  // Start to requet a worker URL.
  EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
    window.dedicatedWorkerUsingServiceWorker = new Worker(
        '/service_worker/dedicated_worker_using_service_worker.js');
  )"));

  dedicated_worker_response.WaitForRequest();

  // Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Return the dedicated worker script.
  dedicated_worker_response.Send(net::HTTP_OK, "text/javascript");
  dedicated_worker_response.Send(R"(
    onmessage = e => {
      postMessage(e.data);
    };
  )");
  dedicated_worker_response.Done();

  // Confirm that the worker in back/forward cache is invisible from the service
  // worker.
  EXPECT_EQ(0, CountWorkerClients(current_frame_host()));

  // Restore from the back/forward cache. Now the number of client is 1.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);

  // Confirm that the dedicated worker is completely loaded.
  EXPECT_EQ("foo", EvalJs(current_frame_host(), JsReplace(R"(
    new Promise(async (resolve) => {
      window.dedicatedWorkerUsingServiceWorker.onmessage = e => {
        resolve(e.data);
      };
      window.dedicatedWorkerUsingServiceWorker.postMessage("foo");
    });
  )")));

  // If the PlzDedicatedWorker is enabled, the number of worker clients should
  // be 1. If PlzDedicatedWorker is disabled, worker clients are not supported,
  // so the number should be 0.
  EXPECT_EQ(IsPlzDedicatedWorkerEnabled() ? 1 : 0,
            CountWorkerClients(current_frame_host()));
}

// TODO(https://crbug.com/154571): Shared workers are not available on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PageWithSharedWorkerNotCached \
  DISABLED_PageWithSharedWorkerNotCached
#else
#define MAYBE_PageWithSharedWorkerNotCached PageWithSharedWorkerNotCached
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_PageWithSharedWorkerNotCached) {
  ASSERT_TRUE(embedded_test_server()->Start());

  EXPECT_TRUE(NavigateToURL(
      shell(),
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_shared_worker.html")));
  RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());

  // Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // The page with the unsupported feature should be deleted (not cached).
  delete_observer_rfh_a.WaitUntilDeleted();

  // Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kSharedWorker}, {}, {}, {},
      FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       AllowedFeaturesForSubframesDoNotEvict) {
  // The main purpose of this test is to check that when a state of a subframe
  // is updated, CanStoreDocument is still called for the main frame - otherwise
  // we would always evict the document, even when the feature is allowed as
  // CanStoreDocument always returns false for non-main frames.

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);

  // 2) Navigate to C.
  ASSERT_TRUE(NavigateToURL(shell(), url_c));

  // 3) No-op feature update on a subframe while in cache, should be no-op.
  ASSERT_FALSE(delete_observer_rfh_b.deleted());
  RenderFrameHostImpl::BackForwardCacheBlockingDetails empty_vector;
  rfh_b->DidChangeBackForwardCacheDisablingFeatures(std::move(empty_vector));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(current_frame_host(), rfh_a);

  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheIfRecordingAudio) {
  ASSERT_TRUE(embedded_test_server()->Start());

  BackForwardCacheDisabledTester tester;

  // Navigate to an empty page.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  int process_id = current_frame_host()->GetProcess()->GetID();
  int routing_id = current_frame_host()->GetRoutingID();

  // Request for audio recording.
  EXPECT_EQ("success", EvalJs(current_frame_host(), R"(
    new Promise(resolve => {
      navigator.mediaDevices.getUserMedia({audio: true})
        .then(m => { window.keepaliveMedia = m; resolve("success"); })
        .catch(() => { resolve("error"); });
    });
  )"));

  RenderFrameDeletedObserver deleted(current_frame_host());

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // The page was still recording audio when we navigated away, so it shouldn't
  // have been cached.
  deleted.WaitUntilDeleted();

  // 3) Go back. Note that the reason for kWasGrantedMediaAccess occurs after
  // MediaDevicesDispatcherHost is called, hence, both are reasons for the page
  // not being restored.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  auto reason = BackForwardCacheDisable::DisabledReason(
      BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost);
  ExpectNotRestored({NotRestoredReason::kWasGrantedMediaAccess,
                     NotRestoredReason::kDisableForRenderFrameHostCalled},
                    {}, {}, {reason}, {}, FROM_HERE);
  EXPECT_TRUE(
      tester.IsDisabledForFrameWithReason(process_id, routing_id, reason));
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheIfSubframeRecordingAudio) {
  ASSERT_TRUE(embedded_test_server()->Start());

  BackForwardCacheDisabledTester tester;

  // Navigate to a page with an iframe.
  GURL url(embedded_test_server()->GetURL("/page_with_iframe.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh = current_frame_host();
  int process_id =
      rfh->child_at(0)->current_frame_host()->GetProcess()->GetID();
  int routing_id = rfh->child_at(0)->current_frame_host()->GetRoutingID();

  // Request for audio recording from the subframe.
  EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"(
    new Promise(resolve => {
      navigator.mediaDevices.getUserMedia({audio: true})
        .then(m => { resolve("success"); })
        .catch(() => { resolve("error"); });
    });
  )"));

  RenderFrameDeletedObserver deleted(current_frame_host());

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // The page was still recording audio when we navigated away, so it shouldn't
  // have been cached.
  deleted.WaitUntilDeleted();

  // 3) Go back. Note that the reason for kWasGrantedMediaAccess occurs after
  // MediaDevicesDispatcherHost is called, hence, both are reasons for the page
  // not being restored.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  auto reason = BackForwardCacheDisable::DisabledReason(
      BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost);

  ExpectNotRestored({NotRestoredReason::kWasGrantedMediaAccess,
                     NotRestoredReason::kDisableForRenderFrameHostCalled},
                    {}, {}, {reason}, {}, FROM_HERE);
  EXPECT_TRUE(
      tester.IsDisabledForFrameWithReason(process_id, routing_id, reason));
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheIfMediaDeviceSubscribed) {
  ASSERT_TRUE(embedded_test_server()->Start());

  BackForwardCacheDisabledTester tester;

  // Navigate to a page with an iframe.
  GURL url(embedded_test_server()->GetURL("/page_with_iframe.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh = current_frame_host();
  int process_id =
      rfh->child_at(0)->current_frame_host()->GetProcess()->GetID();
  int routing_id = rfh->child_at(0)->current_frame_host()->GetRoutingID();

  EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"(
    new Promise(resolve => {
      navigator.mediaDevices.addEventListener(
          'devicechange', function(event){});
      resolve("success");
    });
  )"));

  RenderFrameDeletedObserver deleted(current_frame_host());

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // The page was subscribed to media devices when we navigated away, so it
  // shouldn't have been cached.
  deleted.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  auto reason = BackForwardCacheDisable::DisabledReason(
      BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost);
  ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {},
                    {}, {reason}, {}, FROM_HERE);
  EXPECT_TRUE(
      tester.IsDisabledForFrameWithReason(process_id, routing_id, reason));
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheIfWebGL) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page with WebGL usage
  GURL url(embedded_test_server()->GetURL(
      "example.com", "/back_forward_cache/page_with_webgl.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // The page had an active WebGL context when we navigated away,
  // but it should be cached.

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Since blink::mojom::HidService binder is not added in
// content/browser/browser_interface_binders.cc for Android, this test is not
// applicable for this OS.
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebHID) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to an empty page.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // Request for HID devices.
  EXPECT_EQ("success", EvalJs(current_frame_host(), R"(
    new Promise(resolve => {
      navigator.hid.getDevices()
        .then(m => { resolve("success"); })
        .catch(() => { resolve("error"); });
    });
  )"));

  RenderFrameDeletedObserver deleted(current_frame_host());

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // The page uses WebHID so it should be deleted.
  deleted.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {blink::scheduler::WebSchedulerTrackedFeature::kWebHID}, {},
                    {}, {}, FROM_HERE);
}
#endif  // !BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       WakeLockReleasedUponEnteringBfcache) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  // 1) Navigate to a page with WakeLock usage.
  GURL url(https_server()->GetURL(
      "a.test", "/back_forward_cache/page_with_wakelock.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  RenderFrameHostImpl* rfh_a = current_frame_host();
  // Acquire WakeLock.
  EXPECT_EQ("DONE", EvalJs(rfh_a, "acquireWakeLock()"));
  // Make sure that WakeLock is not released yet.
  EXPECT_FALSE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool());

  // 2) Navigate away.
  shell()->LoadURL(https_server()->GetURL("b.test", "/title1.html"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back to the page with WakeLock, restored from BackForwardCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(current_frame_host(), rfh_a);
  EXPECT_TRUE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool());
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheWithWebFileSystem) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page with WebFileSystem usage.
  GURL url(embedded_test_server()->GetURL("a.test", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  // Writer a file 'file.txt' with a content 'foo'.
  EXPECT_EQ("success", EvalJs(rfh_a, R"(
      new Promise((resolve, reject) => {
        window.webkitRequestFileSystem(
          window.TEMPORARY,
          1024 * 1024,
          (fs) => {
            fs.root.getFile('file.txt', {create: true}, (entry) => {
              entry.createWriter((writer) => {
                writer.onwriteend = () => {
                  resolve('success');
                };
                writer.onerror = reject;
                var blob = new Blob(['foo'], {type: 'text/plain'});
                writer.write(blob);
              }, reject);
            }, reject);
          }, reject);
        });
    )"));

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.test", "/title1.html"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // 3) Go back to the page with WebFileSystem.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
  // Check the file content is reserved.
  EXPECT_EQ("foo", EvalJs(rfh_a, R"(
      new Promise((resolve, reject) => {
        window.webkitRequestFileSystem(
          window.TEMPORARY,
          1024 * 1024,
          (fs) => {
            fs.root.getFile('file.txt', {}, (entry) => {
              entry.file((file) => {
                const reader = new FileReader();
                reader.onloadend = (e) => {
                  resolve(e.target.result);
                };
                reader.readAsText(file);
              }, reject);
            }, reject);
          }, reject);
        });
    )"));
}

namespace {

class FakeIdleTimeProvider : public ui::IdleTimeProvider {
 public:
  FakeIdleTimeProvider() = default;
  ~FakeIdleTimeProvider() override = default;
  FakeIdleTimeProvider(const FakeIdleTimeProvider&) = delete;
  FakeIdleTimeProvider& operator=(const FakeIdleTimeProvider&) = delete;

  base::TimeDelta CalculateIdleTime() override { return base::Seconds(0); }

  bool CheckIdleStateIsLocked() override { return false; }
};

}  // namespace

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIdleManager) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the IdleManager class.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);

  ui::test::ScopedIdleProviderForTest scoped_idle_provider(
      std::make_unique<FakeIdleTimeProvider>());

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(async resolve => {
      let idleDetector = new IdleDetector();
      idleDetector.start();
      resolve();
    });
  )"));

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // The page uses IdleManager so it should be deleted.
  deleted.WaitUntilDeleted();

  // 3) Go back and make sure the IdleManager page wasn't in the cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kIdleManager}, {}, {}, {},
      FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheSMSService) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the SMSService.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver rfh_a_deleted(rfh_a);

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    navigator.credentials.get({otp: {transport: ["sms"]}});
  )",
                     EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // The page uses SMSService so it should be deleted.
  rfh_a_deleted.WaitUntilDeleted();

  // 3) Go back and make sure the SMSService page wasn't in the cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Note that on certain linux tests, there is occasionally a not restored
  // reason of kDisableForRenderFrameHostCalled. This is due to the javascript
  // navigator.credentials.get, which will call on authentication code for linux
  // but not other operating systems. The authenticator code explicitly invokes
  // kDisableForRenderFrameHostCalled. This causes flakiness if we check against
  // all not restored reasons. As a result, we only check for the blocklist
  // reason.
  ExpectBlocklistedFeature(
      blink::scheduler::WebSchedulerTrackedFeature::kWebOTPService, FROM_HERE);
}

namespace {

void OnInstallPaymentApp(base::OnceClosure done_callback,
                         bool* out_success,
                         bool success) {
  *out_success = success;
  std::move(done_callback).Run();
}

}  // namespace

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCachePaymentManager) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  base::RunLoop run_loop;
  GURL service_worker_javascript_file_url =
      https_server()->GetURL("a.test", "/payments/payment_app.js");
  bool success = false;
  PaymentAppProvider::GetOrCreateForWebContents(shell()->web_contents())
      ->InstallPaymentAppForTesting(
          /*app_icon=*/SkBitmap(), service_worker_javascript_file_url,
          /*service_worker_scope=*/
          service_worker_javascript_file_url.GetWithoutFilename(),
          /*payment_method_identifier=*/
          url::Origin::Create(service_worker_javascript_file_url).Serialize(),
          base::BindOnce(&OnInstallPaymentApp, run_loop.QuitClosure(),
                         &success));
  run_loop.Run();
  ASSERT_TRUE(success);

  // 1) Navigate to a page which includes PaymentManager functionality. Note
  // that service workers are used, and therefore we use https server instead of
  // embedded_server()
  EXPECT_TRUE(NavigateToURL(
      shell(), https_server()->GetURL(
                   "a.test", "/payments/payment_app_invocation.html")));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver rfh_a_deleted(rfh_a);

  // Execute functionality that calls PaymentManager.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(async resolve => {
      const registration = await navigator.serviceWorker.getRegistration(
          '/payments/payment_app.js');
      await registration.paymentManager.enableDelegations(['shippingAddress']);
      resolve();
    });
  )"));

  // 2) Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));

  // The page uses PaymentManager so it should be deleted.
  rfh_a_deleted.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager}, {}, {},
      {}, FROM_HERE);

  // Note that on Mac10.10, there is occasionally blocklisting for network
  // requests (kOutstandingNetworkRequestOthers). This causes flakiness if we
  // check against all blocklisted features. As a result, we only check for the
  // blocklist we care about.
  base::HistogramBase::Sample sample = base::HistogramBase::Sample(
      blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager);
  std::vector<base::Bucket> blocklist_values = histogram_tester().GetAllSamples(
      "BackForwardCache.HistoryNavigationOutcome."
      "BlocklistedFeature");
  EXPECT_TRUE(base::Contains(blocklist_values, sample, &base::Bucket::min));

  std::vector<base::Bucket> all_sites_blocklist_values =
      histogram_tester().GetAllSamples(
          "BackForwardCache.AllSites.HistoryNavigationOutcome."
          "BlocklistedFeature");

  EXPECT_TRUE(
      base::Contains(all_sites_blocklist_values, sample, &base::Bucket::min));
}

// Pages with acquired keyboard lock should not enter BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheOnKeyboardLock) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the Keyboard lock.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver rfh_a_deleted(rfh_a);

  AcquireKeyboardLock(rfh_a);

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // The page uses keyboard lock so it should be deleted.
  rfh_a_deleted.WaitUntilDeleted();

  // 3) Go back and make sure the keyboard lock page wasn't in the cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {},
      FROM_HERE);
}

// If pages released keyboard lock, they can enter BackForwardCache. It will
// remain eligible for multiple restores.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CacheIfKeyboardLockReleasedMultipleRestores) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the Keyboard lock.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  AcquireKeyboardLock(rfh_a.get());
  ReleaseKeyboardLock(rfh_a.get());

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  RenderFrameHostImplWrapper rfh_b(current_frame_host());

  // 3) Go back and page should be restored from BackForwardCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);

  // 4) Go forward and back, the page should be restored from BackForwardCache.
  ASSERT_TRUE(HistoryGoForward(web_contents()));
  EXPECT_EQ(rfh_b.get(), current_frame_host());
  ExpectRestored(FROM_HERE);

  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a.get(), current_frame_host());
  ExpectRestored(FROM_HERE);
}

// If pages previously released the keyboard lock, but acquired it again, they
// cannot enter BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoNotCacheIfKeyboardLockIsHeldAfterRelease) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the Keyboard lock.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  AcquireKeyboardLock(rfh_a.get());
  ReleaseKeyboardLock(rfh_a.get());
  AcquireKeyboardLock(rfh_a.get());

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // The page uses keyboard lock so it should be deleted.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back and make sure the keyboard lock page wasn't in the cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {},
      FROM_HERE);
}

// If pages released keyboard lock before navigation, they can enter
// BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CacheIfKeyboardLockReleased) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the Keyboard lock.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  AcquireKeyboardLock(rfh_a.get());
  ReleaseKeyboardLock(rfh_a.get());

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // 3) Go back and page should be restored from BackForwardCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// If pages released keyboard lock during pagehide, they can enter
// BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CacheIfKeyboardLockReleasedInPagehide) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the Keyboard lock.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  AcquireKeyboardLock(rfh_a.get());
  // Register a pagehide handler to release keyboard lock.
  EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
    window.onpagehide = function(e) {
      new Promise(resolve => {
      navigator.keyboard.unlock();
      resolve();
      });
    };
  )"));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // 3) Go back and page should be restored from BackForwardCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheWithDummyStickyFeature) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page and start using the dummy sticky feature.
  GURL url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // The page uses the dummy sticky feature so it should be deleted.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back and make sure the dummy sticky feature page wasn't in the cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {blink::scheduler::WebSchedulerTrackedFeature::kDummy}, {},
                    {}, {}, FROM_HERE);
}

// Tests which blocklisted features are tracked in the metrics when we used
// blocklisted features (sticky and non-sticky) and do a browser-initiated
// cross-site navigation.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       BlocklistedFeaturesTracking_CrossSite_BrowserInitiated) {
  ASSERT_TRUE(CreateHttpsServer()->Start());
  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title2.html"));
  // 1) Navigate to a page.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_a = current_frame_host();
  scoped_refptr<SiteInstanceImpl> site_instance_a =
      static_cast<SiteInstanceImpl*>(rfh_a->GetSiteInstance());
  RenderFrameDeletedObserver rfh_a_deleted(rfh_a);

  // 2) Use BroadcastChannel (non-sticky) and a dummy sticky blocklisted
  // features.
  EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
  rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();

  // 3) Navigate cross-site, browser-initiated.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // The previous page won't get into the back-forward cache because of the
  // blocklisted features. Because we used sticky blocklisted features, we will
  // not do a proactive BrowsingInstance swap, however the RFH will still change
  // and get deleted.
  rfh_a_deleted.WaitUntilDeleted();
  EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
      web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Both sticky and non-sticky features are recorded.
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kDummy,
       blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
      {}, {}, {}, FROM_HERE);
}

// Tests which blocklisted features are tracked in the metrics when we used
// blocklisted features (sticky and non-sticky) and do a renderer-initiated
// cross-site navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTest,
    BlocklistedFeaturesTracking_CrossSite_RendererInitiated) {
  ASSERT_TRUE(CreateHttpsServer()->Start());
  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title2.html"));

  // 1) Navigate to a page.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_a = current_frame_host();
  scoped_refptr<SiteInstanceImpl> site_instance_a =
      static_cast<SiteInstanceImpl*>(rfh_a->GetSiteInstance());

  // 2) Use BroadcastChannel (non-sticky) and Dummy sticky blocklisted
  // features.
  EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
  rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();

  // 3) Navigate cross-site, renderer-inititated.
  ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
  // The previous page won't get into the back-forward cache because of the
  // blocklisted features. Because we used sticky blocklisted features, we will
  // not do a proactive BrowsingInstance swap.
  EXPECT_TRUE(site_instance_a->IsRelatedSiteInstance(
      web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  if (AreStrictSiteInstancesEnabled()) {
    // Both sticky and non-sticky features are recorded.
    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);
  }
}

// Tests which blocklisted features are tracked in the metrics when we used
// blocklisted features (sticky and non-sticky) and do a same-site navigation.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       BlocklistedFeaturesTracking_SameSite) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  ASSERT_TRUE(CreateHttpsServer()->Start());
  GURL url_1(https_server()->GetURL("/title1.html"));
  GURL url_2(https_server()->GetURL("/title2.html"));

  // 1) Navigate to a page.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_1 = current_frame_host();
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(rfh_1->GetSiteInstance());
  rfh_1->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Use BroadcastChannel (non-sticky) and dummy sticky blocklisted features.
  EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
  rfh_1->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();

  // 3) Navigate same-site.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // Because we used sticky blocklisted features, we will not do a proactive
  // BrowsingInstance swap.
  EXPECT_TRUE(site_instance_1->IsRelatedSiteInstance(
      web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Both sticky and non-sticky reasons are recorded here.
  ExpectNotRestored(
      {
          NotRestoredReason::kBlocklistedFeatures,
          NotRestoredReason::kBrowsingInstanceNotSwapped,
      },
      {blink::scheduler::WebSchedulerTrackedFeature::kDummy,
       blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel},
      {ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {}, {},
      FROM_HERE);
  // NotRestoredReason tree should match the flattened list.
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures,
                             NotRestoredReason::kBrowsingInstanceNotSwapped),
          BlockListedFeatures(
              blink::scheduler::WebSchedulerTrackedFeature::kDummy,
              blink::scheduler::WebSchedulerTrackedFeature::
                  kBroadcastChannel)));
}

// Tests which blocklisted features are tracked in the metrics when we used a
// non-sticky blocklisted feature and do a browser-initiated cross-site
// navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTest,
    BlocklistedFeaturesTracking_CrossSite_BrowserInitiated_NonSticky) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  // 1) Navigate to an empty page.
  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_a = current_frame_host();
  // 2) Use BroadcastChannel (a non-sticky blocklisted feature).
  EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
  scoped_refptr<SiteInstanceImpl> site_instance_a =
      static_cast<SiteInstanceImpl*>(
          web_contents()->GetPrimaryMainFrame()->GetSiteInstance());

  // 3) Navigate cross-site, browser-initiated.
  // The previous page won't get into the back-forward cache because of the
  // blocklisted feature.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  // Because we only used non-sticky blocklisted features, we will still do a
  // proactive BrowsingInstance swap.
  EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
      web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Because the RenderFrameHostManager changed, the blocklisted features will
  // be tracked in RenderFrameHostManager::UnloadOldFrame.
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
      {}, FROM_HERE);
}

// Tests which blocklisted features are tracked in the metrics when we used a
// non-sticky blocklisted feature and do a renderer-initiated cross-site
// navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTest,
    BlocklistedFeaturesTracking_CrossSite_RendererInitiated_NonSticky) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  // 1) Navigate to an empty page.
  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_a = current_frame_host();
  // 2) Use BroadcastChannel (a non-sticky blocklisted feature).
  EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
  scoped_refptr<SiteInstanceImpl> site_instance_a =
      static_cast<SiteInstanceImpl*>(
          web_contents()->GetPrimaryMainFrame()->GetSiteInstance());

  // 3) Navigate cross-site, renderer-inititated.
  // The previous page won't get into the back-forward cache because of the
  // blocklisted feature.
  ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
  // Because we only used non-sticky blocklisted features, we will still do a
  // proactive BrowsingInstance swap.
  EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
      web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Because the RenderFrameHostManager changed, the blocklisted features will
  // be tracked in RenderFrameHostManager::UnloadOldFrame.
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
      {}, FROM_HERE);
}

// Tests which blocklisted features are tracked in the metrics when we used a
// non-sticky blocklisted feature and do a same-site navigation.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       BlocklistedFeaturesTracking_SameSite_NonSticky) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  // 1) Navigate to an empty page.
  GURL url_1(https_server()->GetURL("/title1.html"));
  GURL url_2(https_server()->GetURL("/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_1 = current_frame_host();
  // 2) Use BroadcastChannel (a non-sticky blocklisted feature).
  EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
  scoped_refptr<SiteInstanceImpl> site_instance_1 =
      static_cast<SiteInstanceImpl*>(
          web_contents()->GetPrimaryMainFrame()->GetSiteInstance());

  // 3) Navigate same-site.
  // The previous page won't get into the back-forward cache because of the
  // blocklisted feature.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  // Because we only used non-sticky blocklisted features, we will still do a
  // proactive BrowsingInstance swap.
  EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(
      web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Because the RenderFrameHostManager changed, the blocklisted features will
  // be tracked in RenderFrameHostManager::UnloadOldFrame.
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
      {}, FROM_HERE);
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_DoesNotCacheIfWebDatabase DISABLED_DoesNotCacheIfWebDatabase
#else
#define MAYBE_DoesNotCacheIfWebDatabase DoesNotCacheIfWebDatabase
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_DoesNotCacheIfWebDatabase) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page with WebDatabase usage.
  GURL url(embedded_test_server()->GetURL("/simple_database.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);

  // 2) Navigate away.
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
  // The page uses WebDatabase so it should be deleted.
  deleted.WaitUntilDeleted();

  // 3) Go back to the page with WebDatabase.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kWebDatabase}, {}, {}, {},
      FROM_HERE);
}

class BackForwardCacheBrowserTestWithFlagForIndexedDB
    : public BackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<int32_t> {
 public:
  // Different level of BFCache support for document with IndexedDB usage. This
  // will affect the feature's value in the browser tests.
  enum class IndexedDBBackForwardCacheEligibilityLevel {
    // Do not cache if IndexedDB is used.
    kNoCache = 0,
    // Allow BFCache if the document has open connections, but without ongoing
    // IndexedDB transactions.
    kCacheConnectionOnly = 1,
    // Allow BFCache if the document has open connections and/or ongoing
    // IndexedDB transactions.
    kCacheConnectionAndTransaction = 2,

    kMinLevel = kNoCache,
    kMaxLevel = kCacheConnectionAndTransaction,
  };

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
      EnableFeatureAndSetParams(
          blink::features::kAllowPageWithIDBConnectionInBFCache, "", "true");
    } else {
      DisableFeature(blink::features::kAllowPageWithIDBConnectionInBFCache);
    }
    if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
      EnableFeatureAndSetParams(
          blink::features::kAllowPageWithIDBTransactionInBFCache, "", "true");
    } else {
      DisableFeature(blink::features::kAllowPageWithIDBTransactionInBFCache);
    }

    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

  bool ShouldAllowPageWithIndexedDBConnectionInBFCache() {
    return GetParam() >=
           int(IndexedDBBackForwardCacheEligibilityLevel::kCacheConnectionOnly);
  }

  bool ShouldAllowPageWithIndexedDBTransactionInBFCache() {
    return GetParam() >= int(IndexedDBBackForwardCacheEligibilityLevel::
                                 kCacheConnectionAndTransaction);
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheBrowserTestWithFlagForIndexedDB,
    ::testing::Range(
        int(BackForwardCacheBrowserTestWithFlagForIndexedDB::
                IndexedDBBackForwardCacheEligibilityLevel::kMinLevel),
        int(BackForwardCacheBrowserTestWithFlagForIndexedDB::
                IndexedDBBackForwardCacheEligibilityLevel::kMaxLevel) +
            1));

IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
                       DoesNotCacheIfOpenIndexedDBConnection) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()"));

  // 2) Navigate away.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
    EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  }

  // 3) Go back to the page with IndexedDB.
  if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
    // If the flag indicates that the page with open IndexedDB connection is
    // eligible for back/forward cache, after navigating back, the page should
    // be restored.
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    ExpectRestored(FROM_HERE);
  } else {
    // If the flag indicates that the page with open IndexedDB connection is not
    // eligible for back/forward cache, the document should be deleted.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

    // It should not be restored from the back/forward cache, and the reason
    // should indicate that it was blocked due to `kIndexedDBConnection`.
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    ExpectNotRestored(
        {NotRestoredReason::kBlocklistedFeatures},
        {blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection},
        {}, {}, {}, FROM_HERE);
  }
}

IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
                       EvictCacheIfOnVersionChangeEventReceived) {
  ASSERT_TRUE(embedded_test_server()->Start());

  Shell* tab_receiving_version_change = shell();
  Shell* tab_sending_version_change = CreateBrowser();

  // 1) Navigate the tab receiving version change to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      tab_receiving_version_change,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  // Create two connection with the same version here so that it can cover the
  // cases when IndexedDB connection coordinator is not implemented correctly to
  // handle multiple connections' back/forward cache status.
  ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()"));
  ASSERT_TRUE(
      ExecJs(rfh_a.get(), "setupNewIndexedDBConnectionWithSameVersion()"));

  // 2) Navigate the tab receiving version change away, and navigate the tab
  // sending version change to the same page, and create a new IndexedDB
  // connection with a higher version. The new IndexedDB connection should be
  // created without being blocked by the page in back/forward cache.
  ASSERT_TRUE(
      NavigateToURL(tab_receiving_version_change,
                    embedded_test_server()->GetURL("a.com", "/title1.html")));
  if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
    ASSERT_TRUE(rfh_a->IsInBackForwardCache());
  }
  ASSERT_TRUE(NavigateToURL(
      tab_sending_version_change,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));

  // Running `setupNewIndexedDBConnectionWithHigherVersion()` will trigger the
  // `versionchange` event, which should cause the document receiving the
  // version change to be evicted from back/forward cache.
  content::DOMMessageQueue queue_sending_version_change(
      tab_sending_version_change->web_contents());
  std::string message_sending_version_change;
  ExecuteScriptAsync(tab_sending_version_change,
                     "setupNewIndexedDBConnectionWithHigherVersion()");
  ASSERT_TRUE(queue_sending_version_change.WaitForMessage(
      &message_sending_version_change));
  ASSERT_EQ("\"onsuccess\"", message_sending_version_change);

  // 3) Go back to the page a with IndexedDB.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
    // If this feature is enabled, the page should be put into the back/forward
    // cache after the navigation, but gets evicted due to `kIndexedDBEvent`.
    ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
                      {DisallowActivationReasonId::kIndexedDBEvent}, FROM_HERE);
  } else {
    // If this feature is disabled, the page should not be put into back/forward
    // cache at all, and the recorded blocklisted feature should be
    // `kIndexedDBConnection`.
    ExpectNotRestored(
        {NotRestoredReason::kBlocklistedFeatures},
        {blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection},
        {}, {}, {}, FROM_HERE);
  }
}

// Check if the non-sticky feature is properly registered before the
// `versionchange ` is sent. Since the `versionchange` event's handler won't
// close the IndexedDB connection, so when the navigation happens, the
// non-sticky feature will prevent the document from entering BFCache.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestWithFlagForIndexedDB,
    DoesNotCacheIfVersionChangeEventIsSentButIndexedDBConnectionIsNotClosed) {
  ASSERT_TRUE(embedded_test_server()->Start());

  Shell* tab_receiving_version_change = shell();
  Shell* tab_sending_version_change = CreateBrowser();

  // 1) Navigate the receiving tab to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      tab_receiving_version_change,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_receiving(current_frame_host());
  GURL destination_url =
      embedded_test_server()->GetURL("a.com", "/title1.html");

  ASSERT_TRUE(
      ExecJs(tab_receiving_version_change,
             JsReplace("setupIndexedDBVersionChangeHandlerToNavigateTo($1)",
                       destination_url.spec())));

  // 2) Navigate the sending tab to A and use IndexedDB with higher version.
  ASSERT_TRUE(NavigateToURL(
      tab_sending_version_change,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  content::DOMMessageQueue queue_receiving_version_change(
      tab_receiving_version_change->web_contents());
  std::string message_receiving_version_change;
  content::DOMMessageQueue queue_sending_version_change(
      tab_sending_version_change->web_contents());
  std::string message_sending_version_change;
  ExecuteScriptAsync(tab_sending_version_change,
                     "setupNewIndexedDBConnectionWithHigherVersion()");

  // 3) Wait until receiving tab receives the event and sending tab successfully
  // opens the connection. The receiving tab should navigate to another page in
  // the event handler. Before the navigation, the page should register a
  // corresponding feature handle and should not be eligible for BFCache.
  // The document will be disallowed to enter BFCache because of the
  // `versionchange` event without proper closure of connection if the feature
  // is on, otherwise, the reason should be open IndexedDB connection instead.
  blink::scheduler::WebSchedulerTrackedFeature tracked_feature;
  if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
    tracked_feature =
        blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent;
  } else {
    tracked_feature =
        blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection;
  }

  ASSERT_TRUE(queue_receiving_version_change.WaitForMessage(
      &message_receiving_version_change));
  ASSERT_EQ("\"onversionchange\"", message_receiving_version_change);

  TestNavigationManager navigation_manager(
      tab_receiving_version_change->web_contents(), destination_url);
  ASSERT_TRUE(navigation_manager.WaitForRequestStart());
  ASSERT_TRUE(rfh_receiving.get()->GetBackForwardCacheDisablingFeatures().Has(
      tracked_feature));
  navigation_manager.ResumeNavigation();
  ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());

  ASSERT_TRUE(queue_sending_version_change.WaitForMessage(
      &message_sending_version_change));
  ASSERT_EQ("\"onsuccess\"", message_sending_version_change);

  // 4) Go back to the page A in the receiving tab, the page should not be put
  // into back/forward cache at all, and the recorded blocklisted feature should
  // be the `tracked_feature`.
  ASSERT_TRUE(rfh_receiving.WaitUntilRenderFrameDeleted());
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {tracked_feature}, {}, {}, {}, FROM_HERE);
}

// Check if the non-sticky feature is properly registered before the
// `versionchange ` is sent and removed after the IndexedDB Connection is
// closed. Since the `versionchange` event's handler will close the IndexedDB
// connection before navigating away, so the document is eligible for BFCache as
// the non-sticky feature is removed.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestWithFlagForIndexedDB,
    CacheIfVersionChangeEventIsSentAndIndexedDBConnectionIsClosed) {
  ASSERT_TRUE(embedded_test_server()->Start());

  Shell* tab_receiving_version_change = shell();
  Shell* tab_sending_version_change = CreateBrowser();

  // 1) Navigate the receiving tab to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      tab_receiving_version_change,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_receiving(current_frame_host());
  GURL destination_url =
      embedded_test_server()->GetURL("a.com", "/title1.html");

  ASSERT_TRUE(ExecJs(tab_receiving_version_change,
                     JsReplace("setupIndexedDBVersionChangeHandlerToCloseConnec"
                               "tionAndNavigateTo($1)",
                               destination_url.spec())));

  // 2) Navigate the sending tab to A and use IndexedDB with higher version.
  ASSERT_TRUE(NavigateToURL(
      tab_sending_version_change,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  content::DOMMessageQueue queue_receiving_version_change(
      tab_receiving_version_change->web_contents());
  std::string message_receiving_version_change;
  content::DOMMessageQueue queue_sending_version_change(
      tab_sending_version_change->web_contents());
  std::string message_sending_version_change;
  ExecuteScriptAsync(tab_sending_version_change,
                     "setupNewIndexedDBConnectionWithHigherVersion()");

  // 3) Wait until receiving tab receives the event and sending tab successfully
  // opens the connection. The receiving tab should navigate to another page in
  // the event handler. Before the navigation, the page should register a
  // corresponding feature handle and should not be eligible for BFCache, but it
  // will be removed when the connection is closed, making the page eligible for
  // BFCache.
  ASSERT_TRUE(queue_receiving_version_change.WaitForMessage(
      &message_receiving_version_change));
  ASSERT_EQ("\"onversionchange\"", message_receiving_version_change);

  TestNavigationManager navigation_manager(
      tab_receiving_version_change->web_contents(), destination_url);
  ASSERT_TRUE(navigation_manager.WaitForRequestStart());
  // Since the connection is closed, the tracked feature should be reset so
  // the page is allowed to enter BFCache again.

  blink::scheduler::WebSchedulerTrackedFeature tracked_feature;
  if (ShouldAllowPageWithIndexedDBConnectionInBFCache()) {
    tracked_feature =
        blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBEvent;
  } else {
    tracked_feature =
        blink::scheduler::WebSchedulerTrackedFeature::kIndexedDBConnection;
  }

  ASSERT_FALSE(rfh_receiving.get()->GetBackForwardCacheDisablingFeatures().Has(
      tracked_feature));

  navigation_manager.ResumeNavigation();
  ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());

  ASSERT_TRUE(queue_sending_version_change.WaitForMessage(
      &message_sending_version_change));
  ASSERT_EQ("\"onsuccess\"", message_sending_version_change);

  // 4) Go back to the page A in the receiving tab, it should be restored from
  // BFCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CacheIfIndexedDBConnectionClosedInPagehide) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to A and use IndexedDB, and close the connection on pagehide.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  EXPECT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()"));
  // This registers a pagehide handler to close the IDB connection. This should
  // remove the bfcache blocking.
  EXPECT_TRUE(
      ExecJs(rfh_a.get(), "registerPagehideToCloseIndexedDBConnection()"));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back to the page with IndexedDB. The connection is closed so it
  // should be restored from bfcache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
                       DoNotCacheIfIndexedDBTransactionNotCommitted) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()"));
  // This registers a pagehide handler to start a new transaction. This will
  // block bfcache because there is an inflight transaction.
  ASSERT_TRUE(ExecJs(rfh_a.get(), "registerPagehideToStartTransaction()"));

  // 2) Navigate away.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // 3) Go back to the page with IndexedDB.
  if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    ExpectRestored(FROM_HERE);
  } else {
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                      {blink::scheduler::WebSchedulerTrackedFeature::
                           kOutstandingIndexedDBTransaction},
                      {}, {}, {}, FROM_HERE);
  }
}

IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
                       CacheIfIndexedDBConnectionTransactionCommit) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  ASSERT_TRUE(ExecJs(rfh_a.get(), "setupIndexedDBConnection()"));
  // This registers a pagehide handler to start and commit the IDB transactions.
  // Since the transactions are ended inside the handler, the page is no longer
  // blocked for inflight IDB transactions.
  ASSERT_TRUE(
      ExecJs(rfh_a.get(), "registerPagehideToStartAndCommitTransaction()"));

  // 2) Navigate away.
  ASSERT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  ASSERT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back to the page with IndexedDB.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
                       DoNotCacheIfIndexedDBTransactionIsAcquiringTheLock) {
  ASSERT_TRUE(embedded_test_server()->Start());

  Shell* tab_holding_locks = CreateBrowser();
  Shell* tab_waiting_for_locks = shell();

  // 1) Navigate the tab holding locks to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      tab_holding_locks,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  ASSERT_TRUE(ExecJs(tab_holding_locks, "setupIndexedDBConnection()"));
  // Make sure the page keeps holding the lock by running infinite tasks on the
  // object store.
  ExecuteScriptAsync(tab_holding_locks,
                     "runInfiniteIndexedDBTransactionLoop()");

  // 2) Navigate the tab waiting for locks to A as well and make it requesting
  // for the same lock on pagehide. Since the other tab is holding the lock,
  // this tab will be blocked and waiting for the lock to be released.
  ASSERT_TRUE(NavigateToURL(
      tab_waiting_for_locks,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "setupIndexedDBConnection()"));
  ASSERT_TRUE(
      ExecJs(tab_waiting_for_locks, "registerPagehideToStartTransaction()"));

  // 3) Navigate the tab waiting for locks away.
  ASSERT_TRUE(
      NavigateToURL(tab_waiting_for_locks,
                    embedded_test_server()->GetURL("b.com", "/title1.html")));

  // 4) Go back to the page with IndexedDB.
  if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
    // If the flag that enables a page with IndexedDB features to enter BFCache
    // is toggled on, the page should be evicted by disallowing activation.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents()));
    ExpectNotRestored(
        {NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
        {DisallowActivationReasonId::kIndexedDBTransactionIsAcquiringLocks},
        FROM_HERE);
  } else {
    // If the flag is not toggled on, the page will not be eligible for BFCache
    // because of the registered feature.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(tab_waiting_for_locks->web_contents()));
    ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                      {blink::scheduler::WebSchedulerTrackedFeature::
                           kOutstandingIndexedDBTransaction},
                      {}, {}, {}, FROM_HERE);
  }
}

IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestWithFlagForIndexedDB,
    DoNotCacheIfIndexedDBTransactionHoldingLocksAndBlockingOthers) {
  ASSERT_TRUE(embedded_test_server()->Start());

  Shell* tab_holding_locks = shell();
  Shell* tab_waiting_for_locks = CreateBrowser();

  // 1) Navigate the tab holding locks to A and use IndexedDB.
  ASSERT_TRUE(NavigateToURL(
      tab_holding_locks,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  ASSERT_TRUE(ExecJs(tab_holding_locks, "setupIndexedDBConnection()"));
  ASSERT_TRUE(ExecJs(tab_holding_locks,
                     "registerPagehideToCloseIndexedDBConnection()"));
  // Make sure the page keeps holding the lock by running infinite tasks on the
  // object store.
  ExecuteScriptAsync(tab_holding_locks,
                     "runInfiniteIndexedDBTransactionLoop()");

  // 2) Navigate the tab waiting for locks to A as well and make it request for
  // the same lock on pagehide. Since the other tab is holding the lock, this
  // tab will be blocked and waiting for the lock to be released.
  ASSERT_TRUE(NavigateToURL(
      tab_waiting_for_locks,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "setupIndexedDBConnection()"));
  ASSERT_TRUE(ExecJs(tab_waiting_for_locks, "startIndexedDBTransaction()"));

  // 3) Navigate the tab holding locks away.
  ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL(
                                                   "b.com", "/title1.html")));

  // 4) Go back to the page with IndexedDB from the tab holding the locks.
  if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
    // If the flag that enables a page with IndexedDB features to enter BFCache
    // is toggled on, the page should be evicted by disallowing activation.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents()));
    ExpectNotRestored(
        {NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
        {DisallowActivationReasonId::kIndexedDBTransactionIsBlockingOthers},
        FROM_HERE);
  } else {
    // If the flag is not toggled on, the page will not be eligible for BFCache
    // because of the registered feature.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents()));
    ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                      {blink::scheduler::WebSchedulerTrackedFeature::
                           kOutstandingIndexedDBTransaction},
                      {}, {}, {}, FROM_HERE);
  }
}

IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForIndexedDB,
                       EvictCacheIfPageBlocksNewTransaction) {
  ASSERT_TRUE(embedded_test_server()->Start());

  Shell* tab_holding_locks = shell();
  Shell* tab_acquiring_locks = CreateBrowser();

  // 1) Navigate the tab holding locks to A and use IndexedDB, it also register
  // a event on pagehide to run tasks that never ends to keep the IndexedDB
  // transaction locks.
  ASSERT_TRUE(NavigateToURL(
      tab_holding_locks,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  content::DOMMessageQueue queue_holding_locks(
      tab_holding_locks->web_contents());
  std::string message_holding_locks;
  ASSERT_TRUE(ExecJs(tab_holding_locks, "setupIndexedDBConnection()"));
  ASSERT_TRUE(
      ExecJs(tab_holding_locks, "registerPagehideToStartTransaction()"));

  // 2) Navigate the tab holding locks away.
  ASSERT_TRUE(NavigateToURL(tab_holding_locks, embedded_test_server()->GetURL(
                                                   "b.com", "/title1.html")));

  // 3) After confirming the transaction has been created from the tab holding
  // locks, navigate the tab acquiring locks to A that tries to acquire the same
  // lock.
  ASSERT_TRUE(queue_holding_locks.WaitForMessage(&message_holding_locks));
  ASSERT_EQ("\"transaction_created\"", message_holding_locks);
  ASSERT_TRUE(NavigateToURL(
      tab_acquiring_locks,
      embedded_test_server()->GetURL(
          "a.com", "/back_forward_cache/page_with_indexedDB.html")));

  content::DOMMessageQueue queue_acquiring_locks(
      tab_acquiring_locks->web_contents());
  std::string message_acquiring_locks;
  ASSERT_TRUE(ExecJs(tab_acquiring_locks, "setupIndexedDBConnection()"));
  ASSERT_TRUE(ExecJs(tab_acquiring_locks, "startIndexedDBTransaction()"));

  // 4) After confirming that the transaction from the tab acquiring locks is
  // completed (which should evict the other tab if it's in BFCache), navigate
  // the tab holding locks back to the page with IndexedDB.
  ASSERT_TRUE(queue_acquiring_locks.WaitForMessage(&message_acquiring_locks));
  ASSERT_EQ("\"transaction_completed\"", message_acquiring_locks);
  if (ShouldAllowPageWithIndexedDBTransactionInBFCache()) {
    // If the flag that enables a page with IndexedDB features to enter BFCache
    // is toggled on, the page should be evicted by disallowing activation.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents()));
    ExpectNotRestored(
        {NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {},
        {DisallowActivationReasonId::kIndexedDBTransactionIsBlockingOthers},
        FROM_HERE);
  } else {
    // If the flag is not toggled on, the page will not be eligible for BFCache
    // because of the registered feature.
    ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(HistoryGoBack(tab_holding_locks->web_contents()));
    ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                      {blink::scheduler::WebSchedulerTrackedFeature::
                           kOutstandingIndexedDBTransaction},
                      {}, {}, {}, FROM_HERE);
  }
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheIfBroadcastChannelStillOpen) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  // 1) Navigate to an empty page.
  GURL url_a(https_server()->GetURL(
      "a.test", "/back_forward_cache/page_with_broadcastchannel.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // 2) Use BroadcastChannel (a non-sticky blocklisted feature).
  RenderFrameHostImpl* rfh_a = current_frame_host();
  EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();"));
  EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(false);"));

  // 3) Navigate cross-site, browser-initiated.
  // The previous page won't get into the back-forward cache because of the
  // blocklisted feature.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Because the RenderFrameHostManager changed, the blocklisted features will
  // be tracked in RenderFrameHostManager::UnloadOldFrame.
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
      {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CacheIfBroadcastChannelIsClosedInPagehide) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  // 1) Navigate to an empty page.
  GURL url_a(https_server()->GetURL(
      "a.test", "/back_forward_cache/page_with_broadcastchannel.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImpl* rfh_a = current_frame_host();
  // 2) Use BroadcastChannel (a non-sticky blocklisted feature).
  EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();"));
  EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(true);"));

  // 3) Navigate cross-site, browser-initiated.
  // The previous page won't get into the back-forward cache because of the
  // blocklisted feature.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Disabled on Android, since we have problems starting up the websocket test
// server in the host
// TODO(crbug.com/1372291): Re-enable the test after solving the WS server.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebSocketCachedIfClosed DISABLED_WebSocketCachedIfClosed
#else
#define MAYBE_WebSocketCachedIfClosed WebSocketCachedIfClosed
#endif
// Pages with WebSocket should be cached if the connection is closed.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_WebSocketCachedIfClosed) {
  net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS,
                                   net::GetWebSocketTestDataDirectory());
  ASSERT_TRUE(ws_server.Start());

  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Open a WebSocket.
  const char script[] = R"(
      let socket;
      window.onpagehide = event => {
        socket.close();
      }
      new Promise(resolve => {
        socket = new WebSocket($1);
        socket.addEventListener('open', () => resolve());
      });)";
  ASSERT_TRUE(
      ExecJs(rfh_a.get(),
             JsReplace(script, ws_server.GetURL("echo-with-no-extension"))));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

class WebTransportBackForwardCacheBrowserTest
    : public BackForwardCacheBrowserTest {
 public:
  WebTransportBackForwardCacheBrowserTest() { server_.Start(); }
  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
    server_.SetUpCommandLine(command_line);
  }
  int port() const { return server_.server_address().port(); }

 private:
  WebTransportSimpleTestServer server_;
};

// Pages with active WebTransport should not be cached.
// TODO(yhirano): Update this test once
// https://github.com/w3c/webtransport/issues/326 is resolved.
IN_PROC_BROWSER_TEST_F(WebTransportBackForwardCacheBrowserTest,
                       ActiveWebTransportEvictsPage) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Establish a WebTransport session.
  const char script[] = R"(
      let transport = new WebTransport('https://localhost:$1/echo');
      )";
  ASSERT_TRUE(ExecJs(rfh_a.get(), JsReplace(script, port())));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Confirm A is evicted.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kWebTransport}, {}, {}, {},
      FROM_HERE);
}

// Pages with inactive WebTransport should be cached.
IN_PROC_BROWSER_TEST_F(WebTransportBackForwardCacheBrowserTest,
                       WebTransportCachedIfClosed) {
  CreateHttpsServer();
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Establish a WebTransport session.
  const char script[] = R"(
      let transport;
      window.onpagehide = event => {
        transport.close();
      };
      transport = new WebTransport('https://localhost:$1/echo');
      )";
  ASSERT_TRUE(ExecJs(rfh_a.get(), JsReplace(script, port())));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Disabled on Android, since we have problems starting up the websocket test
// server in the host
// TODO(crbug.com/1372291): Re-enable the test after solving the WS server.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebSocketNotCached DISABLED_WebSocketNotCached
#else
#define MAYBE_WebSocketNotCached WebSocketNotCached
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MAYBE_WebSocketNotCached) {
  net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS,
                                   net::GetWebSocketTestDataDirectory());
  ASSERT_TRUE(ws_server.Start());

  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // Open a WebSocket.
  const char script[] = R"(
      new Promise(resolve => {
        const socket = new WebSocket($1);
        socket.addEventListener('open', () => resolve());
      });)";
  ASSERT_TRUE(ExecJs(
      rfh_a, JsReplace(script, ws_server.GetURL("echo-with-no-extension"))));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Confirm A is evicted.
  delete_observer_rfh_a.WaitUntilDeleted();
}

namespace {

void RegisterServiceWorker(RenderFrameHostImpl* rfh) {
  EXPECT_EQ("success", EvalJs(rfh, R"(
    let controller_changed_promise = new Promise(resolve_controller_change => {
      navigator.serviceWorker.oncontrollerchange = resolve_controller_change;
    });

    new Promise(async resolve => {
      try {
        await navigator.serviceWorker.register(
          "./service-worker.js", {scope: "./"})
      } catch (e) {
        resolve("error: registration has failed");
      }

      await controller_changed_promise;

      if (navigator.serviceWorker.controller) {
        resolve("success");
      } else {
        resolve("error: not controlled by service worker");
      }
    });
  )"));
}

// Returns a unique script for each request, to test service worker update.
std::unique_ptr<net::test_server::HttpResponse> RequestHandlerForUpdateWorker(
    const net::test_server::HttpRequest& request) {
  if (request.relative_url != "/back_forward_cache/service-worker.js")
    return nullptr;
  static int counter = 0;
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_OK);
  const char script[] = R"(
    // counter = $1
    self.addEventListener('activate', function(event) {
      event.waitUntil(self.clients.claim());
    });
  )";
  http_response->set_content(JsReplace(script, counter++));
  http_response->set_content_type("text/javascript");
  http_response->AddCustomHeader("Cache-Control",
                                 "no-cache, no-store, must-revalidate");
  return http_response;
}

}  // namespace

class TestVibrationManager : public device::mojom::VibrationManager {
 public:
  TestVibrationManager() {
    OverrideVibrationManagerBinderForTesting(base::BindRepeating(
        &TestVibrationManager::BindVibrationManager, base::Unretained(this)));
  }

  ~TestVibrationManager() override {
    OverrideVibrationManagerBinderForTesting(base::NullCallback());
  }

  void BindVibrationManager(
      mojo::PendingReceiver<device::mojom::VibrationManager> receiver) {
    receiver_.Bind(std::move(receiver));
  }

  bool TriggerVibrate(RenderFrameHostImpl* rfh, int duration) {
    return EvalJs(rfh, JsReplace("navigator.vibrate($1)", duration))
        .ExtractBool();
  }

  bool TriggerShortVibrationSequence(RenderFrameHostImpl* rfh) {
    return EvalJs(rfh, "navigator.vibrate([10] * 1000)").ExtractBool();
  }

  bool WaitForCancel() {
    run_loop_.Run();
    return IsCancelled();
  }

  bool IsCancelled() { return cancelled_; }

 private:
  // device::mojom::VibrationManager:
  void Vibrate(int64_t milliseconds, VibrateCallback callback) override {
    cancelled_ = false;
    std::move(callback).Run();
  }

  void Cancel(CancelCallback callback) override {
    cancelled_ = true;
    std::move(callback).Run();
    run_loop_.Quit();
  }

  bool cancelled_ = false;
  base::RunLoop run_loop_;
  mojo::Receiver<device::mojom::VibrationManager> receiver_{this};
};

// Tests that vibration stops after the page enters bfcache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       VibrationStopsAfterEnteringCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  TestVibrationManager vibration_manager;

  // 1) Navigate to a page with a long vibration.
  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  ASSERT_TRUE(vibration_manager.TriggerVibrate(rfh_a, 10000));
  EXPECT_FALSE(vibration_manager.IsCancelled());

  // 2) Navigate away and expect the vibration to be canceled.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  EXPECT_NE(current_frame_host(), rfh_a);
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  EXPECT_TRUE(vibration_manager.WaitForCancel());

  // 3) Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Tests that the short vibration sequence on the page stops after it enters
// bfcache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       ShortVibrationSequenceStopsAfterEnteringCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  TestVibrationManager vibration_manager;

  // 1) Navigate to a page with a long vibration.
  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  ASSERT_TRUE(vibration_manager.TriggerShortVibrationSequence(rfh_a));
  EXPECT_FALSE(vibration_manager.IsCancelled());

  // 2) Navigate away and expect the vibration to be canceled.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  EXPECT_NE(current_frame_host(), rfh_a);
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  EXPECT_TRUE(vibration_manager.WaitForCancel());

  // 3) Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CachedPagesWithServiceWorkers) {
  CreateHttpsServer();
  SetupCrossSiteRedirector(https_server());
  ASSERT_TRUE(https_server()->Start());

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(
      shell(),
      https_server()->GetURL("a.test", "/back_forward_cache/empty.html")));

  // Register a service worker.
  RegisterServiceWorker(current_frame_host());

  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);

  // 2) Navigate away.
  EXPECT_TRUE(
      NavigateToURL(shell(), https_server()->GetURL("b.test", "/title1.html")));

  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back to A. The navigation should be served from the cache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_FALSE(deleted.deleted());
  EXPECT_EQ(rfh_a, current_frame_host());
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       EvictIfCacheBlocksServiceWorkerVersionActivation) {
  CreateHttpsServer();
  https_server()->RegisterRequestHandler(
      base::BindRepeating(&RequestHandlerForUpdateWorker));
  SetupCrossSiteRedirector(https_server());
  ASSERT_TRUE(https_server()->Start());
  Shell* tab_x = shell();
  Shell* tab_y = CreateBrowser();
  // 1) Navigate to A in tab X.
  EXPECT_TRUE(NavigateToURL(
      tab_x,
      https_server()->GetURL("a.test", "/back_forward_cache/empty.html")));
  // 2) Register a service worker.
  RegisterServiceWorker(current_frame_host());

  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);
  // 3) Navigate away to B in tab X.
  EXPECT_TRUE(
      NavigateToURL(tab_x, https_server()->GetURL("b.test", "/title1.html")));
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  // 4) Navigate to A in tab Y.
  EXPECT_TRUE(NavigateToURL(
      tab_y,
      https_server()->GetURL("a.test", "/back_forward_cache/empty.html")));
  // 5) Close tab Y to activate a service worker version.
  // This should evict |rfh_a| from the cache.
  tab_y->Close();
  deleted.WaitUntilDeleted();
  // 6) Navigate to A in tab X.
  ASSERT_TRUE(HistoryGoBack(tab_x->web_contents()));
  ExpectNotRestored(
      {
          NotRestoredReason::kServiceWorkerVersionActivation,
      },
      {}, {}, {}, {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       EvictWithPostMessageToCachedClient) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.RegisterRequestHandler(
      base::BindRepeating(&RequestHandlerForUpdateWorker));
  https_server.AddDefaultHandlers(GetTestDataFilePath());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  SetupCrossSiteRedirector(&https_server);
  ASSERT_TRUE(https_server.Start());
  Shell* tab_to_execute_service_worker = shell();
  Shell* tab_to_be_bfcached = CreateBrowser();

  // Observe the new WebContents to trace the navigation ID.
  WebContentsObserver::Observe(tab_to_be_bfcached->web_contents());

  // 1) Navigate to A in |tab_to_execute_service_worker|.
  EXPECT_TRUE(NavigateToURL(
      tab_to_execute_service_worker,
      https_server.GetURL(
          "a.test", "/back_forward_cache/service_worker_post_message.html")));

  // 2) Register a service worker.
  EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker,
                           "register('service_worker_post_message.js')"));

  // 3) Navigate to A in |tab_to_be_bfcached|.
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      https_server.GetURL(
          "a.test", "/back_forward_cache/service_worker_post_message.html")));
  const std::string script_to_store =
      "executeCommandOnServiceWorker('StoreClients')";
  EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, script_to_store));
  RenderFrameHostImplWrapper rfh(
      tab_to_be_bfcached->web_contents()->GetPrimaryMainFrame());

  // 4) Navigate away to B in |tab_to_be_bfcached|.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
                            https_server.GetURL("b.test", "/title1.html")));
  EXPECT_FALSE(rfh.IsDestroyed());
  EXPECT_TRUE(rfh->IsInBackForwardCache());

  // 5) Trigger client.postMessage via |tab_to_execute_service_worker|. Cache in
  // |tab_to_be_bfcached| will be evicted.
  const std::string script_to_post_message =
      "executeCommandOnServiceWorker('PostMessageToStoredClients')";
  EXPECT_EQ("DONE",
            EvalJs(tab_to_execute_service_worker, script_to_post_message));
  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());

  // 6) Go back to A in |tab_to_be_bfcached|.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kServiceWorkerPostMessage}, {}, {}, {},
                    {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EvictOnServiceWorkerClaim) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.RegisterRequestHandler(
      base::BindRepeating(&RequestHandlerForUpdateWorker));
  https_server.AddDefaultHandlers(GetTestDataFilePath());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  SetupCrossSiteRedirector(&https_server);
  ASSERT_TRUE(https_server.Start());

  Shell* tab_to_be_bfcached = shell();
  Shell* tab_to_execute_service_worker = CreateBrowser();

  // 1) Navigate to A in |tab_to_be_bfcached|.
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      https_server.GetURL(
          "a.test", "/back_forward_cache/service_worker_registration.html")));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);

  // 2) Navigate away to B in |tab_to_be_bfcached|.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
                            https_server.GetURL("b.test", "/title1.html")));
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to A in |tab_to_execute_service_worker|.
  EXPECT_TRUE(NavigateToURL(
      tab_to_execute_service_worker,
      https_server.GetURL(
          "a.test", "/back_forward_cache/service_worker_registration.html")));

  // 4) Register a service worker for |tab_to_execute_service_worker|.
  EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker,
                           "register('service_worker_registration.js')"));

  // 5) The service worker calls clients.claim(). |rfh_a| would normally be
  //    claimed but because it's in bfcache, it is evicted from the cache.
  EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, "claim()"));
  deleted.WaitUntilDeleted();

  // 6) Navigate to A in |tab_to_be_bfcached|.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kServiceWorkerClaim}, {}, {}, {}, {},
                    FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       EvictOnServiceWorkerUnregistration) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.RegisterRequestHandler(
      base::BindRepeating(&RequestHandlerForUpdateWorker));
  https_server.AddDefaultHandlers(GetTestDataFilePath());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  SetupCrossSiteRedirector(&https_server);
  ASSERT_TRUE(https_server.Start());

  Shell* tab_to_be_bfcached = shell();
  Shell* tab_to_unregister_service_worker = CreateBrowser();

  // 1) Navigate to A in |tab_to_be_bfcached|. This tab will be controlled by a
  // service worker.
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      https_server.GetURL("a.test",
                          "/back_forward_cache/"
                          "service_worker_registration.html?to_be_bfcached")));

  // 2) Register a service worker for |tab_to_be_bfcached|, but with a narrow
  // scope with URL param. This is to prevent |tab_to_unregister_service_worker|
  // from being controlled by the service worker.
  EXPECT_EQ("DONE",
            EvalJs(tab_to_be_bfcached,
                   "register('service_worker_registration.js', "
                   "'service_worker_registration.html?to_be_bfcached')"));
  EXPECT_EQ("DONE", EvalJs(tab_to_be_bfcached, "claim()"));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);

  // 3) Navigate to A in |tab_to_unregister_service_worker|. This tab is not
  // controlled by the service worker.
  EXPECT_TRUE(NavigateToURL(
      tab_to_unregister_service_worker,
      https_server.GetURL(
          "a.test", "/back_forward_cache/service_worker_registration.html")));

  // 5) Navigate from A to B in |tab_to_be_bfcached|. Now |tab_to_be_bfcached|
  // should be in bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
                            https_server.GetURL("b.test", "/title1.html")));
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 6) The service worker gets unregistered. Now |tab_to_be_bfcached| should be
  // notified of the unregistration and evicted from bfcache.
  EXPECT_EQ(
      "DONE",
      EvalJs(tab_to_unregister_service_worker,
             "unregister('service_worker_registration.html?to_be_bfcached')"));
  deleted.WaitUntilDeleted();
  // 7) Navigate back to A in |tab_to_be_bfcached|.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kServiceWorkerUnregistration}, {}, {},
                    {}, {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BeaconAndBfCache) {
  constexpr char kKeepalivePath[] = "/keepalive";

  net::test_server::ControllableHttpResponse keepalive(embedded_test_server(),
                                                       kKeepalivePath);
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_ping(embedded_test_server()->GetURL("a.com", kKeepalivePath));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a.get());

  EXPECT_TRUE(
      ExecJs(shell(), JsReplace(R"(navigator.sendBeacon($1, "");)", url_ping)));

  // 2) Navigate to B.
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // Ensure that the keepalive request is sent.
  keepalive.WaitForRequest();
  // Don't actually send the response.

  // Page A should be in the cache.
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
}

class GeolocationBackForwardCacheBrowserTest
    : public BackForwardCacheBrowserTest {
 protected:
  GeolocationBackForwardCacheBrowserTest() : geo_override_(0.0, 0.0) {}

  device::ScopedGeolocationOverrider geo_override_;
};

// Test that a page which has queried geolocation in the past, but have no
// active geolocation query, can be bfcached.
IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest,
                       CacheAfterGeolocationRequest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();

  // Query current position, and wait for the query to complete.
  EXPECT_EQ("received", EvalJs(rfh_a, R"(
      new Promise(resolve => {
        navigator.geolocation.getCurrentPosition(() => resolve('received'));
      });
  )"));

  RenderFrameDeletedObserver deleted(rfh_a);

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // The page has no inflight geolocation request when we navigated away,
  // so it should have been cached.
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
}

// Test that a page which has an in-flight geolocation query can be bfcached,
// and verify that the page does not observe any geolocation while the page
// was inside bfcache.
IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest,
                       CancelGeolocationRequestInFlight) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    // If set, will be called by handleEvent.
    window.pending_resolve = null;

    window.longitude_log = [];
    window.err_log = [];

    // Returns a promise that will resolve when the `longitude` is recorded in
    // the `longitude_log`. The promise will resolve with the index.
    function waitForLongitudeRecorded(longitude) {
      let index = window.longitude_log.indexOf(longitude);
      if (index >= 0) {
        return Promise.resolve(index);
      }
      return new Promise(resolve => {
        window.pending_resolve = resolve;
      }).then(() => waitForLongitudeRecorded(longitude));
    }

    // Continuously query current geolocation, if the longitude is different
    // from the last recorded value, update the result in the list,
    // and resolve the pending promises with the longitude value.
    navigator.geolocation.watchPosition(
      pos => {
        let new_longitude = pos.coords.longitude;
        let log_length = window.longitude_log.length;
        if (log_length == 0 ||
            window.longitude_log[log_length - 1] != new_longitude) {
          window.longitude_log.push(pos.coords.longitude);
          if (window.pending_resolve != null) {
            window.pending_resolve();
            window.pending_resolve = null;
          }
        }
      },
      err => window.err_log.push(err)
    );
  )"));

  // Wait for the initial value to be updated in the callback.
  EXPECT_EQ(
      0, EvalJs(rfh_a, "window.waitForLongitudeRecorded(0.0);").ExtractInt());

  // Update the location and wait for the promise, this location should be
  // observed.
  geo_override_.UpdateLocation(10.0, 10.0);
  EXPECT_EQ(
      1, EvalJs(rfh_a, "window.waitForLongitudeRecorded(10.0);").ExtractInt())
      << "Geoposition before the page is put into BFCache should be visible.";

  // Pause resolving Geoposition queries to keep the request in-flight.
  // This location should not be observed.
  geo_override_.Pause();
  geo_override_.UpdateLocation(20.0, 20.0);
  EXPECT_EQ(1u, geo_override_.GetGeolocationInstanceCount());

  // 2) Navigate away.
  base::RunLoop loop_until_close;
  geo_override_.SetGeolocationCloseCallback(loop_until_close.QuitClosure());

  RenderFrameDeletedObserver deleted(rfh_a);
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  loop_until_close.Run();

  // The page has no in-flight geolocation request when we navigated away,
  // so it should have been cached.
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Resume resolving Geoposition queries.
  geo_override_.Resume();

  // We update the location while the page is BFCached, but this location should
  // not be observed.
  geo_override_.UpdateLocation(30.0, 30.0);

  // 3) Navigate back to A.

  // Pause resolving Geoposition queries to keep the request in-flight.
  // The location when navigated back can be observed
  geo_override_.Pause();
  geo_override_.UpdateLocation(40.0, 40.0);

  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());
  EXPECT_FALSE(rfh_a->IsInBackForwardCache());

  // Resume resolving Geoposition queries.
  geo_override_.Resume();

  // Wait for an update after the user navigates back to A.
  EXPECT_EQ(2,
            EvalJs(rfh_a, "window.waitForLongitudeRecorded(40.0)").ExtractInt())
      << "Geoposition when the page is restored from BFCache should be visible";

  EXPECT_EQ("0,10,40", EvalJs(rfh_a, "window.longitude_log.toString();"))
      << "Geoposition while the page is put into BFCache should be invisible, "
         "so the log array should only contain 0, 10 and 40 but not 20 and 30";

  EXPECT_EQ(0, EvalJs(rfh_a, "err_log.length;"))
      << "watchPosition API should have reported no errors";
}

class BluetoothForwardCacheBrowserTest : public BackForwardCacheBrowserTest {
 protected:
  BluetoothForwardCacheBrowserTest() = default;

  ~BluetoothForwardCacheBrowserTest() override = default;

  void SetUp() override {
    // Fake the BluetoothAdapter to say it's present.
    // Used in WebBluetooth test.
    adapter_ =
        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
#if BUILDFLAG(IS_CHROMEOS_ASH)
    // In CHROMEOS build, even when |adapter_| object is released at TearDown()
    // it causes the test to fail on exit with an error indicating |adapter_| is
    // leaked.
    testing::Mock::AllowLeak(adapter_.get());
#endif

    BackForwardCacheBrowserTest::SetUp();
  }

  void TearDown() override {
    testing::Mock::VerifyAndClearExpectations(adapter_.get());
    adapter_.reset();
    BackForwardCacheBrowserTest::TearDown();
  }

  scoped_refptr<device::MockBluetoothAdapter> adapter_;
};

IN_PROC_BROWSER_TEST_F(BluetoothForwardCacheBrowserTest, WebBluetooth) {
  // The test requires a mock Bluetooth adapter to perform a
  // WebBluetooth API call. To avoid conflicts with the default Bluetooth
  // adapter, e.g. Windows adapter, which is configured during Bluetooth
  // initialization, the mock adapter is configured in SetUp().

  // WebBluetooth requires HTTPS.
  ASSERT_TRUE(CreateHttpsServer()->Start());
  GURL url(https_server()->GetURL("a.test", "/back_forward_cache/empty.html"));

  ASSERT_TRUE(NavigateToURL(web_contents(), url));
  BackForwardCacheDisabledTester tester;

  EXPECT_EQ("device not found", EvalJs(current_frame_host(), R"(
    new Promise(resolve => {
      navigator.bluetooth.requestDevice({
        filters: [
          { services: [0x1802, 0x1803] },
        ]
      })
      .then(() => resolve("device found"))
      .catch(() => resolve("device not found"))
    });
  )"));
  auto reason = BackForwardCacheDisable::DisabledReason(
      BackForwardCacheDisable::DisabledReasonId::kWebBluetooth);
  EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
      current_frame_host()->GetProcess()->GetID(),
      current_frame_host()->GetRoutingID(), reason));

  ASSERT_TRUE(NavigateToURL(web_contents(),
                            https_server()->GetURL("b.test", "/title1.html")));
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {},
                    {}, {reason}, {}, FROM_HERE);
}

enum class SerialContext {
  kDocument,
  kWorker,
  kNestedWorker,
};

enum class SerialType {
  kSerial,
  kWebUsb,
};

class BackForwardCacheBrowserWebUsbTest
    : public BackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<
          std::tuple<SerialContext, SerialType>> {
 public:
  std::string GetJsToUseSerial(SerialContext context, SerialType serial_type) {
    switch (serial_type) {
      case SerialType::kSerial:
        switch (context) {
          case SerialContext::kDocument:
            return R"(
              new Promise(async resolve => {
                let ports = await navigator.serial.getPorts();
                resolve("Found " + ports.length + " ports");
              });
            )";
          case SerialContext::kWorker:
            return R"(
              new Promise(async resolve => {
                const worker = new Worker(
                    "/back_forward_cache/serial/worker.js");
                worker.onmessage = message => resolve(message.data);
                worker.postMessage("Run");
              });
            )";
          case SerialContext::kNestedWorker:
            return R"(
              new Promise(async resolve => {
                const worker = new Worker(
                  "/back_forward_cache/serial/nested-worker.js");
                worker.onmessage = message => resolve(message.data);
                worker.postMessage("Run");
              });
            )";
        }
      case SerialType::kWebUsb:
        switch (context) {
          case SerialContext::kDocument:
            return R"(
              new Promise(async resolve => {
                let devices = await navigator.usb.getDevices();
                resolve("Found " + devices.length + " devices");
              });
            )";
          case SerialContext::kWorker:
            return R"(
              new Promise(async resolve => {
                const worker = new Worker(
                    "/back_forward_cache/webusb/worker.js");
                worker.onmessage = message => resolve(message.data);
                worker.postMessage("Run");
              });
            )";
          case SerialContext::kNestedWorker:
            return R"(
              new Promise(async resolve => {
                const worker = new Worker(
                  "/back_forward_cache/webusb/nested-worker.js");
                worker.onmessage = message => resolve(message.data);
                worker.postMessage("Run");
              });
            )";
        }
    }
  }
};

// Check the BackForwardCache is disabled when the WebUSB feature is used.
// TODO(https://crbug.com/1339720): Consider testing in a subframe. This will
// require adjustments to Permissions Policy.
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserWebUsbTest, Serials) {
  // WebUSB requires HTTPS.
  ASSERT_TRUE(CreateHttpsServer()->Start());

  SerialContext context;
  SerialType serial_type;
  std::tie(context, serial_type) = GetParam();

  content::BackForwardCacheDisabledTester tester;
  GURL url(https_server()->GetURL(
      "a.test", "/cross_site_iframe_factory.html?a.test(a.test)"));

  ASSERT_TRUE(NavigateToURL(shell(), url));

  // Check that the frames we care about are cacheable.
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh(
      current_frame_host()->child_at(0)->current_frame_host());
  ASSERT_FALSE(main_rfh->IsBackForwardCacheDisabled());
  ASSERT_FALSE(sub_rfh->IsBackForwardCacheDisabled());

  // Execute script to use WebUSB.
  ASSERT_EQ(
      serial_type == SerialType::kSerial ? "Found 0 ports" : "Found 0 devices",
      content::EvalJs(main_rfh.get(), GetJsToUseSerial(context, serial_type)));

  // Verify that the correct frames are now uncacheable.
  EXPECT_TRUE(main_rfh->IsBackForwardCacheDisabled());
  EXPECT_FALSE(sub_rfh->IsBackForwardCacheDisabled());
  auto expected_reason =
      serial_type == SerialType::kSerial
          ? BackForwardCacheDisable::DisabledReasonId::kSerial
          : BackForwardCacheDisable::DisabledReasonId::kWebUSB;
  EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
      main_rfh->GetProcess()->GetID(), main_rfh->GetRoutingID(),
      BackForwardCacheDisable::DisabledReason(expected_reason)));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheBrowserWebUsbTest,
    testing::Combine(testing::Values(SerialContext::kDocument,
                                     SerialContext::kWorker,
                                     SerialContext::kNestedWorker),
                     testing::Values(SerialType::kWebUsb
#if !BUILDFLAG(IS_ANDROID)
                                     ,
                                     SerialType::kSerial
#endif  // !BUILDFLAG(IS_ANDROID)
                                     )));

// Check that an audio suspends when the page goes to the cache and can resume
// after restored.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, AudioSuspendAndResume) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  EXPECT_TRUE(ExecJs(rfh_a, R"(
    var audio = document.createElement('audio');
    document.body.appendChild(audio);

    audio.testObserverEvents = [];
    let event_list = [
      'canplaythrough',
      'pause',
      'play',
      'error',
    ];
    for (event_name of event_list) {
      let result = event_name;
      audio.addEventListener(event_name, event => {
        document.title = result;
        audio.testObserverEvents.push(result);
      });
    }

    audio.src = 'media/bear-opus.ogg';

    var timeOnFrozen = 0.0;
    audio.addEventListener('pause', () => {
      timeOnFrozen = audio.currentTime;
    });
  )"));

  // Load the media.
  {
    TitleWatcher title_watcher(shell()->web_contents(), u"canplaythrough");
    title_watcher.AlsoWaitForTitle(u"error");
    EXPECT_EQ(u"canplaythrough", title_watcher.WaitAndGetTitle());
  }

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(async resolve => {
      audio.play();
      while (audio.currentTime === 0)
        await new Promise(r => setTimeout(r, 1));
      resolve();
    });
  )"));

  // 2) Navigate to B.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());

  // Check that the media position is not changed when the page is in cache.
  double duration1 = EvalJs(rfh_a, "timeOnFrozen;").ExtractDouble();
  double duration2 = EvalJs(rfh_a, "audio.currentTime;").ExtractDouble();
  EXPECT_LE(0.0, duration2 - duration1);
  EXPECT_GT(0.01, duration2 - duration1);

  // Resume the media.
  EXPECT_TRUE(ExecJs(rfh_a, "audio.play();"));

  // Confirm that the media pauses automatically when going to the cache.
  // TODO(hajimehoshi): Confirm that this media automatically resumes if
  // autoplay attribute exists.
  EXPECT_EQ(ListValueOf("canplaythrough", "play", "pause", "play"),
            EvalJs(rfh_a, "audio.testObserverEvents"));
}

// Check that a video suspends when the page goes to the cache and can resume
// after restored.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VideoSuspendAndResume) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  EXPECT_TRUE(ExecJs(rfh_a, R"(
    var video = document.createElement('video');
    document.body.appendChild(video);

    video.testObserverEvents = [];
    let event_list = [
      'canplaythrough',
      'pause',
      'play',
      'error',
    ];
    for (event_name of event_list) {
      let result = event_name;
      video.addEventListener(event_name, event => {
        document.title = result;
        // Ignore 'canplaythrough' event as we can randomly get extra
        // 'canplaythrough' events after playing here.
        if (result != 'canplaythrough')
          video.testObserverEvents.push(result);
      });
    }

    video.src = 'media/bear.webm';

    // Android bots can be very slow and the video is only 1s long.
    // This gives the first part of the test time to run before reaching
    // the end of the video.
    video.playbackRate = 0.1;

    var timeOnPagehide;
    window.addEventListener('pagehide', () => {
      timeOnPagehide = video.currentTime;
    });
    var timeOnPageshow;
    window.addEventListener('pageshow', () => {
      timeOnPageshow = video.currentTime;
    });
  )"));

  // Load the media.
  {
    TitleWatcher title_watcher(shell()->web_contents(), u"canplaythrough");
    title_watcher.AlsoWaitForTitle(u"error");
    EXPECT_EQ(u"canplaythrough", title_watcher.WaitAndGetTitle());
  }

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(async resolve => {
      video.play();
      while (video.currentTime == 0)
        await new Promise(r => setTimeout(r, 1));
      resolve();
    });
  )"));

  // Navigate to B.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Sleep for 1s so that playing in BFCache can be detected.
  base::PlatformThread::Sleep(base::Seconds(1));

  // Navigate back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());

  const double timeOnPagehide =
      EvalJs(rfh_a, "timeOnPagehide;").ExtractDouble();
  const double timeOnPageshow = EvalJs(rfh_a, "timeOnPageshow").ExtractDouble();

  // Make sure the video did not reach the end. If it did, our test is not
  // reliable.
  ASSERT_GT(1.0, timeOnPageshow);

  // Check that the duration of video played between pagehide and pageshow is
  // small. We waited for 1s so if it didn't stop in BFCache, it should be much
  // longer than this.
  const double playedDuration = timeOnPageshow - timeOnPagehide;
  EXPECT_LE(0.0, playedDuration);
  EXPECT_GT(0.02, playedDuration);

  // Resume the media.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
      // Ensure that the video does not auto-pause when it completes as that
      // would add an unexpected pause event.
      video.loop = true;
      video.play();
    )"));

  // Confirm that the media pauses automatically when going to the cache.
  // TODO(hajimehoshi): Confirm that this media automatically resumes if
  // autoplay attribute exists.
  EXPECT_EQ(ListValueOf("play", "pause", "play"),
            EvalJs(rfh_a, "video.testObserverEvents"));
}

class SensorBackForwardCacheBrowserTest : public BackForwardCacheBrowserTest {
 protected:
  SensorBackForwardCacheBrowserTest() {
    SensorProviderProxyImpl::OverrideSensorProviderBinderForTesting(
        base::BindRepeating(
            &SensorBackForwardCacheBrowserTest::BindSensorProvider,
            base::Unretained(this)));
  }

  ~SensorBackForwardCacheBrowserTest() override {
    SensorProviderProxyImpl::OverrideSensorProviderBinderForTesting(
        base::NullCallback());
  }

  void SetUpOnMainThread() override {
    provider_ = std::make_unique<device::FakeSensorProvider>();
    provider_->SetAccelerometerData(1.0, 2.0, 3.0);

    BackForwardCacheBrowserTest::SetUpOnMainThread();
  }

  std::unique_ptr<device::FakeSensorProvider> provider_;

 private:
  void BindSensorProvider(
      mojo::PendingReceiver<device::mojom::SensorProvider> receiver) {
    provider_->Bind(std::move(receiver));
  }
};

IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest,
                       AccelerometerNotCached) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(resolve => {
      const sensor = new Accelerometer();
      sensor.addEventListener('reading', () => { resolve(); });
      sensor.start();
    })
  )"));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // - Page A should not be in the cache.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {blink::scheduler::WebSchedulerTrackedFeature::
                         kRequestedBackForwardCacheBlockedSensors},
                    {}, {}, {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, OrientationCached) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    window.addEventListener("deviceorientation", () => {});
  )"));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_THAT(rfh_a, InBackForwardCache());
}

// Tests that the orientation sensor's events are not delivered to a page in the
// back-forward cache.
//
// This sets some JS functions in the pages to enable the sensors, capture and
// validate the events. The a-page should only receive events with alpha=0, the
// b-page is allowed to receive any alpha value. The test captures 3 events in
// the a-page, then navigates to the b-page and changes the reading to have
// alpha=1. While on the b-page it captures 3 more events. If the a-page is
// still receiving events it should receive one or more of these. Finally it
// resets the reasing back to have alpha=0 and navigates back to the a-page and
// captures 3 more events and verifies that all events on the a-page have
// alpha=1.
IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest,
                       SensorPausedWhileCached) {
  ASSERT_TRUE(CreateHttpsServer()->Start());
  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  provider_->SetRelativeOrientationSensorData(0, 0, 0);

  // JS to cause a page to listen to, capture and validate orientation events.
  const std::string sensor_js = R"(
    // Collects events that have happened so far.
    var events = [];
    // If set, will be called by handleEvent.
    var pendingResolve = null;

    // Handles one event, pushing it to |events| and calling |pendingResolve| if
    // set.
    function handleEvent(event) {
      events.push(event);
      if (pendingResolve !== null) {
        pendingResolve('event');
        pendingResolve = null;
      }
    }

    // Returns a promise that will resolve when the events array has at least
    // |eventCountMin| elements. Returns the number of elements.
    function waitForEventsPromise(eventCountMin) {
      if (events.length >= eventCountMin) {
        return Promise.resolve(events.length);
      }
      return new Promise(resolve => {
        pendingResolve = resolve;
      }).then(() => waitForEventsPromise(eventCountMin));
    }

    // Pretty print an orientation event.
    function eventToString(event) {
      return `${event.alpha} ${event.beta} ${event.gamma}`;
    }

    // Ensure that that |expectedAlpha| matches the alpha of all events.
    function validateEvents(expectedAlpha = null) {
      if (expectedAlpha !== null) {
        let count = 0;
        for (event of events) {
          count++;
          if (Math.abs(event.alpha - expectedAlpha) > 0.01) {
            return `fail - ${count}/${events.length}: ` +
                `${expectedAlpha} != ${event.alpha} (${eventToString(event)})`;
          }
        }
      }
      return 'pass';
    }

    window.addEventListener('deviceorientation', handleEvent);
  )";

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  ASSERT_TRUE(ExecJs(rfh_a, sensor_js));

  // Collect 3 orientation events.
  ASSERT_EQ(1, EvalJs(rfh_a, "waitForEventsPromise(1)"));
  provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2);
  ASSERT_EQ(2, EvalJs(rfh_a, "waitForEventsPromise(2)"));
  provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4);
  ASSERT_EQ(3, EvalJs(rfh_a, "waitForEventsPromise(3)"));
  // We should have 3 events with alpha=0.
  ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)"));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
  RenderFrameHostImpl* rfh_b = current_frame_host();

  ASSERT_FALSE(delete_observer_rfh_a.deleted());
  ASSERT_THAT(rfh_a, InBackForwardCache());
  ASSERT_NE(rfh_a, rfh_b);

  ASSERT_TRUE(ExecJs(rfh_b, sensor_js));

  // Collect 3 orientation events.
  provider_->SetRelativeOrientationSensorData(1, 0, 0);
  ASSERT_EQ(1, EvalJs(rfh_b, "waitForEventsPromise(1)"));
  provider_->UpdateRelativeOrientationSensorData(1, 0, 0.2);
  ASSERT_EQ(2, EvalJs(rfh_b, "waitForEventsPromise(2)"));
  provider_->UpdateRelativeOrientationSensorData(1, 0, 0.4);
  ASSERT_EQ(3, EvalJs(rfh_b, "waitForEventsPromise(3)"));
  // We should have 3 events with alpha=1.
  ASSERT_EQ("pass", EvalJs(rfh_b, "validateEvents()"));

  // 3) Go back to A.
  provider_->UpdateRelativeOrientationSensorData(0, 0, 0);
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ASSERT_EQ(rfh_a, current_frame_host());

  // Collect 3 orientation events.
  provider_->UpdateRelativeOrientationSensorData(0, 0, 0);
  // There are 2 processes so, it's possible that more events crept in. So we
  // capture how many there are at this point and uses to wait for at least 3
  // more.
  int count = EvalJs(rfh_a, "waitForEventsPromise(4)").ExtractInt();
  provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2);
  count++;
  ASSERT_EQ(count, EvalJs(rfh_a, base::StringPrintf("waitForEventsPromise(%d)",
                                                    count)));
  provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4);
  count++;
  ASSERT_EQ(count, EvalJs(rfh_a, base::StringPrintf("waitForEventsPromise(%d)",
                                                    count)));

  // We should have the earlier 3 plus another 3 events with alpha=0.
  ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)"));
}

// This tests that even if a page initializes WebRTC, tha page can be cached as
// long as it doesn't make a connection.
// On the Android test environments, the test might fail due to IP restrictions.
// See the discussion at http://crrev.com/c/2564926.
#if !BUILDFLAG(IS_ANDROID)

// TODO(https://crbug.com/1213145): The test is consistently failing on some Mac
// bots.
#if BUILDFLAG(IS_MAC)
#define MAYBE_TrivialRTCPeerConnectionCached \
  DISABLED_TrivialRTCPeerConnectionCached
#else
#define MAYBE_TrivialRTCPeerConnectionCached TrivialRTCPeerConnectionCached
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_TrivialRTCPeerConnectionCached) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  GURL url_a(https_server()->GetURL("/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();

  // Create an RTCPeerConnection without starting a connection.
  EXPECT_TRUE(ExecJs(rfh_a, "const pc1 = new RTCPeerConnection()"));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);

  // RTCPeerConnection object, that is created before being put into the cache,
  // is still available.
  EXPECT_EQ("success", EvalJs(rfh_a, R"(
    new Promise(async resolve => {
      const pc1 = new RTCPeerConnection();
      const pc2 = new RTCPeerConnection();
      pc1.onicecandidate = e => {
        if (e.candidate)
          pc2.addIceCandidate(e.candidate);
      }
      pc2.onicecandidate = e => {
        if (e.candidate)
          pc1.addIceCandidate(e.candidate);
      }
      pc1.addTransceiver("audio");
      const connectionEstablished = new Promise((resolve, reject) => {
        pc1.oniceconnectionstatechange = () => {
          const state = pc1.iceConnectionState;
          switch (state) {
          case "connected":
          case "completed":
            resolve();
            break;
          case "failed":
          case "disconnected":
          case "closed":
            reject(state);
            break;
          }
        }
      });
      await pc1.setLocalDescription();
      await pc2.setRemoteDescription(pc1.localDescription);
      await pc2.setLocalDescription();
      await pc1.setRemoteDescription(pc2.localDescription);
      try {
        await connectionEstablished;
      } catch (e) {
        resolve("fail " + e);
        return;
      }
      resolve("success");
    });
  )"));
}
#endif  // !BUILDFLAG(IS_ANDROID)

// This tests that a page using WebRTC and creating actual connections cannot be
// cached.
// On the Android test environments, the test might fail due to IP restrictions.
// See the discussion at http://crrev.com/c/2564926.
#if !BUILDFLAG(IS_ANDROID)

// TODO(https://crbug.com/1213145): The test is consistently failing on some Mac
// bots.
#if BUILDFLAG(IS_MAC)
#define MAYBE_NonTrivialRTCPeerConnectionNotCached \
  DISABLED_NonTrivialRTCPeerConnectionNotCached
#else
#define MAYBE_NonTrivialRTCPeerConnectionNotCached \
  NonTrivialRTCPeerConnectionNotCached
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_NonTrivialRTCPeerConnectionNotCached) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  GURL url_a(https_server()->GetURL("/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // Create an RTCPeerConnection with starting a connection.
  EXPECT_EQ("success", EvalJs(rfh_a, R"(
    new Promise(async resolve => {
      const pc1 = new RTCPeerConnection();
      const pc2 = new RTCPeerConnection();
      pc1.onicecandidate = e => {
        if (e.candidate)
          pc2.addIceCandidate(e.candidate);
      }
      pc2.onicecandidate = e => {
        if (e.candidate)
          pc1.addIceCandidate(e.candidate);
      }
      pc1.addTransceiver("audio");
      const connectionEstablished = new Promise(resolve => {
        pc1.oniceconnectionstatechange = () => {
          const state = pc1.iceConnectionState;
          switch (state) {
          case "connected":
          case "completed":
            resolve();
            break;
          case "failed":
          case "disconnected":
          case "closed":
            reject(state);
            break;
          }
        }
      });
      await pc1.setLocalDescription();
      await pc2.setRemoteDescription(pc1.localDescription);
      await pc2.setLocalDescription();
      await pc1.setRemoteDescription(pc2.localDescription);
      await connectionEstablished;
      try {
        await connectionEstablished;
      } catch (e) {
        resolve("fail " + e);
        return;
      }
      resolve("success");
    });
  )"));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // - Page A should not be in the cache.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {blink::scheduler::WebSchedulerTrackedFeature::kWebRTC}, {},
                    {}, {}, FROM_HERE);
}
#endif  // !BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebLocksNotCached) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // Wait for the page to acquire a lock and ensure that it continues to do so.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
    const never_resolved = new Promise(resolve => {});
    new Promise(continue_test => {
      navigator.locks.request('test', async () => {
        continue_test();
        await never_resolved;
      });
    })
  )"));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // - Page A should not be in the cache.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {blink::scheduler::WebSchedulerTrackedFeature::kWebLocks},
                    {}, {}, {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebMidiNotCached) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // Request access to MIDI. This should prevent the page from entering the
  // BackForwardCache.
  EXPECT_TRUE(ExecJs(rfh_a, "navigator.requestMIDIAccess()",
                     EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // - Page A should not be in the cache.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kRequestedMIDIPermission},
      {}, {}, {}, FROM_HERE);
}

// https://crbug.com/1410441
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DISABLED_PresentationConnectionClosed) {
  ASSERT_TRUE(CreateHttpsServer()->Start());
  GURL url_a(https_server()->GetURL(
      "a.test", "/back_forward_cache/presentation_controller.html"));

  // Navigate to A (presentation controller page).
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  auto* rfh_a = current_frame_host();
  // Start a presentation connection in A.
  MockPresentationServiceDelegate mock_presentation_service_delegate;
  auto& presentation_service = rfh_a->GetPresentationServiceForTesting();
  presentation_service.SetControllerDelegateForTesting(
      &mock_presentation_service_delegate);
  EXPECT_CALL(mock_presentation_service_delegate, StartPresentation(_, _, _));
  EXPECT_TRUE(ExecJs(rfh_a, "presentationRequest.start().then(setConnection)",
                     EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
  // Ensure that the above script runs before continuing.
  EXPECT_TRUE(ExecJs(rfh_a, "var foo = 42;"));

  // Send a mock connection to the renderer.
  MockPresentationConnection mock_controller_connection;
  mojo::Receiver<PresentationConnection> controller_connection_receiver(
      &mock_controller_connection);
  mojo::Remote<PresentationConnection> receiver_connection;
  const std::string presentation_connection_id = "foo";
  presentation_service.OnStartPresentationSucceeded(
      presentation_service.start_presentation_request_id_,
      PresentationConnectionResult::New(
          blink::mojom::PresentationInfo::New(GURL("fake-url"),
                                              presentation_connection_id),
          controller_connection_receiver.BindNewPipeAndPassRemote(),
          receiver_connection.BindNewPipeAndPassReceiver()));

  // Navigate to B, make sure that the connection started in A is closed.
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));
  EXPECT_CALL(
      mock_controller_connection,
      DidClose(blink::mojom::PresentationConnectionCloseReason::WENT_AWAY));
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Navigate back to A. Ensure that connection state has been updated
  // accordingly.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_FALSE(rfh_a->IsInBackForwardCache());
  EXPECT_EQ(presentation_connection_id, EvalJs(rfh_a, "connection.id"));
  EXPECT_EQ("closed", EvalJs(rfh_a, "connection.state"));
  EXPECT_TRUE(EvalJs(rfh_a, "connectionClosed").ExtractBool());

  // Try to start another connection, should successfully reach the browser side
  // PresentationServiceDelegate.
  EXPECT_CALL(mock_presentation_service_delegate,
              ReconnectPresentation(_, presentation_connection_id, _, _));
  EXPECT_TRUE(ExecJs(rfh_a, "presentationRequest.reconnect(connection.id);",
                     EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
  base::RunLoop().RunUntilIdle();

  // Reset |presentation_service|'s controller delegate so that it won't try to
  // call Reset() on it on destruction time.
  presentation_service.OnDelegateDestroyed();
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DoesNotCacheIfSpeechRecognitionIsStarted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to url_a.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // 2) Start SpeechRecognition.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(async resolve => {
    var r = new webkitSpeechRecognition();
    r.start();
    resolve();
    });
  )"));

  // 3) Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // 4) The page uses SpeechRecognition so it should be deleted.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 5) Go back to the page with SpeechRecognition.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kSpeechRecognizer}, {}, {},
      {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CanCacheIfSpeechRecognitionIsNotStarted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to url_a.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // 2) Initialise SpeechRecognition but don't start it yet.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
    new Promise(async resolve => {
    var r = new webkitSpeechRecognition();
    resolve();
    });
  )"));

  // 3) Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // 4) The page didn't start using SpeechRecognition so it shouldn't be deleted
  // and enter BackForwardCache.
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 5) Go back to the page with SpeechRecognition.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());

  ExpectRestored(FROM_HERE);
}

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());

// This test is not important for Chrome OS if TTS is called in content. For
// more details refer (content/browser/speech/tts_platform_impl.cc).
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_CacheIfUsingSpeechSynthesis DISABLED_CacheIfUsingSpeechSynthesis
#else
#define MAYBE_CacheIfUsingSpeechSynthesis CacheIfUsingSpeechSynthesis
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
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"));

  // 1) Navigate to a page and start using SpeechSynthesis.
  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();
    });
  )"));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  // 3) Go back to the page with SpeechSynthesis and ensure the page is
  // restored if the flag is on.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  if (IsSpeechSynthesisSupported()) {
    ExpectRestored(FROM_HERE);
    // TODO(crbug.com/1411151): Test that onend callback is fired upon restore.
  } 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"));

  // 1) Navigate to url_a and open file chooser.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted_rfh_a(rfh_a);
  content::BackForwardCacheDisabledTester tester;

  // 2) Bind FileChooser to RenderFrameHost.
  mojo::Remote<blink::mojom::FileChooser> chooser =
      FileChooserImpl::CreateBoundForTesting(rfh_a);

  auto quit_run_loop = [](base::OnceClosure callback,
                          blink::mojom::FileChooserResultPtr result) {
    std::move(callback).Run();
  };

  // 3) Run OpenFileChooser and wait till its run.
  base::RunLoop run_loop;
  chooser->OpenFileChooser(
      blink::mojom::FileChooserParams::New(),
      base::BindOnce(quit_run_loop, run_loop.QuitClosure()));
  run_loop.Run();

  // 4) rfh_a should be disabled for BackForwardCache after opening file
  // chooser.
  EXPECT_TRUE(rfh_a->IsBackForwardCacheDisabled());
  auto reason = BackForwardCacheDisable::DisabledReason(
      BackForwardCacheDisable::DisabledReasonId::kFileChooser);
  EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
      rfh_a->GetProcess()->GetID(), rfh_a->GetRoutingID(), reason));

  // 5) Navigate to B having the file chooser open.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // The page uses FileChooser so it should be deleted.
  deleted_rfh_a.WaitUntilDeleted();

  // 6) Go back to the page with FileChooser.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {},
                    {}, {reason}, {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheWithMediaSession) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page using MediaSession.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
    navigator.mediaSession.metadata = new MediaMetadata({
      artwork: [
        {src: "test_image.jpg", sizes: "1x1", type: "image/jpeg"},
        {src: "test_image.jpg", sizes: "10x10", type: "image/jpeg"}
      ]
    });
  )"));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a.get(), current_frame_host());
  ExpectRestored(FROM_HERE);
  // Check the media session state is reserved.
  EXPECT_EQ("10x10", EvalJs(rfh_a.get(), R"(
    navigator.mediaSession.metadata.artwork[1].sizes;
  )"));
}

class BackForwardCacheBrowserTestWithSupportedFeatures
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features",
                              "BroadcastChannel,KeyboardLock");
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }
};

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSupportedFeatures,
                       CacheWithSpecifiedFeatures) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to the page A with BroadcastChannel.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver deleted(rfh_a);
  EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back to the page A
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());
  ExpectRestored(FROM_HERE);

  // 4) Use KeyboardLock.
  AcquireKeyboardLock(rfh_a);

  // 5) Navigate away again.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_FALSE(deleted.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 6) Go back to the page A again.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());
  ExpectRestored(FROM_HERE);
}

class BackForwardCacheBrowserTestWithNoSupportedFeatures
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Specify empty supported features explicitly.
    EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features",
                              "");
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }
};

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithNoSupportedFeatures,
                       DontCache) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  GURL url_a(https_server()->GetURL("a.test", "/title1.html"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to the page A with BroadcastChannel.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a1 = current_frame_host();
  RenderFrameDeletedObserver deleted_a1(rfh_a1);
  EXPECT_TRUE(ExecJs(rfh_a1, "window.foo = new BroadcastChannel('foo');"));

  // 2) Navigate away.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  deleted_a1.WaitUntilDeleted();

  // 3) Go back to the page A
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
      {}, FROM_HERE);

  RenderFrameHostImpl* rfh_a2 = current_frame_host();
  RenderFrameDeletedObserver deleted_a2(rfh_a2);

  // 4) Use KeyboardLock.
  AcquireKeyboardLock(rfh_a2);

  // 5) Navigate away again.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  deleted_a2.WaitUntilDeleted();

  // 6) Go back to the page A again.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {}, {},
      FROM_HERE);
}

class BackForwardCacheBrowserTestWithMediaSession
    : public BackForwardCacheBrowserTest {
 protected:
  void PlayVideoNavigateAndGoBack() {
    MediaSession* media_session = MediaSession::Get(shell()->web_contents());
    ASSERT_TRUE(media_session);

    content::MediaStartStopObserver start_observer(
        shell()->web_contents(), MediaStartStopObserver::Type::kStart);
    EXPECT_TRUE(ExecJs(current_frame_host(),
                       "document.querySelector('#long-video').play();"));
    start_observer.Wait();

    content::MediaStartStopObserver stop_observer(
        shell()->web_contents(), MediaStartStopObserver::Type::kStop);
    media_session->Suspend(MediaSession::SuspendType::kSystem);
    stop_observer.Wait();

    // Navigate away.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("b.test", "/title1.html")));

    // Go back.
    ASSERT_TRUE(HistoryGoBack(web_contents()));
  }
};

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
                       CacheWhenMediaSessionPlaybackStateIsChanged) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.test", "/title1.html")));

  // 2) Update the playback state change.
  EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), R"(
    navigator.mediaSession.playbackState = 'playing';
  )"));

  // 3) Navigate away.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // The page is restored since a MediaSession service is not used.
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
                       CacheWhenMediaSessionServiceIsNotUsed) {
  // There are sometimes unexpected messages from a renderer to the browser,
  // which caused test flakiness.
  // TODO(crbug.com/1253200): Fix the test flakiness.
  DoNotFailForUnexpectedMessagesWhileCached();

  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to a page using MediaSession.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.test", "/media/session/media-session.html")));

  // Play the media once, but without setting any callbacks to the MediaSession.
  // In this case, a MediaSession service is not used.
  PlayVideoNavigateAndGoBack();

  // The page is restored since a MediaSession service is not used.
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
                       DontCacheWhenMediaSessionServiceIsUsed) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Navigate to a page using MediaSession.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.test", "/media/session/media-session.html")));
  RenderFrameHostWrapper rfh_a(current_frame_host());
  // Register a callback explicitly to use a MediaSession service.
  EXPECT_TRUE(ExecJs(rfh_a.get(), R"(
    navigator.mediaSession.setActionHandler('play', () => {});
  )"));

  PlayVideoNavigateAndGoBack();

  // The page is not restored since a MediaSession service is used.
  auto reason = BackForwardCacheDisable::DisabledReason(
      BackForwardCacheDisable::DisabledReasonId::kMediaSessionService);
  ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {},
                    {}, {reason}, {}, FROM_HERE);
}

}  // namespace content