// 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 <memory>

#include "base/test/test_future.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "build/chromecast_buildflags.h"
#include "components/unexportable_keys/features.h"
#include "content/browser/back_forward_cache_browsertest.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "net/base/features.h"
#include "net/device_bound_sessions/test_support.h"
#include "net/dns/mock_host_resolver.h"
#include "net/net_buildflags.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/install_default_websocket_handlers.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/mojom/clear_data_filter.mojom.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"

// This file contains back-/forward-cache tests for the
// `Cache-control: no-store` header. It was forked from
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/back_forward_cache_browsertest.cc;drc=b339487e39ad6ae93af30fa8fcb37dc61bd138ec
//
// When adding tests please also add WPTs. See
// third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/README.md

namespace content {

using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason;
using NotRestoredReasons =
    BackForwardCacheCanStoreDocumentResult::NotRestoredReasons;
using BlocklistedFeature = blink::scheduler::WebSchedulerTrackedFeature;

namespace {

const char kResponseWithNoCache[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "\r\n"
    "The server speaks HTTP!";

class BackForwardCacheBrowserTestDisableCCNS
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
    DisableFeature(features::kCacheControlNoStoreEnterBackForwardCache);
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }
};

}  // namespace

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestDisableCCNS,
                       MainFrameWithNoStoreNotCached) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/main_document");
  ASSERT_TRUE(embedded_test_server()->Start());

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

  // 1. Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();

  // 2. Navigate away and expect frame to be deleted.
  RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  delete_observer_rfh_a.WaitUntilDeleted();
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestDisableCCNS,
                       SubframeWithNoStoreCached) {
  // iframe will try to load title1.html.
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());

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

  // 3) Navigate back and expect everything to be restored.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_EQ(rfh_a, current_frame_host());
}

// When CCNS is present and WebRTC is used, both features should be recorded
// and the test should not hit CHECK.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestDisableCCNS,
                       CCNSAndWebRTCBothRecorded) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1. Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostWrapper rfh_a(current_frame_host());
  // Open a WebRTC connection.
  const char script[] = R"(
      new Promise(resolve => {
        const pc = new RTCPeerConnection();
        pc.addIceCandidate({ candidate: "test", sdpMLineIndex: 0 }).finally(()=>{
          resolve(42);
        });
      });)";
  ASSERT_EQ(42, EvalJs(rfh_a.get(), script));

  // 2. Navigate away and expect frame to be deleted.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // 3. Go back and make sure both reasons are recorded.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {BlocklistedFeature::kWebRTC,
                     BlocklistedFeature::kMainResourceHasCacheControlNoStore,
                     BlocklistedFeature::kWebRTCSticky},
                    {}, {}, {}, FROM_HERE);
}

enum class TestNavigationType {
  kNonPrerender,
  kPrerender,
};

class BackForwardCacheBrowserTestWithPrerendering
    : public BackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<TestNavigationType> {
 public:
  static std::string DescribeParams(
      const ::testing::TestParamInfo<ParamType>& info) {
    switch (info.param) {
      case TestNavigationType::kNonPrerender:
        return "NonPrerender";
      case TestNavigationType::kPrerender:
        return "Prerender";
    }
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
    // `prerender_helper_` has a ScopedFeatureList so we needed to delay its
    // creation until the BackForwardCacheBrowserTest finishes setting up the
    // ScopedFeatureList.
    prerender_helper_ =
        std::make_unique<test::PrerenderTestHelper>(base::BindRepeating(
            &BackForwardCacheBrowserTestWithPrerendering::GetWebContents,
            base::Unretained(this)));
    prerender_helper_->RegisterServerRequestMonitor(embedded_test_server());
  }

  WebContents* GetWebContents() { return web_contents(); }

  test::PrerenderTestHelper& prerender_helper() { return *prerender_helper_; }

 protected:
  void NavigateToPageWithResponseFromMainWebContents(
      GURL& url,
      net::test_server::ControllableHttpResponse& response,
      const char* response_bytes) {
    switch (GetParam()) {
      case TestNavigationType::kNonPrerender: {
        TestNavigationObserver observer(web_contents());
        shell()->LoadURL(url);
        response.WaitForRequest();
        response.Send(response_bytes);
        response.Done();
        observer.Wait();
        break;
      }
      case TestNavigationType::kPrerender: {
        prerender_helper().AddPrerenderAsync(url);
        response.WaitForRequest();
        response.Send(response_bytes);
        response.Done();
        TestActivationManager activation_manager(web_contents(), url);
        ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
                           JsReplace("location = $1", url)));
        activation_manager.WaitForNavigationFinished();
        EXPECT_TRUE(activation_manager.was_activated());
        break;
      }
    }
  }

 private:
  std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
};

