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

#include <array>
#include <string_view>
#include <utility>

#include "base/command_line.h"
#include "base/functional/callback.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_logging_settings.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/task_manager/task_manager_tester.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/tracing.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h"
#include "components/permissions/permission_manager.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_request_description.h"
#include "content/public/browser/permission_result.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "pdf/buildflags.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(ENABLE_PDF)
#include <tuple>
#include <variant>

#include "chrome/browser/pdf/pdf_extension_test_util.h"
#include "chrome/browser/pdf/test_mime_handler_stream_manager.h"
#include "pdf/pdf_features.h"

namespace {
struct ChromeBackForwardCacheBrowserWithEmbedPdfTestPassToString {
  std::string operator()(
      const ::testing::TestParamInfo<std::tuple<std::string_view, bool>>& i)
      const {
    return std::string(std::get<1>(i.param) ? "oopif_" : "guestview_") +
           std::string(std::get<0>(i.param));
  }
};
}  // namespace
#endif  // BUILDFLAG(ENABLE_PDF)

namespace {
constexpr std::array<std::string_view, 2>
    kChromeBackForwardCacheBrowserWithEmbedTestValues = {"embed", "object"};
}  // namespace

class ChromeBackForwardCacheBrowserTest : public InProcessBrowserTest {
 public:
  ChromeBackForwardCacheBrowserTest() = default;

  ChromeBackForwardCacheBrowserTest(const ChromeBackForwardCacheBrowserTest&) =
      delete;
  ChromeBackForwardCacheBrowserTest& operator=(
      const ChromeBackForwardCacheBrowserTest&) = delete;

  ~ChromeBackForwardCacheBrowserTest() override = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  GURL GetURL(const std::string& host) {
    return embedded_test_server()->GetURL(host, "/title1.html");
  }

  virtual std::vector<base::test::FeatureRefAndParams>
  GetEnabledFeaturesAndParams() const {
    return content::GetDefaultEnabledBackForwardCacheFeaturesForTesting();
  }

  virtual std::vector<base::test::FeatureRef> GetDisabledFeatures() const {
    return content::GetDefaultDisabledBackForwardCacheFeaturesForTesting(
        {// Entry to the cache can be slow during testing and cause
         // flakiness.
         features::kBackForwardCacheEntryTimeout});
  }

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // For using WebBluetooth.
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);

    SetupFeaturesAndParameters();
  }

  content::WebContents* web_contents() const {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  content::RenderFrameHost* current_frame_host() {
    return web_contents()->GetPrimaryMainFrame();
  }

  void SetupFeaturesAndParameters() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        GetEnabledFeaturesAndParams(), GetDisabledFeatures());
    vmodule_switches_.InitWithSwitches("back_forward_cache_impl=1");
  }

  std::unique_ptr<base::HistogramTester> histogram_tester_;

 private:
  // TODO(https://crbug.com/423465927): Explore a better approach to make the
  // existing tests run with the prewarm feature enabled.
  test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
      test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};

  base::test::ScopedFeatureList scoped_feature_list_;
  logging::ScopedVmoduleSwitches vmodule_switches_;
};

IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest, Basic) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to A.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("a.com")));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // 2) Navigate to B.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("b.com")));
  content::RenderFrameHostWrapper rfh_b(current_frame_host());

  // A is frozen in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 3) Navigate back.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));

  // A is restored, B is stored.
  EXPECT_EQ(rfh_b->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 4) Navigate forward.
  web_contents()->GetController().GoForward();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));

  // A is stored, B is restored.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest, BasicIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to A.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("a.com")));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // 2) Add an iframe B.
  EXPECT_TRUE(content::ExecJs(rfh_a.get(), R"(
    let url = new URL(location.href);
    url.hostname = 'b.com';
    let iframe = document.createElement('iframe');
    iframe.url = url;
    document.body.appendChild(iframe);
  )"));
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));

  content::RenderFrameHost* rfh_b = nullptr;
  rfh_a->ForEachRenderFrameHost([&](content::RenderFrameHost* rfh) {
    if (rfh != rfh_a.get()) {
      rfh_b = rfh;
    }
  });
  EXPECT_TRUE(rfh_b);
  content::RenderFrameHostWrapper rfh_b_wrapper(rfh_b);

  // 2) Navigate to C.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("c.com")));
  content::RenderFrameHostWrapper rfh_c(current_frame_host());

  // A and B are frozen. The page A(B) is stored in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  EXPECT_EQ(rfh_b_wrapper->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 3) Navigate back.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));

  // The page A(B) is restored and C is frozen.
  EXPECT_EQ(rfh_c->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       ContentSettingPermissionContextBase) {
  // HTTPS needed for GEOLOCATION permission
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  ASSERT_TRUE(https_server.Start());

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

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(web_contents(), url_a));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // 2) Navigate to B.
  EXPECT_TRUE(NavigateToURL(web_contents(), url_b));
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  base::MockOnceCallback<void(content::PermissionResult)> callback;
  EXPECT_CALL(callback, Run(content::PermissionResult(
                            blink::mojom::PermissionStatus::ASK,
                            content::PermissionStatusSource::UNSPECIFIED)));
  browser()
      ->GetProfile()
      ->GetPermissionController()
      ->RequestPermissionFromCurrentDocument(
          rfh_a.get(),
          content::PermissionRequestDescription(
              content::PermissionDescriptorUtil::
                  CreatePermissionDescriptorForPermissionType(
                      blink::PermissionType::GEOLOCATION),
              /* user_gesture = */ true),
          callback.Get());

  // Ensure |rfh_a| is evicted from the cache because it is not allowed to
  // service the GEOLOCATION permission request.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
}

IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       DoesNotCacheIfPictureInPicture) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  // Navigate to a page with picture-in-picture functionality.
  const base::FilePath::CharType picture_in_picture_page[] =
      FILE_PATH_LITERAL("media/picture-in-picture/window-size.html");
  GURL test_page_url = chrome_test_utils::GetTestUrl(
      base::FilePath(base::FilePath::kCurrentDirectory),
      base::FilePath(picture_in_picture_page));
  EXPECT_TRUE(content::NavigateToURL(web_contents(), test_page_url));
  content::RenderFrameHostWrapper rfh(current_frame_host());

  // Execute picture-in-picture on the page.
  ASSERT_EQ(true, content::EvalJs(web_contents(), "enterPictureInPicture();"));

  // Navigate away.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("b.com")));

  // The page uses Picture-in-Picture so it must be evicted from the cache and
  // deleted.
  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
}

#if BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       DoesNotCacheIfWebShare) {
  // HTTPS needed for WebShare permission.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  ASSERT_TRUE(https_server.Start());

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

  // 1) Navigate to A.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_a));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // Use the WebShare feature on the empty page.
  EXPECT_EQ("success", content::EvalJs(current_frame_host(), R"(
    new Promise(resolve => {
      navigator.share({title: 'the title'})
        .then(m => { resolve("success"); })
        .catch(error => { resolve(error.message); });
    });
  )"));

  // 2) Navigate away.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_b));

  // The page uses WebShare so it must be evicted from the cache and deleted.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
}

IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       DoesNotCacheIfWebNfc) {
  // HTTPS needed for WebNfc permission.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  ASSERT_TRUE(https_server.Start());

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

  // 1) Navigate to A.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_a));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // Use the WebNfc feature on the empty page.
  EXPECT_EQ("success", content::EvalJs(current_frame_host(), R"(
    const ndef = new NDEFReader();
    new Promise(async resolve => {
      try {
        await ndef.write("Hello");
        resolve('success');
      } catch (error) {
        resolve(error.message);
      }
    });
  )"));

  // 2) Navigate away.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_b));

  // The page uses WebNfc so it must be evicted from the cache and deleted.
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
}
#endif  // BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       RestoresMixedContentSettings) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  ASSERT_TRUE(https_server.Start());
  GURL url_a(https_server.GetURL("a.test",
                                 "/content_setting_bubble/mixed_script.html"));
  GURL url_b(https_server.GetURL("b.test",
                                 "/content_setting_bubble/mixed_script.html"));

  // 1) Load page A that has mixed content.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_a));
  // Mixed content should be blocked at first.
  EXPECT_FALSE(MixedContentSettingsTabHelper::FromWebContents(web_contents())
                   ->IsRunningInsecureContentAllowed(*current_frame_host()));

  // 2) Emulate link clicking on the mixed script bubble to allow mixed content
  // to run.
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());

  {
    std::unique_ptr<ContentSettingBubbleModel> model(
        ContentSettingBubbleModel::CreateContentSettingBubbleModel(
            browser()->GetFeatures().content_setting_bubble_model_delegate(),
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetPrimaryPage(),
            ContentSettingsType::MIXEDSCRIPT));
    model->OnCustomLinkClicked();
  }

  // 3) Wait for reload.
  observer.Wait();
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // Mixed content should no longer be blocked.
  EXPECT_TRUE(MixedContentSettingsTabHelper::FromWebContents(web_contents())
                  ->IsRunningInsecureContentAllowed(*current_frame_host()));

  // 4) Navigate to page B, which should use a different SiteInstance and
  // resets the mixed content settings.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_b));
  // Mixed content should be blocked in the new page.
  EXPECT_FALSE(MixedContentSettingsTabHelper::FromWebContents(web_contents())
                   ->IsRunningInsecureContentAllowed(*current_frame_host()));

  // 5) A is stored in BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 6) Go back to page A.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
  // Mixed content settings is restored, so it's no longer blocked.
  EXPECT_TRUE(MixedContentSettingsTabHelper::FromWebContents(web_contents())
                  ->IsRunningInsecureContentAllowed(*current_frame_host()));
}

// Enables trace events related to navigation. As pages are cached or restored,
// trace events are interspersed between state updates. This test ensures that
// we don't have partially updated state leading to invariant violations while
// tracing values.
IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest, Tracing) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(tracing::BeginTracing("content,navigation"));

  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("a.com")));
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("b.com")));
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
}

class MetricsChromeBackForwardCacheBrowserTest
    : public ChromeBackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<std::string> {
 public:
  MetricsChromeBackForwardCacheBrowserTest() = default;
  ~MetricsChromeBackForwardCacheBrowserTest() override = default;

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // TODO(crbug.com/40188113): This test used an experiment param (which no
    // longer exists) to suppress the metrics send timer. If and when the test
    // is re-enabled, it should be updated to use a different mechanism.
    ChromeBackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }
};

// Flaky https://crbug.com/40188113
IN_PROC_BROWSER_TEST_P(MetricsChromeBackForwardCacheBrowserTest,
                       DISABLED_FirstInputDelay) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url2(embedded_test_server()->GetURL(
      (GetParam() == "SameSite") ? "a.com" : "b.com", "/title2.html"));

  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  internal::kHistogramFirstContentfulPaint),
              testing::IsEmpty());

  // 1) Navigate to url1.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url1));
  content::RenderFrameHostWrapper rfh_url1(current_frame_host());

  // Simulate mouse click. FirstInputDelay won't get updated immediately.
  content::SimulateMouseClickAt(web_contents(), 0,
                                blink::WebMouseEvent::Button::kLeft,
                                gfx::Point(100, 100));
  // Run arbitrary script and run tasks in the browser to ensure the input is
  // processed in the renderer.
  EXPECT_TRUE(content::ExecJs(rfh_url1.get(), "var foo = 42;"));
  base::RunLoop().RunUntilIdle();
  content::FetchHistogramsFromChildProcesses();
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 0);

  // 2) Immediately navigate to url2.
  if (GetParam() == "CrossSiteRendererInitiated") {
    EXPECT_TRUE(content::NavigateToURLFromRenderer(web_contents(), url2));
  } else {
    EXPECT_TRUE(content::NavigateToURL(web_contents(), url2));
  }

  // Ensure |rfh_url1| is cached.
  EXPECT_EQ(rfh_url1->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  content::FetchHistogramsFromChildProcesses();
  if (GetParam() != "CrossSiteBrowserInitiated" ||
      rfh_url1.get()->GetProcess() == current_frame_host()->GetProcess()) {
    // - For "SameSite" case, since the old and new RenderFrame share a process,
    // the metrics update will be sent to the browser during commit and won't
    // get ignored, successfully updating the FirstInputDelay histogram.
    // - For "CrossSiteRendererInitiated" case, FirstInputDelay was sent when
    // the renderer-initiated navigation started on the old frame.
    // - For "CrossSiteBrowserInitiated" case, if the old and new RenderFrame
    // share a process, the metrics update will be sent to the browser during
    // commit and won't get ignored, successfully updating the histogram.
    histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 1);
  } else {
    // Note that in some cases the metrics might flakily get updated in time,
    // before the browser changed the current RFH. So, we can neither expect it
    // to be 0 all the time or 1 all the time.
    // TODO(crbug.com/40157795): Support updating metrics consistently on
    // cross-RFH cross-process navigations.
  }
}