class BackForwardCacheBrowserTestAllowCacheControlNoStore
    : public BackForwardCacheBrowserTestWithPrerendering {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
    EnableFeatureAndSetParams(
        features::kCacheControlNoStoreEnterBackForwardCache, "level",
        "store-and-evict");
    BackForwardCacheBrowserTestWithPrerendering::SetUpCommandLine(command_line);
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    ::testing::Values(TestNavigationType::kNonPrerender,
                      TestNavigationType::kPrerender),
    &BackForwardCacheBrowserTestAllowCacheControlNoStore::DescribeParams);

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// but does not get restored and gets evicted.
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestAllowCacheControlNoStore,
                       PagesWithCacheControlNoStoreEnterBfcacheAndEvicted) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCache);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter the bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  ExpectNotRestored({NotRestoredReason::kCacheControlNoStore}, {}, {}, {}, {},
                    FROM_HERE);
  // Make sure that the tree result also has the same reason.
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and if a cookie is modified while it is in bfcache via JavaScript, gets
// evicted with cookie modified marked.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreCookieModifiedThroughJavaScript) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCache);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Set a normal cookie from JavaScript.
  EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=bar'"));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie"));

  // 3) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie"));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 5) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie"));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  // Make sure that the tree result also has the same reason.
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a prerendered page with cache-control:no-store enters bfcache with
// the flag on, and if a cookie is modified before the prerendered page is
// activated via JavaScript, gets evicted with cookie modified marked.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreCookieModifiedBeforePrerendererActivationThroughJavaScript) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

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

  // 1) Prerender the document and specify no-store for the main resource.
  prerender_helper().AddPrerenderAsync(url_a);
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  TestActivationManager activation_manager(web_contents(), url_a);
  ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
                     JsReplace("location = $1", url_a)));
  ASSERT_TRUE(activation_manager.WaitForAfterChecks());

  // 2) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript before the page is activated.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 3) Resume the activation.
  activation_manager.WaitForNavigationFinished();
  EXPECT_TRUE(activation_manager.was_activated());
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 4) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 5) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie"));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  // Make sure that the tree result also has the same reason.
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and if a cookie is modified, it gets evicted with cookie changed, but if
// navigated away again and navigated back, it gets evicted without cookie
// change marked.
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestAllowCacheControlNoStore,
                       PagesWithCacheControlNoStoreCookieModifiedBackTwice) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response_back(
      embedded_test_server(), "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCache);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Set a normal cookie from JavaScript.
  EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=bar'"));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie"));

  // 3) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie"));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 5) Go back. |rfh_a| should be evicted upon restoration.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->web_contents()->GetController().GoBack();
  response_back.WaitForRequest();
  response_back.Send(kResponseWithNoCache);
  response_back.Done();
  observer.Wait();

  EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie"));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
  RenderFrameHostImplWrapper rfh_a_2(current_frame_host());
  rfh_a_2->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 6) Navigate away to b.com. |rfh_a_2| should enter bfcache again.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a_2->IsInBackForwardCache());

  // 7) Navigate back to a.com. This time the cookie change has to be reset and
  // gets evicted with a different reason.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStore}, {}, {}, {}, {},
                    FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and even if a cookie is modified on a different domain than the entry, the
// entry is not marked as cookie modified.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreCookieModifiedThroughJavaScriptOnDifferentDomain) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCache);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to b.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_b));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  ExpectNotRestored({NotRestoredReason::kCacheControlNoStore}, {}, {}, {}, {},
                    FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store records other not restored
// reasons along with kCacheControlNoStore when eviction happens.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreRecordOtherReasonsWhenEvictionHappens) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCache);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. At this point the page should be in bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Execute JavaScript and evict the entry.
  EvictByJavaScript(rfh_a.get());

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

  ExpectNotRestored({NotRestoredReason::kJavaScriptExecution,
                     NotRestoredReason::kCacheControlNoStore},
                    {}, {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution,
                                      NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store records other not restored
// reasons along with kCacheControlNoStore when there are other blocking reasons
// upon entering bfcache.
// TODO(crbug.com/417215501): this test is not using `embedded_test_server()` so
// the current set up for prerendering doesn't work, we will only test the
// normal navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreRecordOtherReasonsUponEntrance) {
  ASSERT_TRUE(CreateHttpsServer()->Start());

  GURL url_a(
      https_server()->GetURL("a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(https_server()->GetURL("b.com", "/title1.html"));

  // 1) Load the document and specify no-store for the main resource.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
  // Use blocklisted feature.
  EXPECT_TRUE(ExecJs(rfh_a.get(), kBlockingScript));

  // 2) Navigate away. |rfh_a| should not enter bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

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

  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures,
                     NotRestoredReason::kCacheControlNoStore},
                    {kBlockingReasonEnum}, {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures,
                                      NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures({kBlockingReasonEnum})));
}

// Test that a page with cache-control:no-store records eviction reasons along
// with kCacheControlNoStore when the entry is evicted for other reasons.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreRecordOtherReasonsForEviction) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCache);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Evict |rfh_a| by JavaScriptExecution.
  EvictByJavaScript(rfh_a.get());
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 4) Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kJavaScriptExecution,
                     NotRestoredReason::kCacheControlNoStore},
                    {}, {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution,
                                      NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures()));
}

namespace {
const char kResponseWithNoCacheWithCookie[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Set-Cookie: foo=bar\r\n"
    "Cache-Control: no-store\r\n"
    "\r\n"
    "The server speaks HTTP!";

const char kResponseWithNoCacheWithHTTPOnlyCookie[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Set-Cookie: foo=bar; Secure; HttpOnly;\r\n"
    "Cache-Control: no-store\r\n"
    "\r\n"
    "The server speaks HTTP!";

const char kResponseWithNoCacheWithHTTPOnlyCookie2[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Set-Cookie: foo=baz; Secure; HttpOnly;\r\n"
    "Cache-Control: no-store\r\n"
    "\r\n"
    "The server speaks HTTP!";

const char kResponseWithNoCacheWithRedirectionWithHTTPOnlyCookie[] =
    "HTTP/1.1 302 Moved Temporarily\r\n"
    "Location: /redirected\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Set-Cookie: foo=baz; Secure; HttpOnly;\r\n"
    "Cache-Control: no-store\r\n"
    "\r\n";
}  // namespace

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and if a cookie is modified while it is in bfcache via response header, gets
// evicted with cookie modified marked.
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestAllowCacheControlNoStore,
                       PagesWithCacheControlNoStoreSetFromResponseHeader) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_initial(embedded_test_server()->GetURL("a.com", "/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_initial));

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

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

  // 1) Load the document and specify no-store for the main resource.
  NavigateToPageWithResponseFromMainWebContents(url_a, response,
                                                kResponseWithNoCacheWithCookie);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
  EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie"));

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie"));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie"));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and if HTTPOnly cookie is modified while it is in bfcache, gets evicted with
// HTTPOnly cookie modified marked.
// TODO(crbug.com/417215501): this test is not using `embedded_test_server()` so
// the current set up for prerendering doesn't work, we will only test the
// normal navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreSetFromResponseHeaderHTTPOnlyCookie) {
  // HTTPOnly cookie can be only set over HTTPS.
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title2.html");
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
  GURL url_a_2(https_server()->GetURL("a.com", "/title2.html"));
  GURL url_b(https_server()->GetURL("b.com", "/title3.html"));

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCacheWithHTTPOnlyCookie);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
  // HTTPOnly cookie should not be accessible from JavaScript.
  EXPECT_EQ("", EvalJs(tab_to_be_bfcached, "document.cookie"));

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify HTTPOnly cookie
  // from the response.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a_2);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2);
  response2.Done();
  observer2.Wait();

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and if a HTTPOnly cookie is modified, it gets evicted with cookie changed,
// but if navigated away again and navigated back, it gets evicted without
// HTTPOnly cookie change marked.
// TODO(crbug.com/417215501): this test is not using `embedded_test_server()` so
// the current set up for prerendering doesn't work, we will only test the
// normal navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestAllowCacheControlNoStore,
    PagesWithCacheControlNoStoreHTTPOnlyCookieModifiedBackTwice) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title2.html");
  net::test_server::ControllableHttpResponse response3(https_server(),
                                                       "/title1.html");
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
  GURL url_a_2(https_server()->GetURL("a.com", "/title2.html"));
  GURL url_b(https_server()->GetURL("b.com", "/title3.html"));

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCacheWithHTTPOnlyCookie);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // response header.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a_2);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2);
  response2.Done();
  observer2.Wait();

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  TestNavigationObserver observer3(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->web_contents()->GetController().GoBack();
  response3.WaitForRequest();
  response3.Send(kResponseWithNoCache);
  response3.Done();
  observer3.Wait();

  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));

  RenderFrameHostImplWrapper rfh_a_2(current_frame_host());
  rfh_a_2->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 5) Navigate away to b.com. |rfh_a_2| should enter bfcache again.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a_2->IsInBackForwardCache());

  // 6) Navigate back to a.com. This time the cookie change has to be reset and
  // gets evicted with a different reason.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStore}, {}, {}, {}, {},
                    FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                  BlockListedFeatures()));
}