std::vector<std::string> MetricsChromeBackForwardCacheBrowserTestValues() {
  return {"SameSite", "CrossSiteRendererInitiated",
          "CrossSiteBrowserInitiated"};
}

INSTANTIATE_TEST_SUITE_P(
    All,
    MetricsChromeBackForwardCacheBrowserTest,
    testing::ValuesIn(MetricsChromeBackForwardCacheBrowserTestValues()),
    [](const testing::TestParamInfo<std::string>& i) { return i.param; });

// Ensure that BackForwardCache RenderFrameHosts are shown in the Task Manager.
IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       ShowMainFrameInTaskManager) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(embedded_test_server()->GetURL("a.com", "/title2.html"));
  const std::u16string expected_url_a_active_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_TAB_PREFIX, u"Title Of Awesomeness");
  const std::u16string expected_url_a_cached_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_BACK_FORWARD_CACHE_PREFIX, u"http://a.com/");

  GURL url_b(embedded_test_server()->GetURL("b.com", "/title3.html"));
  const std::u16string expected_url_b_active_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_TAB_PREFIX, u"Title Of More Awesomeness");
  const std::u16string expected_url_b_cached_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_BACK_FORWARD_CACHE_PREFIX, u"http://b.com/");

  auto tester =
      task_manager::TaskManagerTester::Create(base::RepeatingClosure());

  // 1) Navigate to |url_a|.
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // 2) Navigate to |url_b|.
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url_b));
  content::RenderFrameHostWrapper rfh_b(current_frame_host());

  // 3) Verify |url_a| is in the BackForwardCache.
  ASSERT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 4) Ensure both tabs show up in Task Manager.
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_b_active_title);
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_a_cached_title);
  EXPECT_THAT(tester->GetWebContentsTaskTitles(),
              ::testing::ElementsAre(expected_url_b_active_title,
                                     expected_url_a_cached_title));

  // 5) Navigate back to |url_a|.
  web_contents()->GetController().GoBack();
  ASSERT_TRUE(content::WaitForLoadStop(web_contents()));

  // 6) Verify |url_b| is in the BackForwardCache.
  ASSERT_EQ(rfh_b->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 7) Ensure both tabs show up in Task Manager.
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_a_active_title);
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_b_cached_title);
  EXPECT_THAT(tester->GetWebContentsTaskTitles(),
              ::testing::ElementsAre(expected_url_a_active_title,
                                     expected_url_b_cached_title));
}

// Ensure that BackForwardCache cross-site subframes are shown in the Task
// Manager.
IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       ShowCrossSiteOOPIFInTaskManager) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Load a page on a.com with cross-site iframes on b.com and c.com.
  GURL url_a(
      embedded_test_server()->GetURL("a.com", "/iframe_cross_site.html"));
  const std::u16string expected_url_a_cached_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_BACK_FORWARD_CACHE_PREFIX, u"http://a.com/");
  const std::u16string expected_url_a_cached_subframe_b_title =
      l10n_util::GetStringFUTF16(
          IDS_TASK_MANAGER_BACK_FORWARD_CACHE_SUBFRAME_PREFIX,
          u"http://b.com/");
  const std::u16string expected_url_a_cached_subframe_c_title =
      l10n_util::GetStringFUTF16(
          IDS_TASK_MANAGER_BACK_FORWARD_CACHE_SUBFRAME_PREFIX,
          u"http://c.com/");

  GURL url_b(embedded_test_server()->GetURL("b.com", "/title3.html"));
  const std::u16string expected_url_b_active_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_TAB_PREFIX, u"Title Of More Awesomeness");

  auto tester =
      task_manager::TaskManagerTester::Create(base::RepeatingClosure());

  // 1) Navigate to |url_a|.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_a));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // 2) Navigate to |url_b|.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_b));

  // 3) Verify |url_a| is in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 4) Ensure the subframe tasks for |url_a| show up in Task Manager.
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_b_active_title);
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_a_cached_title);
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_a_cached_subframe_b_title);
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_a_cached_subframe_c_title);
  EXPECT_THAT(tester->GetWebContentsTaskTitles(),
              ::testing::UnorderedElementsAre(
                  expected_url_b_active_title, expected_url_a_cached_title,
                  expected_url_a_cached_subframe_b_title,
                  expected_url_a_cached_subframe_c_title));
}

// Ensure that BackForwardCache same-site subframes are not shown in the Task
// Manager.
IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
                       DoNotShowSameSiteSubframeInTaskManager) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Load a page on a.com with an a.com iframe.
  GURL url_a(embedded_test_server()->GetURL("a.com", "/iframe.html"));
  const std::u16string expected_url_a_cached_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_BACK_FORWARD_CACHE_PREFIX, u"http://a.com/");

  GURL url_b(embedded_test_server()->GetURL("b.com", "/title3.html"));
  const std::u16string expected_url_b_active_title = l10n_util::GetStringFUTF16(
      IDS_TASK_MANAGER_TAB_PREFIX, u"Title Of More Awesomeness");

  auto tester =
      task_manager::TaskManagerTester::Create(base::RepeatingClosure());

  // 1) Navigate to |url_a|.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_a));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // 2) Navigate to |url_b|.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url_b));

  // 3) Verify |url_a| is in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 4) Ensure that only one task for |url_a| shows up in Task Manager.
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_b_active_title);
  task_manager::browsertest_util::WaitForTaskManagerRows(
      1, expected_url_a_cached_title);
  EXPECT_THAT(tester->GetWebContentsTaskTitles(),
              ::testing::ElementsAre(expected_url_b_active_title,
                                     expected_url_a_cached_title));
}