namespace {
// Causes a fetch request, start and complete in the target frame.
void SendFetchRequest(const ToRenderFrameHost& execution_target,
                      const GURL& url) {
  ASSERT_EQ(42, EvalJs(execution_target, JsReplace(
                                             R"(
      fetch($1)
          .then(p => {
              // Ensure that we drain the pipe to avoid blocking on network
              // activity.
              p.text();
              return 42;
          })
      )",
                                             url)));
}

// Causes an XHR, start and complete in the target frame.
void SendXhrRequest(const ToRenderFrameHost& execution_target,
                    const GURL& url) {
  ASSERT_EQ(42, EvalJs(execution_target, JsReplace(
                                             R"(
      const xhr = new XMLHttpRequest();
      xhr.open('GET', $1);
      xhr.send();
      new Promise(resolve => {
        // Use `onloadend` to ensure that the response is loaded successfully.
        xhr.onloadend = () => {resolve(42)};
      });
      )",
                                             url)));
}

// Creates an iframe in the target frame with this url. It waits until the frame
// has loaded.
void CreateIframe(const ToRenderFrameHost& execution_target, GURL url) {
  ASSERT_EQ(42, EvalJs(execution_target, JsReplace(
                                             R"(
      const iframeElement = document.createElement("iframe");
      iframeElement.src = $1;
      document.body.appendChild(iframeElement);
      new Promise(r => {
          iframeElement.onload = () => {r(42)};
      });
      )",
                                             url)));
}
}  // namespace

enum class RequestType {
  kFetch,
  kXhr,
};

// Testing the BFCache behavior when the document sends a JavaScript network
// request and receiving response with "Cache-Control: no-store" header.
class BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest
    : public BackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<RequestType> {
 public:
  // Provides meaningful param names instead of /0 and /1.
  static std::string DescribeParams(
      const ::testing::TestParamInfo<ParamType>& info) {
    switch (info.param) {
      case RequestType::kFetch:
        return "Fetch";
      case RequestType::kXhr:
        return "XHR";
    }
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

 protected:
  // Make a JavaScript network request using the appropriate method.
  void SendJsNetworkRequest(const ToRenderFrameHost& execution_target,
                            GURL url) {
    switch (GetParam()) {
      case RequestType::kFetch:
        SendFetchRequest(execution_target, url);
        break;
      case RequestType::kXhr:
        SendXhrRequest(execution_target, url);
        break;
    }
  }
};
INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest,
    ::testing::Values(RequestType::kFetch, RequestType::kXhr),
    &BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest::
        DescribeParams);

// Test that a page without CCNS that makes a request that receives CCNS
// response is not evicted and does not log the
// `kJsNetworkRequestReceivedCacheControlNoStoreResource` reason.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest,
    CCNSResponseNotLogged) {
  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"));
  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));

  // Load the document.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Make a request that receives CCNS response in the main frame.
  SendJsNetworkRequest(shell(), url_a_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Check that the document is cached.
  ASSERT_TRUE(rfh_a->IsInBackForwardCache());

  // Go back and check that it was restored.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectRestored(FROM_HERE);
}

// Test that a page with CCNS that makes a JavaScript network request which
// receives CCNS response gets evicted and logs the
// `kJsNetworkRequestReceivedCacheControlNoStoreResource` reason.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest,
    CCNSResponseLoggedMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Make a request that receives CCNS response in the main frame.
  SendJsNetworkRequest(shell(), url_a_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Wait until the first document has been destroyed.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back and check that it was not restored.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {BlocklistedFeature::kMainResourceHasCacheControlNoStore,
                     BlocklistedFeature::
                         kJsNetworkRequestReceivedCacheControlNoStoreResource},
                    {}, {}, {}, FROM_HERE);
}

// Test that a page with CCNS that makes a request which receives CCNS response
// in a same-as-root-origin subframe of a cross-origin subframe gets evicted and
// logs the `kJsNetworkRequestReceivedCacheControlNoStoreResource` reason.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest,
    CCNSResponseSameOriginSubFrameLogged) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Create a cross-origin iframe with same-as-root-origin iframe inside that
  // and make a request that receives CCNS response in that grand-child
  // iframe.
  CreateIframe(rfh_a.get(), url_b);
  CreateIframe(DescendantRenderFrameHostImplAt(rfh_a.get(), {0}),
               url_a_no_store);

  SendJsNetworkRequest(DescendantRenderFrameHostImplAt(rfh_a.get(), {0, 0}),
                       url_a_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Wait until the first document has been destroyed.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back and check that it was not cached and that both reasons are present.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {BlocklistedFeature::kMainResourceHasCacheControlNoStore,
                     BlocklistedFeature::
                         kJsNetworkRequestReceivedCacheControlNoStoreResource},
                    {}, {}, {}, FROM_HERE);
}