class ChromeBackForwardCacheBrowserWithEmbedTestBase
    : public ChromeBackForwardCacheBrowserTest {
 public:
  ChromeBackForwardCacheBrowserWithEmbedTestBase() = default;
  ~ChromeBackForwardCacheBrowserWithEmbedTestBase() override = default;

  static std::string GetSrcAttributeForTag(const std::string_view& tag) {
    return tag == "embed" ? "src" : "data";
  }

  void SetUpOnMainThread() override {
    ChromeBackForwardCacheBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 protected:
  void ExpectBlocklistedFeature(
      blink::scheduler::WebSchedulerTrackedFeature feature,
      base::Location location) {
    content::FetchHistogramsFromChildProcesses();
    base::HistogramBase::Sample32 sample(std::to_underlying(feature));
    base::Bucket expected_blocklisted(sample, 1);

    EXPECT_THAT(histogram_tester_->GetAllSamples(
                    "BackForwardCache.HistoryNavigationOutcome."
                    "BlocklistedFeature"),
                testing::Contains(expected_blocklisted))
        << location.ToString();

    EXPECT_THAT(histogram_tester_->GetAllSamples(
                    "BackForwardCache.AllSites.HistoryNavigationOutcome."
                    "BlocklistedFeature"),
                testing::Contains(expected_blocklisted))
        << location.ToString();
  }
};

class ChromeBackForwardCacheBrowserWithEmbedTest
    : public ChromeBackForwardCacheBrowserWithEmbedTestBase,
      public ::testing::WithParamInterface<std::string_view> {};

INSTANTIATE_TEST_SUITE_P(
    All,
    ChromeBackForwardCacheBrowserWithEmbedTest,
    testing::ValuesIn(kChromeBackForwardCacheBrowserWithEmbedTestValues),
    [](const testing::TestParamInfo<std::string_view>& i) {
      return std::string(i.param);
    });

#if BUILDFLAG(ENABLE_PDF)
class ChromeBackForwardCacheBrowserWithEmbedPdfTest
    : public ChromeBackForwardCacheBrowserWithEmbedTestBase,
      public ::testing::WithParamInterface<std::tuple<std::string_view, bool>> {
 public:
  void SetUpOnMainThread() override {
    ChromeBackForwardCacheBrowserWithEmbedTestBase::SetUpOnMainThread();

    if (UseOopif()) {
      factory_ = std::make_unique<pdf::TestMimeHandlerStreamManagerFactory>();
    }
  }

  const std::string_view& html_tag() const { return std::get<0>(GetParam()); }

  bool UseOopif() const { return std::get<1>(GetParam()); }

  pdf::TestMimeHandlerStreamManager* GetTestMimeHandlerStreamManager(
      content::WebContents* contents) {
    CHECK(UseOopif());
    return factory_->GetTestMimeHandlerStreamManager(contents);
  }

  std::vector<base::test::FeatureRefAndParams> GetEnabledFeaturesAndParams()
      const override {
    std::vector<base::test::FeatureRefAndParams> enabled =
        ChromeBackForwardCacheBrowserWithEmbedTestBase::
            GetEnabledFeaturesAndParams();
    if (UseOopif()) {
      enabled.push_back({chrome_pdf::features::kPdfOopif, {}});
    }
    return enabled;
  }

  std::vector<base::test::FeatureRef> GetDisabledFeatures() const override {
    std::vector<base::test::FeatureRef> disabled =
        ChromeBackForwardCacheBrowserWithEmbedTestBase::GetDisabledFeatures();
    if (!UseOopif()) {
      disabled.push_back(chrome_pdf::features::kPdfOopif);
    }
    return disabled;
  }

  void ExpectNotRestoredReason(base::Location location) {
    // Reasons to fail caching pages embedding the PDF viewer. For OOPIF PDF
    // viewer, caching is disabled because it's contains a plugin. For GuestView
    // PDF viewer, the PDF viewer contains an inner WebContents. These values
    // should be kept in sync with BackForwardCacheMetrics::NotRestoredReason.
    static constexpr uint8_t kReasonBlocklistedFeatures = 7;
    static constexpr uint8_t kReasonHaveInnerContents = 32;

    content::FetchHistogramsFromChildProcesses();
    base::HistogramBase::Sample32 sample = base::HistogramBase::Sample32(
        UseOopif() ? kReasonBlocklistedFeatures : kReasonHaveInnerContents);
    base::Bucket expected_not_restored(sample, 1);

    EXPECT_THAT(histogram_tester_->GetAllSamples(
                    "BackForwardCache.HistoryNavigationOutcome."
                    "NotRestoredReason"),
                testing::Contains(expected_not_restored))
        << location.ToString();

    EXPECT_THAT(histogram_tester_->GetAllSamples(
                    "BackForwardCache.AllSites.HistoryNavigationOutcome."
                    "NotRestoredReason"),
                testing::Contains(expected_not_restored))
        << location.ToString();
  }

 private:
  // `factory_` is necessary to create a `pdf::TestMimeHandlerStreamManager`
  // instance whenever a PDF loads.
  std::unique_ptr<pdf::TestMimeHandlerStreamManagerFactory> factory_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ChromeBackForwardCacheBrowserWithEmbedPdfTest,
    testing::Combine(
        testing::ValuesIn(kChromeBackForwardCacheBrowserWithEmbedTestValues),
        testing::Bool()),
    ChromeBackForwardCacheBrowserWithEmbedPdfTestPassToString());
#endif  // BUILDFLAG(ENABLE_PDF)

// TODO(crbug.com/40285326): This fails with the field trial testing config.
class ChromeBackForwardCacheBrowserWithEmbedTestNoTestingConfig
    : public ChromeBackForwardCacheBrowserWithEmbedTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ChromeBackForwardCacheBrowserWithEmbedTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch("disable-field-trial-config");
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ChromeBackForwardCacheBrowserWithEmbedTestNoTestingConfig,
    testing::ValuesIn(kChromeBackForwardCacheBrowserWithEmbedTestValues),
    [](const testing::TestParamInfo<std::string_view>& i) {
      return std::string(i.param);
    });

#if BUILDFLAG(ENABLE_PDF)
class ChromeBackForwardCacheBrowserWithEmbedPdfTestNoTestingConfig
    : public ChromeBackForwardCacheBrowserWithEmbedPdfTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ChromeBackForwardCacheBrowserWithEmbedPdfTest::SetUpCommandLine(
        command_line);
    command_line->AppendSwitch("disable-field-trial-config");
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ChromeBackForwardCacheBrowserWithEmbedPdfTestNoTestingConfig,
    testing::Combine(
        testing::ValuesIn(kChromeBackForwardCacheBrowserWithEmbedTestValues),
        testing::Bool()),
    ChromeBackForwardCacheBrowserWithEmbedPdfTestPassToString());
#endif  // BUILDFLAG(ENABLE_PDF)

IN_PROC_BROWSER_TEST_P(
    ChromeBackForwardCacheBrowserWithEmbedTestNoTestingConfig,
    DoesNotCachePageWithEmbeddedPlugin) {
  const auto tag = GetParam();
  const auto page_with_plugin =
      base::StrCat({"/back_forward_cache/page_with_", tag, "_plugin.html"});

  // Navigate to A, a page with embedded Pepper plugin.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(),
      embedded_test_server()->GetURL("a.com", page_with_plugin)));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // Navigate to B.
  bool will_change_rfh =
      rfh_a->ShouldChangeRenderFrameHostOnSameSiteNavigation();

  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title2.html")));

  // Verify A is NOT stored in the BackForwardCache.
  if (will_change_rfh) {
    EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
  } else {
    EXPECT_NE(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  // Navigate back to A.
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));
  // Verify A is not restored from BackForwardCache due to |kContainsPlugins|.
  ExpectBlocklistedFeature(
      blink::scheduler::WebSchedulerTrackedFeature::kContainsPlugins,
      FROM_HERE);
}

#if BUILDFLAG(ENABLE_PDF)
IN_PROC_BROWSER_TEST_P(
    ChromeBackForwardCacheBrowserWithEmbedPdfTestNoTestingConfig,
    DoesNotCachePageWithEmbeddedPdf) {
  const auto tag = html_tag();
  const auto page_with_pdf =
      base::StrCat({"/back_forward_cache/page_with_", tag, "_pdf.html"});

  // Navigate to A, a page with embedded PDF.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", page_with_pdf)));
  if (UseOopif()) {
    ASSERT_TRUE(GetTestMimeHandlerStreamManager(web_contents())
                    ->WaitUntilPdfLoadedInFirstChild());
  } else {
    pdf_extension_test_util::EnsurePDFHasLoadedOptions options{
        .pdf_element = std::string(tag)};
    ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoadedWithOptions(
        web_contents(), options));
  }
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // Navigate to B.
  bool will_change_rfh =
      rfh_a->ShouldChangeRenderFrameHostOnSameSiteNavigation();

  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title2.html")));

  // Verify A is NOT stored in the BackForwardCache.
  if (will_change_rfh) {
    EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
  } else {
    EXPECT_NE(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  // Navigate back to A.
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));

  // Verify A is not restored from BackForwardCache.
  ExpectNotRestoredReason(FROM_HERE);
}

// Flaky: crbug.com/40935990
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_WIN)
#define MAYBE_DoesNotCachePageWithEmbeddedPdfAppendedOnPageLoaded \
  DISABLED_DoesNotCachePageWithEmbeddedPdfAppendedOnPageLoaded
#else
#define MAYBE_DoesNotCachePageWithEmbeddedPdfAppendedOnPageLoaded \
  DoesNotCachePageWithEmbeddedPdfAppendedOnPageLoaded
#endif
IN_PROC_BROWSER_TEST_P(
    ChromeBackForwardCacheBrowserWithEmbedPdfTest,
    MAYBE_DoesNotCachePageWithEmbeddedPdfAppendedOnPageLoaded) {
  const auto tag = html_tag();

  // Navigate to A.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());
  //  Embed a PDF into A, and wait until PDF is loaded.
  EXPECT_EQ("success",
            content::EvalJs(rfh_a.get(), content::JsReplace(
                                             R"(
    new Promise(async resolve => {
      let el = document.createElement($1);
      el.type = 'application/pdf';
      el[$2] = '/pdf/test.pdf';
      el.onload = e => resolve("success");
      document.body.append(el);
    });
  )",
                                             tag, GetSrcAttributeForTag(tag))));
  if (UseOopif()) {
    // Wait for the PDF to fully load.
    ASSERT_TRUE(GetTestMimeHandlerStreamManager(web_contents())
                    ->WaitUntilPdfLoadedInFirstChild());
  }

  // Navigate to B.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title2.html")));

  // Verify A is NOT stored in the BackForwardCache.
  if (content::WillSameSiteNavigationChangeRenderFrameHosts(
          /*is_main_frame=*/true)) {
    EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
  } else {
    EXPECT_NE(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  //  Navigate back to A.
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));

  // Verify A is not restored from BackForwardCache.
  ExpectNotRestoredReason(FROM_HERE);
}
#endif  // BUILDFLAG(ENABLE_PDF)