// Test that a page with CCNS that makes a request which receives CCNS response
// in a same-origin subframe gets evicted and logs the
// `kJsNetworkRequestReceivedCacheControlNoStoreResource` reason in the correct
// place in the tree of reasons.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest,
    CCNSResponseSubFrameTree) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Create a same-origin iframe make a request that receives CCNS response.
  CreateIframe(rfh_a.get(), url_a_no_store);

  SendJsNetworkRequest(DescendantRenderFrameHostImplAt(rfh_a.get(), {0}),
                       url_a_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Wait until the first document has been destroyed.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back and check that it was not cached and that both reasons are present.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {BlocklistedFeature::kMainResourceHasCacheControlNoStore,
                     BlocklistedFeature::
                         kJsNetworkRequestReceivedCacheControlNoStoreResource},
                    {}, {}, {}, FROM_HERE);

  // The not restored reason name for JS network request and CCNS should appear
  // in the subframe.
  auto subframe_result = MatchesNotRestoredReasons(
      /*id=*/std::nullopt, /*name=*/std::nullopt, /*src=*/url_a_no_store.spec(),
      /*reasons=*/
      {MatchesDetailedReason("response-cache-control-no-store",
                             /*source=*/std::nullopt),
       MatchesDetailedReason(
           "response-cache-control-no-store-with-js-network-request",
           /*source=*/std::nullopt)},
      MatchesSameOriginDetails(
          /*url=*/url_a_no_store,
          /*children=*/{}));
  EXPECT_THAT(
      current_frame_host()->NotRestoredReasonsForTesting(),
      MatchesNotRestoredReasons(
          /*id=*/std::nullopt, /*name=*/std::nullopt, /*src=*/std::nullopt,
          /*reasons=*/
          {},
          MatchesSameOriginDetails(
              /*url=*/url_a_no_store,
              /*children=*/
              {subframe_result})));
}

// Test that a page with CCNS that makes a request which receives CCNS response
// in a cross-origin subframe is not evicted and does not log the
// `kJsNetworkRequestReceivedCacheControlNoStoreResource` reason.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest,
    CCNSResponseCrossOriginSubFrameNotLogged) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  GURL url_b_no_store(embedded_test_server()->GetURL(
      "b.com", "/set-header?Cache-Control: no-store"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Create an same-origin iframe and make a request that receives CCNS response
  // in that iframe.
  CreateIframe(rfh_a.get(), url_b_no_store);

  SendJsNetworkRequest(DescendantRenderFrameHostImplAt(rfh_a.get(), {0}),
                       url_b_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Check that the document is cached.
  ASSERT_TRUE(rfh_a->IsInBackForwardCache());

  // Go back and check that it was restored.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectRestored(FROM_HERE);
}

class BackForwardCacheWithJsNetworkRequestCCNSAllowlistBrowserTest
    : public BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(
        blink::features::kBackForwardCacheCCNSAllowlist,
        blink::features::kBackForwardCacheCCNSAllowedDomains.name,
        "allowed.com");
    BackForwardCacheWithJsNetworkRequestReceivingCCNSResourceBrowserTest::
        SetUpCommandLine(command_line);
  }
};
INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheWithJsNetworkRequestCCNSAllowlistBrowserTest,
    ::testing::Values(RequestType::kFetch, RequestType::kXhr),
    &BackForwardCacheWithJsNetworkRequestCCNSAllowlistBrowserTest::
        DescribeParams);

// Test that a page with CCNS that makes a request which receives CCNS response
// from an allowlisted domain is not evicted and gets restored from BFCache.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestCCNSAllowlistBrowserTest,
    AllowlistedDomainNotBlocked) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  GURL url_allowed_no_store(embedded_test_server()->GetURL(
      "allowed.com",
      "/set-header?Cache-Control: no-store&Access-Control-Allow-Origin: *"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Make a request to an allowlisted domain that receives CCNS response.
  SendJsNetworkRequest(shell(), url_allowed_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Check that the document is cached.
  ASSERT_TRUE(rfh_a->IsInBackForwardCache());

  // Go back and check that it was restored.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectRestored(FROM_HERE);
}

// Test that a page with CCNS that makes a request which receives CCNS response
// from a non-allowlisted domain is evicted and logs the
// `kJsNetworkRequestReceivedCacheControlNoStoreResource` reason.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithJsNetworkRequestCCNSAllowlistBrowserTest,
    NonAllowlistedDomainBlocked) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  GURL url_disallowed_no_store(embedded_test_server()->GetURL(
      "disallowed.com",
      "/set-header?Cache-Control: no-store&Access-Control-Allow-Origin: *"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Make a request to a non-allowlisted domain that receives CCNS response.
  SendJsNetworkRequest(shell(), url_disallowed_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Wait until the first document has been destroyed.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back and check that it was not restored.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {BlocklistedFeature::kMainResourceHasCacheControlNoStore,
                     BlocklistedFeature::
                         kJsNetworkRequestReceivedCacheControlNoStoreResource},
                    {}, {}, {}, FROM_HERE);
}

// A subclass of `ContentBrowserTestContentBrowserClient` for testing the logic
// that checks if cookie is enabled.
class CookieDisabledContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  void SetIsCookieEnabled(bool new_value) { is_cookie_enabled_ = new_value; }

  bool IsFullCookieAccessAllowed(
      BrowserContext* browser_context,
      WebContents* web_contents,
      const GURL& url,
      const blink::StorageKey& storage_key,
      net::CookieSettingOverrides overrides) override {
    return is_cookie_enabled_;
  }

 private:
  bool is_cookie_enabled_ = true;
};
class BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpOnMainThread() override {
    BackForwardCacheBrowserTest::SetUpOnMainThread();
    content_browser_client_ =
        std::make_unique<CookieDisabledContentBrowserClient>();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
    EnableFeatureAndSetParams(
        features::kCacheControlNoStoreEnterBackForwardCache, "level",
        "restore-unless-cookie-change");
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

  void SetIsCookieEnabled(bool is_cookie_enabled) {
    CHECK(content_browser_client_);
    content_browser_client_->SetIsCookieEnabled(is_cookie_enabled);
  }

 private:
  std::unique_ptr<CookieDisabledContentBrowserClient> content_browser_client_;
};

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and gets restored if cookies do not change.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreRestoreFromBackForwardCache) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/main_document");
  ASSERT_TRUE(embedded_test_server()->Start());

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();

  // 2) Navigate away. |rfh_a| should enter the bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back. |rfh_a| should be restored.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Test that a page with CCNS that makes a fetch that receives CCNS response
// is blocked even when CCNS pages are allowed to be restored. This only tests
// fetch, the blocking mechanism is the same for all kinds of requests, so if it
// works for one it will work for all.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    CCNSResponseBlocks) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a_no_store(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Load the document and specify no-store for the main resource.
  ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // Make a request that receives CCNS response in the main frame.
  SendFetchRequest(shell(), url_a_no_store);

  // Navigate away.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));

  // Wait until the first document has been destroyed.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // Go back and check that it was not restored.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kBlocklistedFeatures},
      {BlocklistedFeature::kJsNetworkRequestReceivedCacheControlNoStoreResource,
       BlocklistedFeature::kMainResourceHasCacheControlNoStore},
      {}, {}, {}, FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreEvictedIfCookieChange) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie"));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and gets evicted with both JavaScript and HTTPOnly cookie changes. Only
// HTTPOnly cookie reason should be recorded.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreEvictedWithBothCookieReasons) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title2.html");
  ASSERT_TRUE(https_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  // Modify cookie from JavaScript as well.
  EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=quz'"));

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify HTTPOnly cookie
  // from the response.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a_2);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2);
  response2.Done();
  observer2.Wait();

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));
}

// Test that a page with cache-control:no-store gets restored if the only cookie
// modification comes from the response of the `NavigationRequest`.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithCookieSetInResponse) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  ASSERT_TRUE(https_server()->Start());

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

  // 1) Load the document and specify no-store for the main resource, the
  // response also sets a cookie.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCacheWithCookie);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back. |rfh_a| should be restored.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Test that a page with `Cache-control: no-store` header gets evicted if some
// cookie is modified while the server receives the request but has not
// completed the response yet.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithCookieSetAfterRequestIsMade) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  ASSERT_TRUE(https_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // 2) Before the response is sent, set a cookie from another tab.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=bar'"));

  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
  EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie"));

  // 3) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store gets evicted if some cookie is
// modified before navigating away.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithCookieSetBeforeNavigateAway) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  ASSERT_TRUE(https_server()->Start());

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Set a cookie from JavaScript.
  EXPECT_TRUE(ExecJs(web_contents(), "document.cookie='foo=bar'"));
  EXPECT_EQ("foo=bar", EvalJs(web_contents(), "document.cookie"));

  // 3) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store gets evicted if some cookie is
// modified from another tab before navigating away.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithCookieSetFromAnotherTabBeforeNavigateAway) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  ASSERT_TRUE(https_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Set a cookie from another tab.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=bar'"));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie"));

  // 3) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with cache-control:no-store gets restored if the cookie is
// modified by another tab before the navigation completes.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreRestoredIfCookieChangeIsMadeBeforeRedirection) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/main_document");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/redirected");
  ASSERT_TRUE(https_server()->Start());

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

  // 1) Load the document that will be redirected to another document.
  // Both of the documents specify the cache-control:no-store, but only the
  // document before redirection sets cookie.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCacheWithRedirectionWithHTTPOnlyCookie);
  response.Done();
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCache);
  response2.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back. |rfh_a| should be restored from BFCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Test that the cookie change information is retained after same document
// navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithCookieSetBeforeSameDocumentNavigation) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");

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

  GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
  GURL url_a2(https_server()->GetURL("a.com", "/title1.html#foo"));
  GURL url_b(https_server()->GetURL("b.com", "/title1.html"));

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Set a cookie from JavaScript, and perform a same document navigation.
  EXPECT_TRUE(ExecJs(web_contents(), "document.cookie='foo=bar'"));
  EXPECT_EQ("foo=bar", EvalJs(web_contents(), "document.cookie"));
  EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", url_a2.spec())));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  EXPECT_EQ(url_a2,
            web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  EXPECT_TRUE(rfh_a->IsActive());

  // 3) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

// Test that a page with `Cache-control: no-store` header gets evicted without
// crashes if some cookie is modified immediately before the back navigation.
// TODO: this test could be potentially flaky if the notification to
// CookieChangeListener is only received after the entire back navigation
// completes. If any flaky case is reported in the future, we should fix that by
// ensuring the eviction to happen after the NavigationRequest starts to process
// response but before it finishes committing the navigation.
// See discussion from https://crrev.com/c/4408607.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithCookieSetImmediatelyBeforeNavigateBack) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  ASSERT_TRUE(https_server()->Start());

  GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
  GURL url_a_2(https_server()->GetURL("a.com", "/title2.html"));

  // 1) Load the document and specify no-store for the main resource.
  {
    TestNavigationObserver observer(web_contents());
    shell()->LoadURL(url_a);
    RenderFrameHostImplWrapper rfh_a(current_frame_host());
    response.WaitForRequest();
    response.Send(kResponseWithNoCache);
    response.Done();
    observer.Wait();
    rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
  }

  // 2) Navigate away, and set a cookie from the new page.
  EXPECT_TRUE(NavigateToURL(shell(), url_a_2));
  EXPECT_TRUE(ExecJs(shell(), "document.cookie='foo=bar'"));

  // 3) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified}, {},
                    {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                  BlockListedFeatures()));
}

IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
    PagesWithCacheControlNoStoreIsNotCacheIfCookieIsDisabled) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(embedded_test_server(),
                                                       "/title1.html");
  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", "/title2.html"));

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(shell()->web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away and back. `rfh_a` should be restored from BFCache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  ExpectRestored(FROM_HERE);

  // 3) Disable cookies. `rfh_a` should not be stored in BFCache.
  SetIsCookieEnabled(false);
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 4) Go back and check that the reason contains
  // `BlocklistedFeature::kMainResourceHasCacheControlNoStore`.
  TestNavigationObserver observer2(shell()->web_contents());
  shell()->web_contents()->GetController().GoBack();
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCache);
  response2.Done();
  observer2.Wait();
  ExpectNotRestored({NotRestoredReason::kCacheControlNoStore,
                     NotRestoredReason::kCookieDisabled},
                    {}, {}, {}, {}, FROM_HERE);
}

class BackForwardCacheBrowserTestOnlyEvictCCNSIfCookieValueChangedTest
    : public BackForwardCacheBrowserTest,
      public testing::WithParamInterface<bool> {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
    EnableFeatureAndSetParams(
        features::kCacheControlNoStoreEnterBackForwardCache, "level",
        "restore-unless-cookie-change");
    if (IsBackForwardCacheCCNSIgnoreUnchangedCookiesEnabled()) {
      EnableFeatureAndSetParams(
          features::kBackForwardCacheCCNSIgnoreUnchangedCookies, "", "");
    } else {
      DisableFeature(features::kBackForwardCacheCCNSIgnoreUnchangedCookies);
    }
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

  bool IsBackForwardCacheCCNSIgnoreUnchangedCookiesEnabled() const {
    return GetParam();
  }
};

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

IN_PROC_BROWSER_TEST_P(
    BackForwardCacheBrowserTestOnlyEvictCCNSIfCookieValueChangedTest,
    PagesWithCacheControlNoStoreNotEvictedIfCookieChangeWithNoValueChange) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/title1.html");
  ASSERT_TRUE(embedded_test_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  // The response also sets a cookie "foo=bar".
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCacheWithCookie);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. `rfh_a` should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in `tab_to_modify_cookie` and modify cookie from
  // JavaScript to the same value.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=bar'"));
  EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 4) Go back. `rfh_a` should be evicted upon restoration when the flag is
  // off, or restored when the flag is on.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie"));
  if (IsBackForwardCacheCCNSIgnoreUnchangedCookiesEnabled()) {
    ExpectRestored(FROM_HERE);
  } else {
    ExpectNotRestored({NotRestoredReason::kCacheControlNoStoreCookieModified},
                      {}, {}, {}, {}, FROM_HERE);
    EXPECT_THAT(
        GetTreeResult()->GetDocumentResult(),
        MatchesDocumentResult(
            NotRestoredReasons(
                {NotRestoredReason::kCacheControlNoStoreCookieModified}),
            BlockListedFeatures()));
  }
}

class BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
    EnableFeatureAndSetParams(
        features::kCacheControlNoStoreEnterBackForwardCache, "level",
        "restore-unless-http-only-cookie-change");
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }
};

// Test that a page without cache-control:no-store can enter BackForwardCache
// and gets restored if HTTPOnly Cookie changes.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    NoCacheControlNoStoreButHTTPOnlyCookieChange) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/set-header?Set-Cookie: foo=bar; Secure; HttpOnly;"));
  GURL url_a_2(embedded_test_server()->GetURL(
      "a.com", "/set-header?Set-Cookie: foo=baz; Secure; HttpOnly;"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

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

  // 1) Load the document without cache-control:no-store.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify HTTPOnly cookie
  // from the header.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2));

  // 4) Go back. |rfh_a| should be restored from bfcache.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  ExpectRestored(FROM_HERE);
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and does not get evicted if normal cookies change.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    PagesWithCacheControlNoStoreNotEvictedIfNormalCookieChange) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/set-header?Cache-Control: no-store"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

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

  // 1) Load the document and specify no-store for the main resource.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from
  // JavaScript.
  EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a));
  EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'"));
  EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie"));

  // 4) Go back. |rfh_a| should be restored from bfcache.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));

  EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie"));
  ExpectRestored(FROM_HERE);
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and gets evicted if HTTPOnly cookie changes.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    PagesWithCacheControlNoStoreEvictedIfHTTPOnlyCookieChange) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title2.html");
  ASSERT_TRUE(https_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify HTTPOnly cookie
  // from the response.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a_2);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2);
  response2.Done();
  observer2.Wait();

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));
}

// Test that a page with cache-control:no-store enters bfcache with the flag on,
// and gets evicted if HTTPOnly cookie changes.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    PagesWithCacheControlNoStoreEvictedIfJSAndHTTPOnlyCookieChange) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title2.html");
  ASSERT_TRUE(https_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter bfcache.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  // Modify cookie from JavaScript as well.
  EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=quz'"));

  // 3) Navigate to a.com in |tab_to_modify_cookie| and modify HTTPOnly cookie
  // from the response.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a_2);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2);
  response2.Done();
  observer2.Wait();

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));
}

// Test that a page with cache-control:no-store gets restored if the HTTPOnly
// cookie modification comes from the response of the `NavigationRequest`.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithHTTPOnlyCookieSetInResponse) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  ASSERT_TRUE(https_server()->Start());

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

  // 1) Load the document and specify no-store for the main resource, the
  // response also sets a cookie.
  TestNavigationObserver observer(web_contents());
  shell()->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCacheWithHTTPOnlyCookie);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back. |rfh_a| should be restored.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectRestored(FROM_HERE);
}

// Test that a page with cache-control:no-store gets evicted if some HTTPOnly
// cookie is modified from another tab before navigating away.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithHTTPOnlyCookieSetFromAnotherTabBeforeNavigateAway) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title2.html");
  ASSERT_TRUE(https_server()->Start());

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

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Set an HTTPOnly cookie from another tab.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a_2);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie);
  response2.Done();
  observer2.Wait();

  // 3) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));
}