IN_PROC_BROWSER_TEST_P(ChromeBackForwardCacheBrowserWithEmbedTest,
                       DoesCachePageWithEmbeddedHtml) {
  const auto tag = GetParam();
  const auto page_with_html =
      base::StrCat({"/back_forward_cache/page_with_", tag, "_html.html"});

  // Navigate to A, a page with embedded HTML.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", page_with_html)));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  // Navigate to B.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title2.html")));

  // Verify A is stored in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

#if BUILDFLAG(ENABLE_PDF)
// Flaky: crbug.com/40935990
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DoesNotCachePageWithEmbeddedHtmlMutatedIntoPdf \
  DISABLED_DoesNotCachePageWithEmbeddedHtmlMutatedIntoPdf
#else
#define MAYBE_DoesNotCachePageWithEmbeddedHtmlMutatedIntoPdf \
  DoesNotCachePageWithEmbeddedHtmlMutatedIntoPdf
#endif
IN_PROC_BROWSER_TEST_P(ChromeBackForwardCacheBrowserWithEmbedPdfTest,
                       MAYBE_DoesNotCachePageWithEmbeddedHtmlMutatedIntoPdf) {
  const auto tag = html_tag();
  const auto page_with_html =
      base::StrCat({"/back_forward_cache/page_with_", tag, "_html.html"});

  // Navigate to A, a page with embedded HTML.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", page_with_html)));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());
  //  Mutate the embed into PDF, and wait until PDF is loaded.
  EXPECT_EQ("success",
            content::EvalJs(rfh_a.get(), content::JsReplace(
                                             R"(
    new Promise(async resolve => {
      let el = document.getElementById($1);
      el.type = 'application/pdf';
      el[$2] = '/pdf/test.pdf';
      el.onload = e => resolve("success");
    });
  )",
                                             tag, GetSrcAttributeForTag(tag))));
  if (UseOopif()) {
    // Wait for the PDF to fully load.
    ASSERT_TRUE(GetTestMimeHandlerStreamManager(web_contents())
                    ->WaitUntilPdfLoadedInFirstChild());
  }

  bool will_change_rfh =
      rfh_a->ShouldChangeRenderFrameHostOnSameSiteNavigation();
  // Navigate to B.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title2.html")));

  // Verify A is NOT stored in the BackForwardCache.
  if (will_change_rfh) {
    EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
  } else {
    EXPECT_NE(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  // Navigate back to A.
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));

  // Verify A is not restored from BackForwardCache.
  ExpectNotRestoredReason(FROM_HERE);
}

IN_PROC_BROWSER_TEST_P(ChromeBackForwardCacheBrowserWithEmbedPdfTest,
                       DoesCachePageWithEmbeddedPdfMutatedIntoHtml) {
  const auto tag = html_tag();
  const auto page_with_pdf =
      base::StrCat({"/back_forward_cache/page_with_", tag, "_pdf.html"});

  // Navigate to A, a page with embedded PDF.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", page_with_pdf)));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());
  //  Mutate the embed into HTML, and wait until HTML is loaded.
  EXPECT_EQ("success",
            content::EvalJs(rfh_a.get(), content::JsReplace(
                                             R"(
    new Promise(async resolve => {
      let el = document.getElementById($1);
      el.type = 'text/html';
      el[$2] = '/title1.html';
      el.onload = e => resolve("success");
    });
  )",
                                             tag, GetSrcAttributeForTag(tag))));

  // Navigate to B.
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(), embedded_test_server()->GetURL("a.com", "/title2.html")));

  // Verify A is stored in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}
#endif  // BUILDFLAG(ENABLE_PDF)