// Test that the cookie change information is retained after same document
// navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessHTTPOnlyCookieChange,
    PagesWithCacheControlNoStoreNotBFCachedWithHTTPOnlyCookieSetBeforeSameDocumentNavigation) {
  CreateHttpsServer();
  net::test_server::ControllableHttpResponse response(https_server(),
                                                      "/title1.html");
  net::test_server::ControllableHttpResponse response2(https_server(),
                                                       "/title1.html");

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

  GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
  GURL url_a2(https_server()->GetURL("a.com", "/title1.html#foo"));
  GURL url_b(https_server()->GetURL("b.com", "/title1.html"));

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

  // 1) Load the document and specify no-store for the main resource.
  TestNavigationObserver observer(tab_to_be_bfcached->web_contents());
  tab_to_be_bfcached->LoadURL(url_a);
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  response.WaitForRequest();
  response.Send(kResponseWithNoCache);
  response.Done();
  observer.Wait();
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Modify the HTTPOnly cookie from another tab.
  TestNavigationObserver observer2(tab_to_modify_cookie->web_contents());
  tab_to_modify_cookie->LoadURL(url_a);
  response2.WaitForRequest();
  response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie);
  response2.Done();
  observer2.Wait();

  // 3) Perform a same document navigation.
  EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", url_a2.spec())));
  EXPECT_TRUE(WaitForLoadStop(tab_to_be_bfcached->web_contents()));
  EXPECT_EQ(url_a2, tab_to_be_bfcached->web_contents()
                        ->GetPrimaryMainFrame()
                        ->GetLastCommittedURL());
  EXPECT_TRUE(rfh_a->IsActive());

  // 4) Navigate away. |rfh_a| should enter the bfcache since we only evict
  // before restoration.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 5) Go back. |rfh_a| should be evicted upon restoration.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}, {}, {},
      {}, {}, FROM_HERE);
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons(
              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
          BlockListedFeatures()));
}

#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)

class DeviceBoundSessionAccessObserver : public WebContentsObserver {
 public:
  DeviceBoundSessionAccessObserver(
      WebContents* web_contents,
      base::RepeatingCallback<void(
          const net::device_bound_sessions::SessionAccess&)> on_access_callback)
      : WebContentsObserver(web_contents),
        on_access_callback_(std::move(on_access_callback)) {}

  void OnDeviceBoundSessionAccessed(
      NavigationHandle* navigation,
      const net::device_bound_sessions::SessionAccess& access) override {
    on_access_callback_.Run(access);
  }
  void OnDeviceBoundSessionAccessed(
      RenderFrameHost* rfh,
      const net::device_bound_sessions::SessionAccess& access) override {
    on_access_callback_.Run(access);
  }

 private:
  base::RepeatingCallback<void(
      const net::device_bound_sessions::SessionAccess&)>
      on_access_callback_;
};

class BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
    EnableFeatureAndSetParams(
        features::kDeviceBoundSessionTerminationEvictBackForwardCache, "", "");
    EnableFeatureAndSetParams(net::features::kDeviceBoundSessions,
                              "RequireOriginTrialTokens", "false");
    EnableFeatureAndSetParams(
        unexportable_keys::
            kEnableBoundSessionCredentialsSoftwareKeysForManualTesting,
        "", "");
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }
};

IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated,
    NoCacheControlNoStoreButSessionTerminated) {
  embedded_https_test_server().SetSSLConfig(
      net::EmbeddedTestServer::CERT_TEST_NAMES);
  EXPECT_TRUE(embedded_https_test_server().InitializeAndListen());
  embedded_https_test_server().RegisterRequestHandler(
      net::device_bound_sessions::GetTestRequestHandler(
          embedded_https_test_server().GetURL("a.test", "/")));
  embedded_https_test_server().StartAcceptingConnections();

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

  // 1) Load the document without cache-control:no-store
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      embedded_https_test_server().GetURL("a.test", "/set-header")));
  RenderFrameHostImplWrapper cached_rfh(current_frame_host());

  // 2) Navigate away. `cached_rfh` should enter bfcache.
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      embedded_https_test_server().GetURL("b.test", "/set-header")));
  EXPECT_TRUE(cached_rfh->IsInBackForwardCache());

  // 3) Create a device bound session in another tab
  base::test::TestFuture<net::device_bound_sessions::SessionAccess> future;
  DeviceBoundSessionAccessObserver observer(
      tab_to_create_session->web_contents(),
      future.GetRepeatingCallback<
          const net::device_bound_sessions::SessionAccess&>());
  EXPECT_TRUE(NavigateToURL(
      tab_to_create_session,
      embedded_https_test_server().GetURL("a.test", "/dbsc_required")));
  EXPECT_EQ(future.Take().access_type,
            net::device_bound_sessions::SessionAccess::AccessType::kCreation);

  // 4) Terminate the session. This could happen through natural
  // navigation, but it's simpler to test it through the
  // `DeviceBoundSessionManager` interface.
  network::mojom::DeviceBoundSessionManager* device_bound_session_manager =
      tab_to_create_session->web_contents()
          ->GetBrowserContext()
          ->GetStoragePartitionForUrl(
              embedded_https_test_server().GetURL("/set-header"),
              /*can_create=*/true)
          ->GetDeviceBoundSessionManager();
  ASSERT_TRUE(device_bound_session_manager);

  base::RunLoop run_loop;
  device_bound_session_manager->DeleteAllSessions(
      net::device_bound_sessions::DeletionReason::kClearBrowsingData,
      /*created_after_time=*/std::nullopt,
      /*created_before_time=*/std::nullopt,
      /*filter=*/nullptr, run_loop.QuitClosure());
  run_loop.Run();

  // 5) Go back. `cached_rfh` should be restored from bfcache.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectRestored(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated,
    CacheControlNoStoreSessionTerminated) {
  embedded_https_test_server().SetSSLConfig(
      net::EmbeddedTestServer::CERT_TEST_NAMES);
  EXPECT_TRUE(embedded_https_test_server().InitializeAndListen());
  embedded_https_test_server().RegisterRequestHandler(
      net::device_bound_sessions::GetTestRequestHandler(
          embedded_https_test_server().GetURL("a.test", "/")));
  embedded_https_test_server().StartAcceptingConnections();

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

  // 1) Load the document with cache-control:no-store
  EXPECT_TRUE(
      NavigateToURL(tab_to_be_bfcached,
                    embedded_https_test_server().GetURL(
                        "a.test", "/set-header?Cache-Control: no-store")));
  RenderFrameHostImplWrapper cached_rfh(current_frame_host());
  cached_rfh->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. `cached_rfh` should enter bfcache.
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      embedded_https_test_server().GetURL("b.test", "/set-header")));
  EXPECT_TRUE(cached_rfh->IsInBackForwardCache());

  // 3) Create a device bound session in another tab
  base::test::TestFuture<net::device_bound_sessions::SessionAccess> future;
  DeviceBoundSessionAccessObserver observer(
      tab_to_create_session->web_contents(),
      future.GetRepeatingCallback<
          const net::device_bound_sessions::SessionAccess&>());
  EXPECT_TRUE(NavigateToURL(
      tab_to_create_session,
      embedded_https_test_server().GetURL("a.test", "/dbsc_required")));
  EXPECT_EQ(future.Take().access_type,
            net::device_bound_sessions::SessionAccess::AccessType::kCreation);

  // 4) Terminate the session. This could happen through natural
  // navigation, but it's simpler to test it through the
  // `DeviceBoundSessionManager` interface.
  network::mojom::DeviceBoundSessionManager* device_bound_session_manager =
      tab_to_create_session->web_contents()
          ->GetBrowserContext()
          ->GetStoragePartitionForUrl(
              embedded_https_test_server().GetURL("a.test", "/set-header"),
              /*can_create=*/true)
          ->GetDeviceBoundSessionManager();
  ASSERT_TRUE(device_bound_session_manager);

  base::RunLoop run_loop;
  cached_rfh->SetDeviceBoundSessionTerminatedCallback(run_loop.QuitClosure());
  device_bound_session_manager->DeleteAllSessions(
      net::device_bound_sessions::DeletionReason::kClearBrowsingData,
      /*created_after_time=*/std::nullopt,
      /*created_before_time=*/std::nullopt,
      /*filter=*/nullptr, base::DoNothing());
  run_loop.Run();

  // 5) Go back. `cached_rfh` should not be restored from bfcache.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreDeviceBoundSessionTerminated}, {},
      {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::
                           kCacheControlNoStoreDeviceBoundSessionTerminated}),
                  BlockListedFeatures()));
}

std::unique_ptr<net::test_server::HttpResponse> RedirectToUrl(
    const GURL& gurl,
    const net::test_server::HttpRequest& request) {
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_PERMANENT_REDIRECT);
  http_response->AddCustomHeader("Location", gurl.spec());
  return http_response;
}

IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated,
    CacheControlNoStoreSessionTerminatedOnRedirectedPage) {
  embedded_https_test_server().SetSSLConfig(
      net::EmbeddedTestServer::CERT_TEST_NAMES);
  EXPECT_TRUE(embedded_https_test_server().InitializeAndListen());
  embedded_https_test_server().RegisterRequestHandler(
      net::device_bound_sessions::GetTestRequestHandler(
          embedded_https_test_server().GetURL("a.test", "/")));
  embedded_https_test_server().StartAcceptingConnections();

  GURL redirected_url = embedded_https_test_server().GetURL(
      "a.test", "/set-header?Cache-Control: no-store");
  CreateHttpsServer();
  https_server()->RegisterRequestHandler(
      base::BindRepeating(&RedirectToUrl, redirected_url));
  ASSERT_TRUE(https_server()->Start());

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

  // 1) Load the document with cache-control:no-store.
  EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, https_server()->GetURL("/"),
                            redirected_url));
  RenderFrameHostImplWrapper cached_rfh(current_frame_host());
  cached_rfh->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate away. `cached_rfh` should enter bfcache.
  EXPECT_TRUE(NavigateToURL(
      tab_to_be_bfcached,
      embedded_https_test_server().GetURL("b.test", "/set-header")));
  EXPECT_TRUE(cached_rfh->IsInBackForwardCache());

  // 3) Create a device bound session in another tab
  base::test::TestFuture<net::device_bound_sessions::SessionAccess> future;
  DeviceBoundSessionAccessObserver observer(
      tab_to_create_session->web_contents(),
      future.GetRepeatingCallback<
          const net::device_bound_sessions::SessionAccess&>());
  EXPECT_TRUE(NavigateToURL(
      tab_to_create_session,
      embedded_https_test_server().GetURL("a.test", "/dbsc_required")));
  EXPECT_EQ(future.Take().access_type,
            net::device_bound_sessions::SessionAccess::AccessType::kCreation);

  // 4) Terminate the session. This could happen through natural
  // navigation, but it's simpler to test it through the
  // `DeviceBoundSessionManager` interface.
  network::mojom::DeviceBoundSessionManager* device_bound_session_manager =
      tab_to_create_session->web_contents()
          ->GetBrowserContext()
          ->GetStoragePartitionForUrl(
              embedded_https_test_server().GetURL("a.test", "/set-header"),
              /*can_create=*/true)
          ->GetDeviceBoundSessionManager();
  ASSERT_TRUE(device_bound_session_manager);

  base::RunLoop run_loop;
  cached_rfh->SetDeviceBoundSessionTerminatedCallback(run_loop.QuitClosure());
  device_bound_session_manager->DeleteAllSessions(
      net::device_bound_sessions::DeletionReason::kClearBrowsingData,
      /*created_after_time=*/std::nullopt,
      /*created_before_time=*/std::nullopt,
      /*filter=*/nullptr, base::DoNothing());
  run_loop.Run();

  // 5) Go back. `cached_rfh` should not be restored from bfcache.
  ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
  ExpectNotRestored(
      {NotRestoredReason::kCacheControlNoStoreDeviceBoundSessionTerminated}, {},
      {}, {}, {}, FROM_HERE);
  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons(
                      {NotRestoredReason::
                           kCacheControlNoStoreDeviceBoundSessionTerminated}),
                  BlockListedFeatures()));
}

#endif  // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)

}  // namespace content
