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

#include "content/browser/back_forward_cache_browsertest.h"

#include <climits>
#include <optional>
#include <string_view>
#include <unordered_map>

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/metrics_hashes.h"
#include "base/run_loop.h"
#include "base/system/sys_info.h"
#include "base/task/common/task_annotator.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_log.h"
#include "build/build_config.h"
#include "build/chromecast_buildflags.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/bad_message.h"
#include "content/browser/renderer_host/back_forward_cache_can_store_document_result.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/should_swap_browsing_instance.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/features.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/document_service.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.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/commit_message_delayer.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/text_input_test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/public/test/web_contents_observer_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_javascript_dialog_manager.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/filename_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/features.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/device_memory/approximated_device_memory.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "third_party/blink/public/common/switches.h"
#include "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom.h"
#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/render_accessibility.mojom.h"
#include "third_party/blink/public/mojom/script_source_location.mojom.h"

// This file has too many tests.
//
// Before adding new tests to this file, consider if they will fit better into
// one of the other back_forward_cache_*_browsertest.cc files or if there are
// enough new tests to justify a new file.

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

namespace content {

using NotRestoredReasons =
    BackForwardCacheCanStoreDocumentResult::NotRestoredReasons;
using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason;

namespace {

class DOMContentLoadedObserver : public WebContentsObserver {
 public:
  explicit DOMContentLoadedObserver(RenderFrameHostImpl* render_frame_host)
      : WebContentsObserver(
            WebContents::FromRenderFrameHost(render_frame_host)),
        render_frame_host_(render_frame_host) {}

  void DOMContentLoaded(RenderFrameHost* render_frame_host) override {
    if (render_frame_host_ == render_frame_host)
      run_loop_.Quit();
  }

  [[nodiscard]] bool Wait() {
    if (render_frame_host_->IsDOMContentLoaded())
      run_loop_.Quit();
    run_loop_.Run();
    return render_frame_host_->IsDOMContentLoaded();
  }

 private:
  raw_ptr<RenderFrameHostImpl> render_frame_host_;
  base::RunLoop run_loop_;
};

}  // namespace

bool WaitForDOMContentLoaded(RenderFrameHostImpl* rfh) {
  DOMContentLoadedObserver observer(rfh);
  return observer.Wait();
}

EvalJsResult GetLocalStorage(RenderFrameHostImpl* rfh, std::string key) {
  return EvalJs(rfh, JsReplace("localStorage.getItem($1)", key));
}

[[nodiscard]] bool WaitForLocalStorage(RenderFrameHostImpl* rfh,
                                       std::string key,
                                       std::string expected_value) {
  auto value = EvalJs(rfh, JsReplace(R"(
    new Promise((resolve) => {
      let key = $1;
      let expected_value = $2;
      if (localStorage.getItem(key) == expected_value) {
        resolve(localStorage.getItem(key));
        return;
      }
      let listener = window.addEventListener("storage", e => {
        if (e.storageArea == localStorage && e.key == key
                && e.newValue == expected_value) {
          resolve(localStorage.getItem(key));
          removeEventListener("storage", listener);
          return;
        }
      });
    });
    )",
                                     key, expected_value));
  return value == expected_value;
}

BackForwardCacheBrowserTest::BackForwardCacheBrowserTest() = default;

BackForwardCacheBrowserTest::~BackForwardCacheBrowserTest() {
  if (fail_for_unexpected_messages_while_cached_) {
    // If this is triggered, see MojoInterfaceName in
    // tools/metrics/histograms/metadata/navigation/enums.xml for which values
    // correspond which messages.
    std::vector<base::Bucket> samples = histogram_tester().GetAllSamples(
        "BackForwardCache.UnexpectedRendererToBrowserMessage."
        "InterfaceName");
    // TODO(crbug.com/40244391): Remove this.
    // This bucket corresponds to the LocalFrameHost interface. It is known to
    // be flaky due calls to `LocalFrameHost::DidFocusFrame()` after entering
    // BFCache. So we ignore it for now by removing it if it's present until we
    // can fix the root cause.
    // TODO(crbug.com/40925798): Remove this.
    // As above but `LocalMainFrameHost::DidFirstVisuallyNonEmptyPaint()`.
    std::erase_if(samples, [](base::Bucket bucket) {
      return bucket.min ==
                 static_cast<base::HistogramBase::Sample32>(base::HashMetricName(
                     blink::mojom::LocalFrameHost::Name_)) ||
             bucket.min ==
                 static_cast<base::HistogramBase::Sample32>(base::HashMetricName(
                     blink::mojom::LocalMainFrameHost::Name_));
    });

    EXPECT_THAT(samples, testing::ElementsAre());
  }
}

void BackForwardCacheBrowserTest::NotifyNotRestoredReasons(
    std::unique_ptr<BackForwardCacheCanStoreTreeResult> tree_result) {
  tree_result_ = std::move(tree_result);
}

void BackForwardCacheBrowserTest::SetUpCommandLine(
    base::CommandLine* command_line) {
  mock_cert_verifier_.SetUpCommandLine(command_line);

  command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
  command_line->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
  // TODO(sreejakshetty): Initialize ScopedFeatureLists from test constructor.
  EnableFeatureAndSetParams(features::kBackForwardCacheTimeToLiveControl,
                            "time_to_live_seconds", "3600");
  // Entry to the cache can be slow during testing and cause flakiness.
  DisableFeature(features::kBackForwardCacheEntryTimeout);
  EnableFeatureAndSetParams(features::kBackForwardCache,
                            "message_handling_when_cached", "log");
  EnableFeatureAndSetParams(
      blink::features::kLogUnexpectedIPCPostedToBackForwardCachedDocuments,
      "delay_before_tracking_ms", "0");
  // Allow unlimited network during tests. Override this if you want to test the
  // network limiting.
  EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable,
                            "max_buffered_bytes_per_process",
                            base::NumberToString(INT_MAX));
  EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable,
                            "grace_period_to_finish_loading_in_seconds",
                            base::NumberToString(INT_MAX));
  // Enable capturing not-restored-reasons tree.
  EnableFeatureAndSetParams(
      blink::features::kBackForwardCacheSendNotRestoredReasons, "", "");

  // Do not trigger DumpWithoutCrashing() for JavaScript execution.
  DisableFeature(blink::features::kBackForwardCacheDWCOnJavaScriptExecution);
#if BUILDFLAG(IS_ANDROID)
  EnableFeatureAndSetParams(features::kBackForwardCache,
                            "process_binding_strength", "NORMAL");
#endif
    // Allow BackForwardCache for all devices regardless of their memory.
    DisableFeature(features::kBackForwardCacheMemoryControls);
    // Disables BackForwardCache cache size overwritten by
    // `content::kBackForwardCacheSize`, as many browser tests here assume
    // specific or smaller cache size (e.g. 1) rather than 6.
    DisableFeature(kBackForwardCacheSize);

    // WebSQL is disabled by default as of M119 (crbug/695592). Enable feature
    // in tests during deprecation trial and enterprise policy support.
    EnableFeatureAndSetParams(blink::features::kWebSQLAccess, "", "");

    SetupFeaturesAndParameters();

    command_line->AppendSwitchASCII(
        switches::kAutoplayPolicy,
        switches::autoplay::kNoUserGestureRequiredPolicy);
    // Unfortunately needed for one test on slow bots, TextInputStateUpdated,
    // where deferred commits delays input too much.
    command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
}

void BackForwardCacheBrowserTest::SetUpInProcessBrowserTestFixture() {
  ContentBrowserTest::SetUpInProcessBrowserTestFixture();
  mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}

void BackForwardCacheBrowserTest::TearDownInProcessBrowserTestFixture() {
  ContentBrowserTest::TearDownInProcessBrowserTestFixture();
  mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}

void BackForwardCacheBrowserTest::SetupFeaturesAndParameters() {
  std::vector<base::test::FeatureRefAndParams> enabled_features;

  for (const auto& [feature_ref, params] : features_with_params_) {
    enabled_features.emplace_back(*feature_ref, params);
  }

  feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                              disabled_features_);
  vmodule_switches_.InitWithSwitches("back_forward_cache_impl=1");
}

void BackForwardCacheBrowserTest::EnableFeatureAndSetParams(
    const base::Feature& feature,
    std::string param_name,
    std::string param_value) {
  features_with_params_[feature][param_name] = param_value;
}

void BackForwardCacheBrowserTest::DisableFeature(const base::Feature& feature) {
  disabled_features_.push_back(feature);
}

void BackForwardCacheBrowserTest::SetUpOnMainThread() {
  mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
  host_resolver()->AddRule("*", "127.0.0.1");
  // TestAutoSetUkmRecorder's constructor requires a sequenced context.
  ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  histogram_tester_ = std::make_unique<base::HistogramTester>();
  ContentBrowserTest::SetUpOnMainThread();
}

void BackForwardCacheBrowserTest::TearDownOnMainThread() {
  ukm_recorder_.reset();
  ContentBrowserTest::TearDownOnMainThread();
}

WebContentsImpl* BackForwardCacheBrowserTest::web_contents() const {
  return static_cast<WebContentsImpl*>(shell()->web_contents());
}

RenderFrameHostImpl* BackForwardCacheBrowserTest::current_frame_host() {
  return web_contents()->GetPrimaryFrameTree().root()->current_frame_host();
}

RenderFrameHostManager*
BackForwardCacheBrowserTest::render_frame_host_manager() {
  return web_contents()->GetPrimaryFrameTree().root()->render_manager();
}

std::string BackForwardCacheBrowserTest::DepictFrameTree(FrameTreeNode* node) {
  return visualizer_.DepictFrameTree(node);
}

bool BackForwardCacheBrowserTest::HistogramContainsIntValue(
    base::HistogramBase::Sample32 sample,
    std::vector<base::Bucket> histogram_values) {
  return base::Contains(histogram_values, static_cast<int>(sample),
                        &base::Bucket::min);
}

void BackForwardCacheBrowserTest::EvictByJavaScript(RenderFrameHostImpl* rfh) {
  // Run JavaScript on a page in the back-forward cache. The page should be
  // evicted. As the frame is deleted, ExecJs returns false without executing.
  // Run without user gesture to prevent UpdateUserActivationState message
  // being sent back to browser.
  EXPECT_FALSE(
      ExecJs(rfh, "console.log('hi');", EXECUTE_SCRIPT_NO_USER_GESTURE));
}

void BackForwardCacheBrowserTest::StartRecordingEvents(
    RenderFrameHostImpl* rfh) {
  EXPECT_TRUE(ExecJs(rfh, R"(
      window.testObservedEvents = [];
      let event_list = [
        'visibilitychange',
        'pagehide',
        'pageshow',
        'freeze',
        'resume',
      ];
      for (event_name of event_list) {
        let result = event_name;
        window.addEventListener(event_name, event => {
          if (event.persisted)
            result += '.persisted';
          window.testObservedEvents.push('window.' + result);
        });
        document.addEventListener(event_name,
            () => window.testObservedEvents.push('document.' + result));
      }
    )"));
}

void BackForwardCacheBrowserTest::MatchEventList(RenderFrameHostImpl* rfh,
                                                 base::Value list,
                                                 base::Location location) {
  EXPECT_EQ(list, EvalJs(rfh, "window.testObservedEvents"))
      << location.ToString();
}

// Creates a minimal HTTPS server, accessible through https_server().
// Returns a pointer to the server.
net::EmbeddedTestServer* BackForwardCacheBrowserTest::CreateHttpsServer() {
  https_server_ = std::make_unique<net::EmbeddedTestServer>(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_->AddDefaultHandlers(GetTestDataFilePath());
  https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  return https_server();
}

net::EmbeddedTestServer* BackForwardCacheBrowserTest::https_server() {
  return https_server_.get();
}

// Do not fail this test if a message from a renderer arrives at the browser
// for a cached page.
void BackForwardCacheBrowserTest::DoNotFailForUnexpectedMessagesWhileCached() {
  fail_for_unexpected_messages_while_cached_ = false;
}

  // Navigates to a page at |page_url| with an img element with src set to
  // "image.png".
RenderFrameHostImpl* BackForwardCacheBrowserTest::NavigateToPageWithImage(
    const GURL& page_url) {
  EXPECT_TRUE(NavigateToURL(shell(), page_url));
  RenderFrameHostImpl* rfh = current_frame_host();
  // Wait for the document to load DOM to ensure that kLoading is not
  // one of the reasons why the document wasn't cached.
  EXPECT_TRUE(WaitForDOMContentLoaded(rfh));

  EXPECT_TRUE(ExecJs(rfh, R"(
      var image = document.createElement("img");
      image.src = "image.png";
      document.body.appendChild(image);

      var image_load_status = new Promise((resolve, reject) => {
        image.onload = () => { resolve("loaded"); }
        image.onerror = () => { resolve("error"); }
      });
    )"));
  return rfh;
}

void BackForwardCacheBrowserTest::AcquireKeyboardLock(
    RenderFrameHostImpl* rfh) {
  EXPECT_EQ(42, EvalJs(rfh, R"(
        new Promise(resolve => {
          navigator.keyboard.lock();
          resolve(42);
        });
      )"));
}

void BackForwardCacheBrowserTest::ReleaseKeyboardLock(
    RenderFrameHostImpl* rfh) {
  EXPECT_EQ(42, EvalJs(rfh, R"(
        new Promise(resolve => {
          navigator.keyboard.unlock();
          resolve(42);
        });
      )"));
}

void BackForwardCacheBrowserTest::NavigateAndBlock(GURL url,
                                                   int history_offset) {
  // Block the navigation with an error.
  std::unique_ptr<URLLoaderInterceptor> url_interceptor =
      URLLoaderInterceptor::SetupRequestFailForURL(url,
                                                   net::ERR_BLOCKED_BY_CLIENT);
  if (history_offset) {
    shell()->GoBackOrForward(history_offset);
  } else {
    shell()->LoadURL(url);
  }
  WaitForLoadStop(web_contents());
  ASSERT_EQ(current_frame_host()->GetLastCommittedURL(), url);
  ASSERT_TRUE(current_frame_host()->IsErrorDocument());
}

ReasonsMatcher BackForwardCacheBrowserTest::MatchesNotRestoredReasons(
    const std::optional<testing::Matcher<std::string>>& id,
    const std::optional<testing::Matcher<std::string>>& name,
    const std::optional<testing::Matcher<std::string>>& src,
    const std::vector<BlockingDetailsReasonsMatcher>& reasons,
    const std::optional<SameOriginMatcher>& same_origin_details) {
  // TODO(crbug.com/41496143) Make this matcher display human-friendly messages.
  return testing::Pointee(testing::AllOf(
      id.has_value()
          ? testing::Field(
                "id", &blink::mojom::BackForwardCacheNotRestoredReasons::id,
                testing::Optional(id.value()))
          : testing::Field(
                "id", &blink::mojom::BackForwardCacheNotRestoredReasons::id,
                std::optional<std::string>(std::nullopt)),
      name.has_value()
          ? testing::Field(
                "name", &blink::mojom::BackForwardCacheNotRestoredReasons::name,
                testing::Optional(name.value()))
          : testing::Field(
                "name", &blink::mojom::BackForwardCacheNotRestoredReasons::name,
                std::optional<std::string>(std::nullopt)),
      src.has_value()
          ? testing::Field(
                "src", &blink::mojom::BackForwardCacheNotRestoredReasons::src,
                testing::Optional(src.value()))
          : testing::Field(
                "src", &blink::mojom::BackForwardCacheNotRestoredReasons::src,
                std::optional<std::string>(std::nullopt)),
      testing::Field("reasons",
                     &blink::mojom::BackForwardCacheNotRestoredReasons::reasons,
                     testing::UnorderedElementsAreArray(reasons)),
      testing::Field(
          "same_origin_details",
          &blink::mojom::BackForwardCacheNotRestoredReasons::
              same_origin_details,
          same_origin_details.has_value()
              ? same_origin_details.value()
              : testing::Property(
                    "is_null",
                    &blink::mojom::SameOriginBfcacheNotRestoredDetailsPtr::
                        is_null,
                    true))));
}

SameOriginMatcher BackForwardCacheBrowserTest::MatchesSameOriginDetails(
    const testing::Matcher<GURL>& url,
    const std::vector<ReasonsMatcher>& children) {
  // TODO(crbug.com/41496143) Make this matcher display human-friendly messages.
  return testing::Pointee(testing::AllOf(
      testing::Field(
          "url", &blink::mojom::SameOriginBfcacheNotRestoredDetails::url, url),
      testing::Field(
          "children",
          &blink::mojom::SameOriginBfcacheNotRestoredDetails::children,
          testing::ElementsAreArray(children))));
}

BlockingDetailsReasonsMatcher
BackForwardCacheBrowserTest::MatchesDetailedReason(
    const testing::Matcher<std::string>& name,
    const std::optional<SourceLocationMatcher>& source) {
  // TODO(crbug.com/41496143) Make this matcher display human-friendly
  // messages.
  return testing::Pointee(testing::AllOf(
      testing::Field("name", &blink::mojom::BFCacheBlockingDetailedReason::name,
                     name),
      testing::Field(
          "source", &blink::mojom::BFCacheBlockingDetailedReason::source,
          source.has_value()
              ? source.value()
              : testing::Property(
                    "is_null", &blink::mojom::ScriptSourceLocationPtr::is_null,
                    true))));
}

BlockingDetailsMatcher BackForwardCacheBrowserTest::MatchesBlockingDetails(
    const std::optional<SourceLocationMatcher>& source) {
  // TODO(crbug.com/41496143) Make this matcher display human-friendly messages.
  return testing::Pointee(testing::Field(
      "source", &blink::mojom::BlockingDetails::source,
      source.has_value()
          ? source.value()
          : testing::Property("is_null",
                              &blink::mojom::ScriptSourceLocationPtr::is_null,
                              true)));
}

SourceLocationMatcher BackForwardCacheBrowserTest::MatchesSourceLocation(
    const testing::Matcher<GURL>& url,
    const testing::Matcher<std::string>& function_name,
    const testing::Matcher<uint64_t>& line_number,
    const testing::Matcher<uint64_t>& column_number) {
  // TODO(crbug.com/41496143) Make this matcher display human-friendly
  // messages.
  return testing::Pointee(testing::AllOf(
      testing::Field("url", &blink::mojom::ScriptSourceLocation::url, url),
      testing::Field("function_name",
                     &blink::mojom::ScriptSourceLocation::function_name,
                     function_name),
      testing::Field("line_number",
                     &blink::mojom::ScriptSourceLocation::line_number,
                     line_number),
      testing::Field("column_number",
                     &blink::mojom::ScriptSourceLocation::column_number,
                     column_number)));
}

void BackForwardCacheUnloadBrowserTest::SetUpCommandLine(
    base::CommandLine* command_line) {
  BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  scoped_feature_list_.InitAndEnableFeature(kBackForwardCacheUnloadAllowed);
}

std::initializer_list<RenderFrameHostImpl*> Elements(
    std::initializer_list<RenderFrameHostImpl*> t) {
  return t;
}

// Execute a custom callback when navigation is ready to commit. This is
// useful for simulating race conditions happening when a page enters the
// BackForwardCache and receive inflight messages sent when it wasn't frozen
// yet.
class ReadyToCommitNavigationCallback : public WebContentsObserver {
 public:
  ReadyToCommitNavigationCallback(
      WebContents* content,
      base::OnceCallback<void(NavigationHandle*)> callback)
      : WebContentsObserver(content), callback_(std::move(callback)) {}

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

 private:
  // WebContentsObserver:
  void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
    if (callback_)
      std::move(callback_).Run(navigation_handle);
  }

  base::OnceCallback<void(NavigationHandle*)> callback_;
};

class FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver {
 public:
  explicit FirstVisuallyNonEmptyPaintObserver(WebContents* contents)
      : WebContentsObserver(contents) {}
  void DidFirstVisuallyNonEmptyPaint() override {
    if (observed_)
      return;
    observed_ = true;
    run_loop_.Quit();
  }

  bool did_fire() const { return observed_; }

  void Wait() { run_loop_.Run(); }

 private:
  bool observed_ = false;
  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
};

void WaitForFirstVisuallyNonEmptyPaint(WebContents* contents) {
  if (contents->CompletedFirstVisuallyNonEmptyPaint())
    return;
  FirstVisuallyNonEmptyPaintObserver observer(contents);
  observer.Wait();
}

class ThemeColorObserver : public WebContentsObserver {
 public:
  explicit ThemeColorObserver(WebContents* contents)
      : WebContentsObserver(contents) {}

  // Can only be called once.
  [[nodiscard]] bool WaitUntilThemeColorChange() {
    CHECK(!loop_);
    loop_ = std::make_unique<base::RunLoop>();
    if (observed_) {
      return true;
    }
    loop_->Run();
    return observed_;
  }

  void DidChangeThemeColor() override {
    observed_ = true;
    if (loop_) {
      loop_->Quit();
    }
  }

  bool did_fire() const { return observed_; }

 private:
  std::unique_ptr<base::RunLoop> loop_;
  bool observed_ = false;
};

PageLifecycleStateManagerTestDelegate::PageLifecycleStateManagerTestDelegate(
    PageLifecycleStateManager* manager)
    : manager_(manager) {
  manager->SetDelegateForTesting(this);
}

PageLifecycleStateManagerTestDelegate::
    ~PageLifecycleStateManagerTestDelegate() {
  if (manager_)
    manager_->SetDelegateForTesting(nullptr);
}

bool PageLifecycleStateManagerTestDelegate::WaitForInBackForwardCacheAck() {
  DCHECK(manager_);
  if (manager_->last_acknowledged_state().is_in_back_forward_cache) {
    return true;
  }
  base::RunLoop loop;
  store_in_back_forward_cache_ack_received_ = loop.QuitClosure();
  loop.Run();
  return manager_->last_acknowledged_state().is_in_back_forward_cache;
}

void PageLifecycleStateManagerTestDelegate::OnStoreInBackForwardCacheSent(
    base::OnceClosure cb) {
  store_in_back_forward_cache_sent_ = std::move(cb);
}

void PageLifecycleStateManagerTestDelegate::OnDisableJsEvictionSent(
    base::OnceClosure cb) {
  disable_eviction_sent_ = std::move(cb);
}

void PageLifecycleStateManagerTestDelegate::OnRestoreFromBackForwardCacheSent(
    base::OnceClosure cb) {
  restore_from_back_forward_cache_sent_ = std::move(cb);
}

void PageLifecycleStateManagerTestDelegate::OnLastAcknowledgedStateChanged(
    const blink::mojom::PageLifecycleState& old_state,
    const blink::mojom::PageLifecycleState& new_state) {
  if (store_in_back_forward_cache_ack_received_ &&
      new_state.is_in_back_forward_cache)
    std::move(store_in_back_forward_cache_ack_received_).Run();
}

void PageLifecycleStateManagerTestDelegate::OnUpdateSentToRenderer(
    const blink::mojom::PageLifecycleState& new_state) {
  if (store_in_back_forward_cache_sent_ && new_state.is_in_back_forward_cache) {
    std::move(store_in_back_forward_cache_sent_).Run();
  }

  if (disable_eviction_sent_ && new_state.eviction_enabled == false) {
    std::move(disable_eviction_sent_).Run();
  }

  if (restore_from_back_forward_cache_sent_ &&
      !new_state.is_in_back_forward_cache) {
    std::move(restore_from_back_forward_cache_sent_).Run();
  }
}

void PageLifecycleStateManagerTestDelegate::OnDeleted() {
  manager_ = nullptr;
}

// Check the visible URL in the omnibox is properly updated when restoring a
// document from the BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VisibleURL) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Go to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

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

  // 3) Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(url_a, web_contents()->GetVisibleURL());

  // 4) Go forward to B.
  ASSERT_TRUE(HistoryGoForward(web_contents()));
  EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
}

// Test only 1 document is kept in the at a time BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CacheSizeLimitedToOneDocumentPerTab) {
  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_c(embedded_test_server()->GetURL("c.com", "/title1.html"));

  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  // BackForwardCache is empty.
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  // BackForwardCache contains only rfh_a.
  RenderFrameHostImpl* rfh_b = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);

  EXPECT_TRUE(NavigateToURL(shell(), url_c));
  // BackForwardCache contains only rfh_b.
  delete_observer_rfh_a.WaitUntilDeleted();
  EXPECT_FALSE(delete_observer_rfh_b.deleted());

  // If/when the cache size is increased, this can be tested iteratively, see
  // deleted code in: https://crrev.com/c/1782902.

  ASSERT_TRUE(HistoryGoToOffset(web_contents(), -2));
  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kCacheLimit},
                    {}, {}, {}, {}, FROM_HERE);
}

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

  GURL url_a(https_server()->GetURL("a.test", "/set-header?X-Foo: bar"));
  GURL url_b(https_server()->GetURL("b.test", "/title1.html"));

  // 1) Navigate to A.
  NavigationHandleObserver observer1(web_contents(), url_a);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
  EXPECT_TRUE(observer1.has_committed());
  EXPECT_EQ("bar", observer1.GetNormalizedResponseHeader("x-foo"));

  // 2) Navigate to B.
  NavigationHandleObserver observer2(web_contents(), url_b);
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  RenderFrameHostImpl* rfh_b = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  EXPECT_FALSE(rfh_b->IsInBackForwardCache());
  EXPECT_TRUE(observer2.has_committed());

  // 3) Go back to A.
  NavigationHandleObserver observer3(web_contents(), url_a);
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_FALSE(delete_observer_rfh_b.deleted());
  EXPECT_EQ(rfh_a, current_frame_host());
  EXPECT_FALSE(rfh_a->IsInBackForwardCache());
  EXPECT_TRUE(rfh_b->IsInBackForwardCache());
  EXPECT_TRUE(observer3.has_committed());
  EXPECT_EQ("bar", observer3.GetNormalizedResponseHeader("x-foo"));

  ExpectRestored(FROM_HERE);
}

void HighCacheSizeBackForwardCacheBrowserTest::SetUpCommandLine(
    base::CommandLine* command_line) {
  EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
                            base::NumberToString(kBackForwardCacheSize));
  BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
}

// Test documents are evicted from the BackForwardCache at some point.
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
                       CacheEvictionWithIncreasedCacheSize) {
  ASSERT_TRUE(embedded_test_server()->Start());

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

  EXPECT_TRUE(NavigateToURL(shell(), url_a));  // BackForwardCache size is 0.
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  EXPECT_TRUE(NavigateToURL(shell(), url_b));  // BackForwardCache size is 1.
  RenderFrameHostImpl* rfh_b = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);

  for (size_t i = 2; i < kBackForwardCacheSize; ++i) {
    EXPECT_TRUE(NavigateToURL(shell(), i % 2 ? url_b : url_a));
    // After |i+1| navigations, |i| documents went into the BackForwardCache.
    // When |i| is greater than the BackForwardCache size limit, they are
    // evicted:
    EXPECT_EQ(i >= kBackForwardCacheSize + 1, delete_observer_rfh_a.deleted());
    EXPECT_EQ(i >= kBackForwardCacheSize + 2, delete_observer_rfh_b.deleted());
  }
}

// Tests that evicting a page in between the time the back/forward cache
// NavigationRequest restore was created and when the NavigationRequest actually
// starts after finishing beforeunload won't result in a crash.
// See https://crbug.com/1218114.
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
                       EvictedWhileWaitingForBeforeUnload) {
  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"));
  GURL url_c(embedded_test_server()->GetURL("c.com", "/title3.html"));

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

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

  // 3) Navigate to C, which has a beforeunload handler that never finishes.
  EXPECT_TRUE(NavigateToURL(shell(), url_c));
  RenderFrameHostImplWrapper rfh_c(current_frame_host());
  EXPECT_TRUE(ExecJs(rfh_c.get(), R"(
    window.onbeforeunload = () => {
      while (true) {}
    }
  )"));
  // Both A & B are in the back/forward cache.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  EXPECT_TRUE(rfh_b->IsInBackForwardCache());

  // 4) Evict entry A. This will post a task that destroys all evicted entries
  // when it runs (task #1).
  DisableBFCacheForRFHForTesting(rfh_a->GetGlobalId());
  EXPECT_FALSE(rfh_a.IsDestroyed());
  EXPECT_TRUE(rfh_a->is_evicted_from_back_forward_cache());

  // 5) Trigger a back navigation to B. This will create a BFCache restore
  // navigation to B, but will wait for C's beforeunload handler to finish
  // running before continuing.
  // The BFCache entry will be evicted before the back navigation completes, so
  // the old navigation will be reset and a new navigation will be restarted.
  // This observer is waiting for the two navigation requests to complete.
  TestNavigationObserver observer(web_contents(),
                                  /* expected_number_of_navigations= */ 2,
                                  MessageLoopRunner::QuitMode::IMMEDIATE,
                                  /* ignore_uncommitted_navigations= */ false);
  web_contents()->GetController().GoBack();

  // 6) Post a task to run BeforeUnloadCompleted (task #2). This will continue
  // the BFCache restore navigation to B from step 5, which is currently waiting
  // for a BeforeUnloadCompleted call.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindLambdaForTesting([&]() {
        root->navigator().BeforeUnloadCompleted(
            root, /*proceed=*/true, base::TimeTicks::Now(),
            /*for_legacy=*/false, /*showed_dialog=*/false);
      }));

  // 7) Evict entry B. This will post a task (task #3) to restart the navigation
  // to B, and also another task (task #4) to destroy all evicted entries.
  DisableBFCacheForRFHForTesting(rfh_b->GetGlobalId());
  EXPECT_FALSE(rfh_b.IsDestroyed());
  EXPECT_TRUE(rfh_b->is_evicted_from_back_forward_cache());

  // 8) Wait until the back navigation to B finishes. This will run posted tasks
  // in order. So:
  // - Task #1 from step 4 will run and destroy all evicted entries. As both the
  // entries for A & B have been evicted, they are both destroyed.
  // - Task #2 from step 6 will run and continue the back/forward cache restore
  // NavigationRequest to B. However, it would notice that the entry for B is
  // now gone, and should handle it gracefully.
  // - Task #3 from step 7 to restart navigation to B runs, and should create a
  // NavigationRequest to replace the previous NavigationRequest to B.
  // - Task #4 from step 7 to destroy evicted entries runs and won't destroy
  // any entry since there's no longer any entry in the back/forward cache.
  observer.Wait();
  EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b);
  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
                         kDisableForRenderFrameHostCalled},
                    {}, {}, {RenderFrameHostDisabledForTestingReason()}, {},
                    FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       SubframeWithOngoingNavigationNotCached) {
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/hung");
  ASSERT_TRUE(embedded_test_server()->Start());

  // Navigate to a page with an iframe.
  TestNavigationObserver navigation_observer1(web_contents());
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/back_forward_cache/page_with_hung_iframe.html"));
  shell()->LoadURL(main_url);
  navigation_observer1.WaitForNavigationFinished();

  RenderFrameHostImpl* main_frame = current_frame_host();
  RenderFrameDeletedObserver frame_deleted_observer(main_frame);
  response.WaitForRequest();

  // Navigate away.
  TestNavigationObserver navigation_observer2(web_contents());
  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
  navigation_observer2.WaitForNavigationFinished();

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

// Only HTTP/HTTPS main document can enter the BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheHTTPDocumentOnly) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(CreateHttpsServer()->Start());

  GURL http_url(embedded_test_server()->GetURL("a.test", "/title1.html"));
  GURL https_url(https_server()->GetURL("a.test", "/title1.html"));
  GURL file_url = net::FilePathToFileURL(GetTestFilePath("", "title1.html"));
  GURL data_url = GURL("data:text/html,");
  GURL blank_url = GURL(url::kAboutBlankURL);
  GURL webui_url = GetWebUIURL("gpu");

  enum { STORED, DELETED };
  struct {
    int expectation;
    GURL url;
  } test_cases[] = {
      // Only document with HTTP/HTTPS URLs are allowed to enter the
      // BackForwardCache.
      {STORED, http_url},
      {STORED, https_url},

      // Others aren't allowed.
      {DELETED, file_url},
      {DELETED, data_url},
      {DELETED, webui_url},
      {DELETED, blank_url},
  };

  char hostname[] = "a.unique";
  for (auto& test_case : test_cases) {
    SCOPED_TRACE(testing::Message()
                 << std::endl
                 << "expectation = " << test_case.expectation << std::endl
                 << "url = " << test_case.url << std::endl);

    // 1) Navigate to.
    EXPECT_TRUE(NavigateToURL(shell(), test_case.url));
    RenderFrameHostImplWrapper rfh(current_frame_host());

    // 2) Navigate away.
    hostname[0]++;
    GURL reset_url(embedded_test_server()->GetURL(hostname, "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), reset_url));

    if (test_case.expectation == STORED) {
      EXPECT_FALSE(rfh.IsRenderFrameDeleted());
      EXPECT_TRUE(rfh->IsInBackForwardCache());
      continue;
    }

    if (rfh.get() == current_frame_host()) {
      // If the RenderFrameHost is reused, it won't be deleted, so don't wait
      // for deletion. Just check that it's not saved in the back-forward cache.
      EXPECT_FALSE(rfh.IsRenderFrameDeleted());
      EXPECT_FALSE(rfh->IsInBackForwardCache());
      continue;
    }

    // When the RenderFrameHost is not reused and it's not stored in the
    // back-forward cache, it will eventually be deleted.
    ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
  }
}

// Regression test for https://crbug.com/993337.
//
// A note about sharing BrowsingInstances and the BackForwardCache:
//
// We should never keep around more than one main frame that belongs to the same
// BrowsingInstance. When swapping two pages, when one is stored in the
// back-forward cache or one is restored from it, the current code expects the
// two to live in different BrowsingInstances.
//
// History navigation can recreate a page with the same BrowsingInstance as the
// one stored in the back-forward cache. This case must to be handled. When it
// happens, the back-forward cache page is evicted.
//
// Since cache eviction is asynchronous, it's is possible for two main frames
// belonging to the same BrowsingInstance to be alive for a brief period of time
// (the new page being navigated to, and a page in the cache, until it is
// destroyed asynchronously via eviction).
//
// The test below tests that the brief period of time where two main frames are
// alive in the same BrowsingInstance does not cause anything to blow up.

// TODO(crbug.com/1127979, crbug.com/1446206): Flaky on Linux, Windows and
// ChromeOS, iOS, and Mac.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_MAC) || BUILDFLAG(IS_IOS)
#define MAYBE_NavigateToTwoPagesOnSameSite DISABLED_NavigateToTwoPagesOnSameSite
#else
#define MAYBE_NavigateToTwoPagesOnSameSite NavigateToTwoPagesOnSameSite
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_NavigateToTwoPagesOnSameSite) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
  GURL url_b3(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A1.
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));

  // 2) Navigate to A2.
  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  RenderFrameHostImpl* rfh_a2 = current_frame_host();
  RenderFrameDeletedObserver delete_rfh_a2(current_frame_host());

  // 3) Navigate to B3.
  EXPECT_TRUE(NavigateToURL(shell(), url_b3));
  EXPECT_TRUE(rfh_a2->IsInBackForwardCache());
  RenderFrameHostImpl* rfh_b3 = current_frame_host();

  // 4) Do a history navigation back to A1.
  ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0));
  EXPECT_TRUE(rfh_b3->IsInBackForwardCache());

  // Note that the frame for A1 gets created before A2 is deleted from the
  // cache, so there will be a brief period where two the main frames (A1 and
  // A2) are alive in the same BrowsingInstance/SiteInstance, at the same time.
  // That is the scenario this test is covering. This used to cause a CHECK,
  // because the two main frames shared a single RenderViewHost (no longer the
  // case after https://crrev.com/c/1833616).

  // A2 should be evicted from the cache and asynchronously deleted, due to the
  // cache size limit (B3 took its place in the cache).
  delete_rfh_a2.WaitUntilDeleted();
}

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       NavigateToTwoPagesOnSameSiteWithSubframes) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // This test covers the same scenario as NavigateToTwoPagesOnSameSite, except
  // the pages contain subframes:
  // A1(B) -> A2(B(C)) -> D3 -> A1(B)
  //
  // The subframes shouldn't make a difference, so the expected behavior is the
  // same as NavigateToTwoPagesOnSameSite.
  GURL url_a1(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL url_a2(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b(c))"));
  GURL url_d3(embedded_test_server()->GetURL("d.com", "/title1.html"));

  // 1) Navigate to A1(B).
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));

  // 2) Navigate to A2(B(C)).
  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  RenderFrameHostImpl* rfh_a2 = current_frame_host();
  RenderFrameDeletedObserver delete_rfh_a2(current_frame_host());

  // 3) Navigate to D3.
  EXPECT_TRUE(NavigateToURL(shell(), url_d3));
  EXPECT_TRUE(rfh_a2->IsInBackForwardCache());
  RenderFrameHostImpl* rfh_d3 = current_frame_host();

  // 4) Do a history navigation back to A1(B).
  ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0));

  // D3 takes A2(B(C))'s place in the cache.
  EXPECT_TRUE(rfh_d3->IsInBackForwardCache());
  delete_rfh_a2.WaitUntilDeleted();
}

// Sub-frame doesn't transition from LifecycleStateImpl::kInBackForwardCache to
// LifecycleStateImpl::kRunningUnloadHandlers even when the sub-frame having
// unload handlers is being evicted from BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheUnloadBrowserTest,
                       SubframeWithUnloadHandler) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a.com(a.com)"));
  GURL child_url = embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a.com()");
  GURL url_2(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Navigate to |main_url|.
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImpl* main_rfh = current_frame_host();
  ASSERT_EQ(1U, main_rfh->child_count());
  RenderFrameHostImpl* child_rfh = main_rfh->child_at(0)->current_frame_host();
  RenderFrameDeletedObserver main_rfh_observer(main_rfh),
      child_rfh_observer(child_rfh);

  // 2) Add an unload handler to the child RFH.
  EXPECT_TRUE(ExecJs(child_rfh, "window.onunload = () => {} "));

  // 3) Navigate to |url_2|.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // 4) The previous main RFH and child RFH should be in the back-forward
  // cache.
  EXPECT_FALSE(main_rfh_observer.deleted());
  EXPECT_FALSE(child_rfh_observer.deleted());
  EXPECT_TRUE(main_rfh->IsInBackForwardCache());
  EXPECT_TRUE(child_rfh->IsInBackForwardCache());

  // Destruction of bfcached page happens after shutdown and it should not
  // trigger unload handlers and be destroyed directly.
}

// Do a same document navigation and make sure we do not fire the
// DidFirstVisuallyNonEmptyPaint again
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTest,
    DoesNotFireDidFirstVisuallyNonEmptyPaintForSameDocumentNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a_1(embedded_test_server()->GetURL(
      "a.com", "/accessibility/html/a-name.html"));
  GURL url_a_2(embedded_test_server()->GetURL(
      "a.com", "/accessibility/html/a-name.html#id"));

  EXPECT_TRUE(NavigateToURL(shell(), url_a_1));
  WaitForFirstVisuallyNonEmptyPaint(shell()->web_contents());

  FirstVisuallyNonEmptyPaintObserver observer(web_contents());
  EXPECT_TRUE(NavigateToURL(shell(), url_a_2));
  // Make sure the bfcache restore code does not fire the event during commit
  // navigation.
  EXPECT_FALSE(observer.did_fire());
  EXPECT_TRUE(web_contents()->CompletedFirstVisuallyNonEmptyPaint());
}

// Make sure we fire DidFirstVisuallyNonEmptyPaint when restoring from bf-cache.
// TODO(crbug.com/327195951): Re-enable this test
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache \
  DISABLED_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache
#else
#define MAYBE_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache \
  FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache
#endif
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTest,
    MAYBE_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

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

  // 2) Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_FALSE(delete_observer_rfh_a.deleted());
  ASSERT_TRUE(rfh_a->IsInBackForwardCache());
  WaitForFirstVisuallyNonEmptyPaint(shell()->web_contents());

  // 3) Navigate to back to A.
  FirstVisuallyNonEmptyPaintObserver observer(web_contents());
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  // Make sure the bfcache restore code does fire the event during commit
  // navigation.
  EXPECT_TRUE(web_contents()->CompletedFirstVisuallyNonEmptyPaint());
  EXPECT_TRUE(observer.did_fire());
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_SetsThemeColorWhenRestoredFromCache \
  DISABLED_SetsThemeColorWhenRestoredFromCache
#else
#define MAYBE_SetsThemeColorWhenRestoredFromCache \
  SetsThemeColorWhenRestoredFromCache
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_SetsThemeColorWhenRestoredFromCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/theme_color.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  WaitForFirstVisuallyNonEmptyPaint(web_contents());
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  EXPECT_EQ(web_contents()->GetThemeColor(), 0xFFFF0000u);

  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  WaitForFirstVisuallyNonEmptyPaint(web_contents());
  ASSERT_TRUE(rfh_a->IsInBackForwardCache());
  EXPECT_EQ(web_contents()->GetThemeColor(), std::nullopt);

  ThemeColorObserver observer(web_contents());
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ASSERT_TRUE(observer.WaitUntilThemeColorChange());
  EXPECT_EQ(web_contents()->GetThemeColor(), 0xFFFF0000u);
}

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

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
  EXPECT_EQ(web_contents()->GetContentsMimeType(), "text/html");

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

  // Go back to A, which restores A from bfcache. ContentsMimeType should be
  // restored as well.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());
  ExpectRestored(FROM_HERE);
  EXPECT_EQ(web_contents()->GetContentsMimeType(), "text/html");
}

// Check BackForwardCache is enabled and works for devices with very low memory.
// Navigate from A -> B and go back.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       BackForwardCacheEnabledOnLowMemoryDevices) {
  // Set device physical memory to 10 MB.
  blink::ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(10);
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

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

  // 2) Navigate to B. A should be in BackForwardCache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  RenderFrameHostImpl* rfh_b = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 3) Go back to A. B should be in BackForwardCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_FALSE(delete_observer_rfh_b.deleted());
  EXPECT_TRUE(rfh_b->IsInBackForwardCache());
}

// Test for functionality of memory controls in back-forward cache for low
// memory devices.
class BackForwardCacheBrowserTestForLowMemoryDevices
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);

    // Set the value of memory threshold more than the physical memory and check
    // if back-forward cache is disabled or not.
    std::string memory_threshold =
        base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() + 1);
    scoped_feature_list_.InitWithFeaturesAndParameters(
        {{features::kBackForwardCacheMemoryControls,
          {{"memory_threshold_for_back_forward_cache_in_mb",
            memory_threshold}}},
         {blink::features::kLoadingTasksUnfreezable, {}}},
        {});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Ensure that the BackForwardCache trial is not activated as expected on
// low-memory devices.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForLowMemoryDevices,
                       DisableBFCacheForLowEndDevices) {
  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"));

  // Ensure that the BackForwardCache trial starts inactive.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));

  EXPECT_FALSE(IsBackForwardCacheEnabled());

  // Ensure that we do not activate the BackForwardCache trial when querying
  // bfcache status.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));

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

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

  // 3) A shouldn't be stored in back-forward cache because the physical
  // memory is less than the memory threshold.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 4) Go back to check the
  // NotRestoredReasons.kBackForwardCacheDisabledByLowMemory is recorded when
  // the memory is less than the threshold value.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  ExpectNotRestored(
      {
          BackForwardCacheMetrics::NotRestoredReason::kBackForwardCacheDisabled,
          BackForwardCacheMetrics::NotRestoredReason::
              kBackForwardCacheDisabledByLowMemory,
      },
      {}, {}, {}, {}, FROM_HERE);

  // Ensure that the BackForwardCache trial still hasn't been activated.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));
}

// Trigger network reqeuests, then navigate from A to B, then go back.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForLowMemoryDevices,
                       DisableBFCacheForLowEndDevices_NetworkRequests) {
  net::test_server::ControllableHttpResponse image_response(
      embedded_test_server(), "/image.png");
  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"));

  // Ensure that the trials starts inactive.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(
          blink::features::kLoadingTasksUnfreezable)
          ->trial_name()));

  EXPECT_FALSE(IsBackForwardCacheEnabled());

  // Ensure that we do not activate the trials for kBackForwardCache and
  // kLoadingTasksUnfreezable when querying bfcache or unfreezable loading tasks
  // status.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(
          blink::features::kLoadingTasksUnfreezable)
          ->trial_name()));

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

  // Request for an image and send a response to trigger loading code. This is
  // to ensure kLoadingTasksUnfreezable won't trigger bfcache activation.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
      var image = document.createElement("img");
      image.src = "image.png";
      document.body.appendChild(image);
    )"));
  image_response.WaitForRequest();
  image_response.Send(net::HTTP_OK, "image/png");
  image_response.Send("image_body");
  image_response.Done();

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

  // 3) A shouldn't be stored in back-forward cache because the physical
  // memory is less than the memory threshold.
  delete_observer_rfh_a.WaitUntilDeleted();

  // Nothing is recorded when the memory is less than the threshold value.
  ExpectOutcomeDidNotChange(FROM_HERE);
  ExpectNotRestoredDidNotChange(FROM_HERE);

  // Ensure that the trials still haven't been activated.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(
          blink::features::kLoadingTasksUnfreezable)
          ->trial_name()));
}

// Test for functionality of memory controls in back-forward cache for high
// memory devices.
class BackForwardCacheBrowserTestForHighMemoryDevices
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);

    // Set the value of memory threshold less than the physical memory and check
    // if back-forward cache is enabled or not.
    std::string memory_threshold =
        base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() - 1);
    scoped_feature_list_.InitWithFeaturesAndParameters(
        {{features::kBackForwardCacheMemoryControls,
          {{"memory_threshold_for_back_forward_cache_in_mb",
            memory_threshold}}},
         {blink::features::kLoadingTasksUnfreezable, {}}},
        {});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Ensure that the BackForwardCache trial got activated as expected on
// high-memory devices when the BackForwardCache feature is enabled.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForHighMemoryDevices,
                       EnableBFCacheForHighMemoryDevices) {
  // Ensure that the BackForwardCache trial starts active on high-memory devices
  // when the BackForwardCache feature is enabled, because
  // IsBackForwardCacheEnabled() got queried already before the test starts.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));

  EXPECT_TRUE(IsBackForwardCacheEnabled());

  // Ensure that the BackForwardCache trial stays active after querying
  // IsBackForwardCacheEnabled().
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));

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

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

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

  // 3) A should be stored in back-forward cache because the physical memory is
  // greater than the memory threshold.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Ensure that the BackForwardCache trial stays active.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));
}

// Trigger network reqeuests, then navigate from A to B, then go back.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForHighMemoryDevices,
                       EnableBFCacheForHighMemoryDevices_NetworkRequests) {
  net::test_server::ControllableHttpResponse image_response(
      embedded_test_server(), "/image.png");
  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"));

  // Ensure that back-forward cache flag is enabled and the trial is active.
  EXPECT_TRUE(IsBackForwardCacheEnabled());
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));

  // Ensure that the LoadingTasksUnfreezable trials starts as inactive.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(
          blink::features::kLoadingTasksUnfreezable)
          ->trial_name()));

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

  // Request for an image and send a response to trigger loading code.
  EXPECT_TRUE(ExecJs(rfh_a, R"(
      var image = document.createElement("img");
      image.src = "image.png";
      document.body.appendChild(image);
    )"));
  image_response.WaitForRequest();
  image_response.Send(net::HTTP_OK, "image/png");
  image_response.Send("image_body");
  image_response.Done();

  // The loading code activates the LoadingTasksUnfreezable trial.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(
          blink::features::kLoadingTasksUnfreezable)
          ->trial_name()));

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

  // 3) A should be stored in back-forward cache because the physical memory is
  // greater than the memory threshold.
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // Ensure that the trials stay activated.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(features::kBackForwardCache)
          ->trial_name()));
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
      base::FeatureList::GetFieldTrial(
          blink::features::kLoadingTasksUnfreezable)
          ->trial_name()));
}

// Tests for high memory devices that have the BackForwardCache feature flag
// disabled.
class BackForwardCacheBrowserTestForHighMemoryDevicesWithBFCacheDisabled
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);

    // Set the value of memory threshold less than the physical memory and check
    // if back-forward cache is enabled or not.
    std::string memory_threshold =
        base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() - 1);
    scoped_feature_list_.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {{features::kBackForwardCacheMemoryControls,
          {{"memory_threshold_for_back_forward_cache_in_mb",
            memory_threshold}}},
         {blink::features::kLoadingTasksUnfreezable, {}}},
        /*disabled_features=*/
        {features::kBackForwardCache});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTestForHighMemoryDevicesWithBFCacheDisabled,
    HighMemoryDevicesWithBFacheDisabled) {
  // Ensure that IsBackForwardCacheEnabled() returns false, because the
  // BackForwardCache feature is disabled.
  EXPECT_FALSE(IsBackForwardCacheEnabled());

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

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

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

  // 3) A shouldn't be stored in back-forward cache because the BackForwardCache
  // feature is disabled.
  delete_observer_rfh_a.WaitUntilDeleted();

  // 4) Go back to check that only kBackForwardCacheDisabled is recorded.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  ExpectNotRestored(
      {
          BackForwardCacheMetrics::NotRestoredReason::kBackForwardCacheDisabled,
      },
      {}, {}, {}, {}, FROM_HERE);
}

// Start an inifite dialogs in JS, yielding after each. The first dialog should
// be dismissed by navigation. The later dialogs should be handled gracefully
// and not appear while in BFCache. Finally, when the page comes out of BFCache,
// dialogs should appear again.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       CanUseCacheWhenPageAlertsInTimeoutLoop) {
  ASSERT_TRUE(embedded_test_server()->Start());

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

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

  AppModalDialogWaiter dialog_waiter(shell());

  EXPECT_TRUE(ExecJs(rfh_a, R"(
    function alertLoop() {
      setTimeout(alertLoop, 0);
      window.alert("alert");
    }
    // Don't block this script.
    setTimeout(alertLoop, 0);
  )"));

  dialog_waiter.Wait();

  // Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  RenderFrameHostImpl* rfh_b = current_frame_host();

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

  dialog_waiter.Restart();

  // Go back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());
  EXPECT_FALSE(rfh_a->IsInBackForwardCache());

  // The page should still be requesting dialogs in a loop. Wait for one to be
  // requested.
  dialog_waiter.Wait();
}

// UnloadOldFrame will clear all dialogs. We test that further requests for
// dialogs coming from JS do not result in the creation of a dialog. This test
// posts some dialog creation JS to the render from inside the
// CommitNavigationCallback task. This JS is then able to post a task back to
// the renders to show a dialog. By the time this task runs, we the
// RenderFrameHostImpl's is_active() should be false.
//
// This test is not perfect, it can pass simply because the renderer thread does
// not run the JS in time. Ideally it would block until the renderer posts the
// request for a dialog but it's possible to do that without creating a nested
// message loop and if we do that, we risk processing the dialog request.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       DialogsCancelledAndSuppressedWhenCached) {
  ASSERT_TRUE(embedded_test_server()->Start());

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

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

  // Let's us know whether the following callback ran. Not strictly necessary
  // since it really should run.
  bool posted_dialog_js = false;
  // Create a callback that will be called during the DidCommitNavigation task.
  WillEnterBackForwardCacheCallbackForTesting
      will_enter_back_forward_cache_callback =
          base::BindLambdaForTesting([&]() {
            // Post a dialog, it should not result in a dialog being created.
            ExecuteScriptAsync(rfh_a, R"(window.alert("alert");)");
            posted_dialog_js = true;
          });
  rfh_a->render_view_host()->SetWillEnterBackForwardCacheCallbackForTesting(
      will_enter_back_forward_cache_callback);

  AppModalDialogWaiter dialog_waiter(shell());

  // Try show another dialog. It should work.
  ExecuteScriptAsync(rfh_a, R"(window.alert("alert");)");
  dialog_waiter.Wait();

  dialog_waiter.Restart();

  // Navigate to B.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  RenderFrameHostImpl* rfh_b = current_frame_host();

  ASSERT_FALSE(delete_observer_rfh_a.deleted());
  ASSERT_THAT(rfh_a, InBackForwardCache());
  ASSERT_NE(rfh_a, rfh_b);
  // Test that the JS was run and that it didn't result in a dialog.
  ASSERT_TRUE(posted_dialog_js);
  ASSERT_FALSE(dialog_waiter.WasDialogRequestedCallbackCalled());

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

  EXPECT_EQ(rfh_a, current_frame_host());
  EXPECT_FALSE(rfh_a->IsInBackForwardCache());

  // Try show another dialog. It should work.
  ExecuteScriptAsync(rfh_a, R"(window.alert("alert");)");
  dialog_waiter.Wait();
}

// Tests that pagehide handlers of the old RFH are run for bfcached pages even
// if the page is already hidden (and visibilitychange won't run).
// Disabled on Linux and Win because of flakiness, see crbug.com/40165901.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
#define MAYBE_PagehideRunsWhenPageIsHidden DISABLED_PagehideRunsWhenPageIsHidden
#else
#define MAYBE_PagehideRunsWhenPageIsHidden PagehideRunsWhenPageIsHidden
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_PagehideRunsWhenPageIsHidden) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
  GURL url_3(embedded_test_server()->GetURL("a.com", "/title2.html"));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 1) Navigate to |url_1| and hide the tab.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImplWrapper main_frame_1(web_contents->GetPrimaryMainFrame());
  // We need to set it to Visibility::VISIBLE first in case this is the first
  // time the visibility is updated.
  web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
  web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
  EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());

  // Create a pagehide handler that sets item "pagehide_storage" and a
  // visibilitychange handler that sets item "visibilitychange_storage" in
  // localStorage.
  EXPECT_TRUE(ExecJs(main_frame_1.get(),
                     R"(
    localStorage.setItem('pagehide_storage', 'not_dispatched');
    var dispatched_pagehide = false;
    window.onpagehide = function(e) {
      if (dispatched_pagehide) {
        // We shouldn't dispatch pagehide more than once.
        localStorage.setItem('pagehide_storage', 'dispatched_more_than_once');
      } else if (!e.persisted) {
        localStorage.setItem('pagehide_storage', 'wrong_persisted');
      } else {
        localStorage.setItem('pagehide_storage', 'dispatched_once');
      }
      dispatched_pagehide = true;
    }
    localStorage.setItem('visibilitychange_storage', 'not_dispatched');
    document.onvisibilitychange = function(e) {
      localStorage.setItem('visibilitychange_storage',
        'should_not_be_dispatched');
    }
  )"));
  // |visibilitychange_storage| should be set to its initial correct value.
  EXPECT_EQ("not_dispatched",
            GetLocalStorage(main_frame_1.get(), "visibilitychange_storage"));

  // 2) Navigate cross-site to |url_2|. We need to navigate cross-site to make
  // sure we won't run pagehide and visibilitychange during new page's commit,
  // which is tested in ProactivelySwapBrowsingInstancesSameSiteTest.
  EXPECT_TRUE(NavigateToURL(shell(), url_2));

  // |main_frame_1| should be in the back-forward cache.
  EXPECT_TRUE(main_frame_1->IsInBackForwardCache());

  // 3) Navigate to |url_3| which is same-origin with |url_1|, so we can check
  // the localStorage values.
  EXPECT_TRUE(NavigateToURL(shell(), url_3));
  RenderFrameHostImpl* main_frame_3 = web_contents->GetPrimaryMainFrame();

  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
  // are set correctly.
  EXPECT_TRUE(
      WaitForLocalStorage(main_frame_3, "pagehide_storage", "dispatched_once"));
  EXPECT_TRUE(WaitForLocalStorage(main_frame_3, "visibilitychange_storage",
                                  "not_dispatched"));
}

// Tests that we're getting the correct TextInputState and focus updates when a
// page enters the back-forward cache and when it gets restored.
// TODO(b/324570785): Re-enable the test for Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_TextInputStateUpdated DISABLED_TextInputStateUpdated
#else
#define MAYBE_TextInputStateUpdated TextInputStateUpdated
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_TextInputStateUpdated) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));

  // 1) Navigate to |url_1| and add a text input with "foo" as the value.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImpl* rfh_1 = current_frame_host();
  EXPECT_TRUE(ExecJs(rfh_1,
                     "document.title='bfcached';"
                     "var input = document.createElement('input');"
                     "input.setAttribute('type', 'text');"
                     "input.setAttribute('value', 'foo');"
                     "document.body.appendChild(input);"
                     "var focusCount = 0;"
                     "var blurCount = 0;"
                     "input.onfocus = () => { focusCount++;};"
                     "input.onblur = () => { blurCount++; };"));

  {
    TextInputManagerTypeObserver type_observer(web_contents(),
                                               ui::TEXT_INPUT_TYPE_TEXT);
    TextInputManagerValueObserver value_observer(web_contents(), "foo");
    // 2) Press tab key to focus the <input>, and verify the type & value.
    SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, false, false, false);
    type_observer.Wait();
    value_observer.Wait();

    EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 1);
    EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 0);
  }

  {
    TextInputManagerTester tester(web_contents());
    TextInputManagerValueObserver value_observer(web_contents(), "A");
    // 3) Press the "A" key to change the text input value. This should notify
    // the browser that the text input value has changed.
    SimulateKeyPress(web_contents(), ui::DomKey::FromCharacter('A'),
                     ui::DomCode::US_A, ui::VKEY_A, false, false, false, false);
    value_observer.Wait();

    EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 1);
    EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 0);
  }

  {
    TextInputManagerTypeObserver type_observer(web_contents(),
                                               ui::TEXT_INPUT_TYPE_NONE);
    // 4) Navigating to |url_2| should reset type to TEXT_INPUT_TYPE_NONE.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    type_observer.Wait();
    // |rfh_1| should get into the back-forward cache.
    EXPECT_TRUE(rfh_1->IsInBackForwardCache());
    EXPECT_EQ(current_frame_host(), web_contents()->GetFocusedFrame());
    EXPECT_NE(rfh_1, web_contents()->GetFocusedFrame());
  }

  {
    // 5) Navigating back to |url_1|, we shouldn't restore the focus to the
    // text input, but |rfh_1| will be focused again as we will restore focus
    // to main frame after navigation.
    ASSERT_TRUE(HistoryGoBack(web_contents()));

    EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 1);
    EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 1);
  }

  {
    TextInputManagerTypeObserver type_observer(web_contents(),
                                               ui::TEXT_INPUT_TYPE_TEXT);
    TextInputManagerValueObserver value_observer(web_contents(), "A");
    // 6) Press tab key to focus the <input> again. Note that we need to press
    // the tab key twice here, because the last "tab focus" point was the
    // <input> element. The first tab key press would focus on the UI/url bar,
    // then the second tab key would go back to the <input>.
    SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, false, false, false);
    SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, false, false, false);
    type_observer.Wait();
    value_observer.Wait();

    EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 2);
    EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 1);
  }
}

#if (BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID))
#define MAYBE_SubframeTextInputStateUpdated DISABLED_SubframeTextInputStateUpdated
#else
#define MAYBE_SubframeTextInputStateUpdated SubframeTextInputStateUpdated
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_SubframeTextInputStateUpdated) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_1(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b(a))"));
  GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));

  // 1) Navigate to |url_1| and add a text input with "foo" as the value in the
  // a.com subframe.
  EXPECT_TRUE(NavigateToURL(shell(), url_1));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
  RenderFrameHostImpl* rfh_subframe_a =
      rfh_b->child_at(0)->current_frame_host();
  EXPECT_TRUE(ExecJs(rfh_subframe_a,
                     "var input = document.createElement('input');"
                     "input.setAttribute('type', 'text');"
                     "input.setAttribute('value', 'foo');"
                     "document.body.appendChild(input);"
                     "var focusCount = 0;"
                     "var blurCount = 0;"
                     "input.onfocus = () => { focusCount++;};"
                     "input.onblur = () => { blurCount++; };"));

  {
    TextInputManagerTypeObserver type_observer(web_contents(),
                                               ui::TEXT_INPUT_TYPE_TEXT);
    TextInputManagerValueObserver value_observer(web_contents(), "foo");
    // 2) Press tab key to focus the <input>, and verify the type & value.
    SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, false, false, false);
    type_observer.Wait();
    value_observer.Wait();

    EXPECT_EQ(rfh_subframe_a, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 1);
    EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 0);
  }

  {
    TextInputManagerTester tester(web_contents());
    TextInputManagerValueObserver value_observer(web_contents(), "A");
    // 3) Press the "A" key to change the text input value. This should notify
    // the browser that the text input value has changed.
    SimulateKeyPress(web_contents(), ui::DomKey::FromCharacter('A'),
                     ui::DomCode::US_A, ui::VKEY_A, false, false, false, false);
    value_observer.Wait();

    EXPECT_EQ(rfh_subframe_a, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 1);
    EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 0);
  }

  {
    TextInputManagerTypeObserver type_observer(web_contents(),
                                               ui::TEXT_INPUT_TYPE_NONE);
    // 4) Navigating to |url_2| should reset type to TEXT_INPUT_TYPE_NONE and
    // changed focus to the new page's main frame.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    type_observer.Wait();

    // |rfh_a| and its subframes should get into the back-forward cache.
    EXPECT_TRUE(rfh_a->IsInBackForwardCache());
    EXPECT_TRUE(rfh_b->IsInBackForwardCache());
    EXPECT_TRUE(rfh_subframe_a->IsInBackForwardCache());
    EXPECT_NE(rfh_subframe_a, web_contents()->GetFocusedFrame());
  }

  {
    // 5) Navigating back to |url_1|, we shouldn't restore the focus to the
    // text input in the subframe (we will focus on the main frame |rfh_a|
    // instead).
    ASSERT_TRUE(HistoryGoBack(web_contents()));

    EXPECT_EQ(rfh_a, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 1);
    EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 1);
  }

  {
    TextInputManagerTypeObserver type_observer(web_contents(),
                                               ui::TEXT_INPUT_TYPE_TEXT);
    TextInputManagerValueObserver value_observer(web_contents(), "A");
    // 6) Press tab key to focus the <input> again.
    SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, false, false, false);
    type_observer.Wait();
    value_observer.Wait();

    EXPECT_EQ(rfh_subframe_a, web_contents()->GetFocusedFrame());
    EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 2);
    EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 1);
  }
}

// Tests that trying to focus on a BFCached cross-site iframe won't crash.
// See https://crbug.com/1250218.
// TODO(crbug.com/40856039): Flaky on linux tsan
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_FocusSameSiteSubframeOnPagehide \
  DISABLED_FocusSameSiteSubframeOnPagehide
#else
#define MAYBE_FocusSameSiteSubframeOnPagehide FocusSameSiteSubframeOnPagehide
#endif
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       MAYBE_FocusSameSiteSubframeOnPagehide) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(
      embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
  GURL main_url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));

  // 1) Navigate to a page with a same-site iframe.
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper rfh_1(current_frame_host());
  EXPECT_EQ(rfh_1.get(), web_contents()->GetFocusedFrame());

  // 2) Navigate away from the page while trying to focus the subframe on
  // pagehide. The DidFocusFrame IPC should arrive after the page gets into
  // BFCache and should be ignored by the browser. The focus after navigation
  // should go to the new main frame.
  EXPECT_TRUE(ExecJs(rfh_1.get(), R"(
    window.onpagehide = function(e) {
      document.getElementById("test_iframe").focus();
  })"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
  EXPECT_TRUE(rfh_1->IsInBackForwardCache());
  EXPECT_NE(rfh_1.get(), web_contents()->GetFocusedFrame());
  EXPECT_EQ(current_frame_host(), web_contents()->GetFocusedFrame());

  // 3) Navigate back to the page. The focus should be on the main frame.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_1.get(), web_contents()->GetFocusedFrame());
  ExpectRestored(FROM_HERE);
}

// Tests that trying to focus on a BFCached cross-site iframe won't crash.
// See https://crbug.com/1250218.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       FocusCrossSiteSubframeOnPagehide) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL main_url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));

  // 1) Navigate to a page with a cross-site iframe.
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper rfh_1(current_frame_host());
  EXPECT_EQ(rfh_1.get(), web_contents()->GetFocusedFrame());

  // 2) Navigate away from the page while trying to focus the subframe on
  // pagehide. The DidFocusFrame IPC should arrive after the page gets into
  // BFCache and should be ignored by the browser. The focus after navigation
  // should go to the new main frame.
  EXPECT_TRUE(ExecJs(rfh_1.get(), R"(
    window.onpagehide = function(e) {
      document.getElementById("child-0").focus();
    })"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
  EXPECT_TRUE(rfh_1->IsInBackForwardCache());
  EXPECT_NE(rfh_1.get(), web_contents()->GetFocusedFrame());

  // 3) Navigate back to the page. The focus should be on the original page's
  // main frame.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_1.get(), current_frame_host());
  ExpectRestored(FROM_HERE);
}

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

  GURL url_a(embedded_test_server()->GetURL(
      "a.com",
      "/set-header?"
      "Content-Security-Policy: frame-src 'none'"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

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

  // Check that CSP was set.
  {
    const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
        current_frame_host()
            ->policy_container_host()
            ->policies()
            .content_security_policies;
    EXPECT_EQ(1u, root_csp.size());
    EXPECT_EQ("frame-src 'none'", root_csp[0]->header->header_value);
  }

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

  // 3) Navigate back and expect that the CSP headers are present on the main
  // frame.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(rfh_a, current_frame_host());
  ExpectRestored(FROM_HERE);

  // Check that CSP was restored.
  {
    const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
        current_frame_host()
            ->policy_container_host()
            ->policies()
            .content_security_policies;
    EXPECT_EQ(1u, root_csp.size());
    EXPECT_EQ("frame-src 'none'", root_csp[0]->header->header_value);
  }
}

// Check that sandboxed documents are cached and won't lose their sandbox flags
// after restoration.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CspSandbox) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_a(
      embedded_test_server()->GetURL("a.com",
                                     "/set-header?"
                                     "Content-Security-Policy: sandbox"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A, which should set CSP.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
  {
    const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
        current_frame_host()
            ->policy_container_host()
            ->policies()
            .content_security_policies;
    ASSERT_EQ(1u, root_csp.size());
    ASSERT_EQ("sandbox", root_csp[0]->header->header_value);
    ASSERT_EQ(network::mojom::WebSandboxFlags::kAll,
              current_frame_host()->active_sandbox_flags());
  }

  // 2) Navigate to B. Expect the previous RenderFrameHost to enter the bfcache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  {
    const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
        current_frame_host()
            ->policy_container_host()
            ->policies()
            .content_security_policies;
    ASSERT_EQ(0u, root_csp.size());
    ASSERT_EQ(network::mojom::WebSandboxFlags::kNone,
              current_frame_host()->active_sandbox_flags());
  }

  // 3) Navigate back and expect the page to be restored, with the correct
  // CSP and sandbox flags.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_FALSE(delete_observer_rfh_a.deleted());
  EXPECT_EQ(current_frame_host(), rfh_a);
  {
    const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
        current_frame_host()
            ->policy_container_host()
            ->policies()
            .content_security_policies;
    ASSERT_EQ(1u, root_csp.size());
    ASSERT_EQ("sandbox", root_csp[0]->header->header_value);
    ASSERT_EQ(network::mojom::WebSandboxFlags::kAll,
              current_frame_host()->active_sandbox_flags());
  }
}

// Check that about:blank is not cached.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, AboutBlankWillNotBeCached) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Navigate to about:blank.
  GURL blank_url(url::kAboutBlankURL);
  EXPECT_TRUE(NavigateToURL(shell(), blank_url));
  RenderFrameHostImplWrapper rfh_blank(current_frame_host());

  // 2) Navigate to a.com.
  GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  // 3) Navigate back to about:blank.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // This about:blank document does not have a SiteInstance and then loading a
  // page on it doesn't swap the browsing instance.

  if (ShouldCreateNewHostForAllFrames()) {
    EXPECT_TRUE(rfh_blank.WaitUntilRenderFrameDeleted());
    ExpectNotRestored(
        {
            BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK,
            BackForwardCacheMetrics::NotRestoredReason::kSchemeNotHTTPOrHTTPS,
            BackForwardCacheMetrics::NotRestoredReason::
                kBrowsingInstanceNotSwapped,
        },
        {}, {ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite}, {}, {},
        FROM_HERE);

  } else {
    EXPECT_FALSE(rfh_blank->IsInBackForwardCache());
    ExpectNotRestored(
        {
            BackForwardCacheMetrics::NotRestoredReason::
                kBrowsingInstanceNotSwapped,
        },
        {}, {ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite}, {}, {},
        FROM_HERE);
  }
}

// Check that browsing instances are not swapped when a navigation redirects
// toward the last committed URL and the reasons are recorded correctly.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, RedirectToSelf) {
  ASSERT_TRUE(embedded_test_server()->Start());
  NavigationControllerImpl& controller = web_contents()->GetController();

  // 1) Navigate to a.com/empty.html.
  GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  EXPECT_EQ(1, controller.GetEntryCount());
  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  // 2) Navigate to the same page by redirection.
  GURL url_a2(embedded_test_server()->GetURL(
      "a.com", "/server-redirect-301?" + url_a.spec()));
  EXPECT_TRUE(NavigateToURL(shell(), url_a2, url_a));
  RenderFrameHostImplWrapper rfh_b(current_frame_host());
  EXPECT_EQ(2, controller.GetEntryCount());

  if (ShouldCreateNewHostForAllFrames()) {
    EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
  } else {
    EXPECT_FALSE(rfh_a->IsInBackForwardCache());
    EXPECT_TRUE(rfh_a->GetSiteInstance()->IsRelatedSiteInstance(
        rfh_b->GetSiteInstance()));
  }

  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  // 3) Navigate back to the previous page.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(2, controller.GetEntryCount());
  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  // TODO(crbug.com/40760515): Investigate whether these navigation results are
  // expected.
  ExpectNotRestored(
      {
          BackForwardCacheMetrics::NotRestoredReason::
              kBrowsingInstanceNotSwapped,
      },
      {}, {ShouldSwapBrowsingInstance::kNo_SameUrlNavigation}, {}, {},
      FROM_HERE);
}

// Check that reloading doesn't affect the back-forward cache usage.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ReloadDoesntAffectCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  NavigationControllerImpl& controller = web_contents()->GetController();

  // 1) Navigate to a.com.
  GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  EXPECT_EQ(1, controller.GetEntryCount());
  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  // 2) Navigate to b.com.
  GURL url_b(embedded_test_server()->GetURL("b.com", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_EQ(2, controller.GetEntryCount());
  EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());

  // 3) Go back to a.com and reload.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(2, controller.GetEntryCount());
  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  ExpectRestored(FROM_HERE);

  // 4) Reload the tab.
  web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  EXPECT_EQ(2, controller.GetEntryCount());
  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  // By reloading the tab, ShouldSwapBrowsingInstance::
  // kNo_AlreadyHasMatchingBrowsingInstance is set once. This should be reset
  // when the navigation 4)'s commit finishes and should not prevent putting the
  // page into the back-forward cache.
  //
  // Note that SetBrowsingInstanceSwapResult might not be called for every
  // navigation because we might not get to this point for some navigations,
  // e.g. if the navigation uses a pre-existing RenderFrameHost and SiteInstance
  // for navigation.
  //
  // TODO(crbug.com/40747698): Tie BrowsingInstanceSwapResult to
  // NavigationRequest instead and move the SetBrowsingInstanceSwapResult call
  // for navigations to happen at commit time instead.

  // 5) Go forward to b.com and reload.
  ASSERT_TRUE(HistoryGoForward(web_contents()));
  EXPECT_EQ(2, controller.GetEntryCount());
  EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());

  // The page loaded at B) is correctly cached and restored. Reloading doesn't
  // affect the cache usage.
  ExpectRestored(FROM_HERE);

  // 6) Go back to a.com.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  EXPECT_EQ(2, controller.GetEntryCount());
  EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());

  // The page loaded at 3) is correctly cached and restored. Reloading doesn't
  // affect the cache usage.
  ExpectRestored(FROM_HERE);
}

// Regression test for crbug.com/1183313. Checks that CommitNavigationParam's
// |has_user_gesture| value reflects the gesture from the latest navigation
// after the commit finished.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheBrowserTest,
    SameDocumentNavAfterRestoringDocumentLoadedWithUserGesture) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_a_foo(embedded_test_server()->GetURL("a.com", "/title1.html#foo"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  NavigationControllerImpl& controller = web_contents()->GetController();
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  // Initial navigation (so that we can initiate a navigation from renderer).
  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  // 1) Navigate to A with user gesture.
  {
    FrameNavigateParamsCapturer params_capturer(root);
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_a));
    params_capturer.Wait();
    EXPECT_TRUE(params_capturer.has_user_gesture());
    EXPECT_TRUE(root->current_frame_host()
                    ->last_committed_common_params_has_user_gesture());
  }
  RenderFrameHostImpl* rfh_a = current_frame_host();

  // 2) Navigate to B. A should be stored in the back-forward cache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
  EXPECT_FALSE(root->current_frame_host()
                   ->last_committed_common_params_has_user_gesture());

  // 3) GoBack to A. RenderFrameHost of A should be restored from the
  // back-forward cache, and "has_user_gesture" is set to false correctly.
  // Note that since this is a back-forward cache restore we create the
  // DidCommitProvisionalLoadParams completely in the browser, so we got the
  // correct value from the latest navigation. However, we did not update the
  // renderer's navigation-related values, so the renderer's DocumentLoader
  // still thinks the last "gesture" value is "true", which will get corrected
  // on the next navigation.
  {
    FrameNavigateParamsCapturer params_capturer(root);
    controller.GoBack();
    params_capturer.Wait();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(rfh_a, current_frame_host());
    // The navigation doesn't have user gesture.
    EXPECT_FALSE(params_capturer.has_user_gesture());
    EXPECT_FALSE(root->current_frame_host()
                     ->last_committed_common_params_has_user_gesture());
  }

  // 4) Same-document navigation to A#foo without user gesture. At this point
  // we will update the renderer's DocumentLoader's latest gesture value to
  // "no user gesture", and we'll get the correct gesture value in
  // DidCommitProvisionalLoadParams.
  {
    FrameNavigateParamsCapturer params_capturer(root);
    EXPECT_TRUE(
        NavigateToURLFromRendererWithoutUserGesture(shell(), url_a_foo));
    params_capturer.Wait();
    // The navigation doesn't have user gesture.
    EXPECT_FALSE(params_capturer.has_user_gesture());
    EXPECT_FALSE(root->current_frame_host()
                     ->last_committed_common_params_has_user_gesture());
  }
}

testing::Matcher<BackForwardCacheCanStoreTreeResult> MatchesTreeResult(
    testing::Matcher<bool> same_origin,
    GURL url) {
  return testing::AllOf(
      testing::Property("IsSameOrigin",
                        &BackForwardCacheCanStoreTreeResult::IsSameOrigin,
                        same_origin),
      testing::Property("GetUrl", &BackForwardCacheCanStoreTreeResult::GetUrl,
                        url));
}

RenderFrameHostImpl* ChildFrame(RenderFrameHostImpl* rfh, int child_index) {
  return rfh->child_at(child_index)->current_frame_host();
}

// Verifies that the reasons match those given and no others.
testing::Matcher<BackForwardCacheCanStoreDocumentResult>
BackForwardCacheBrowserTest::MatchesDocumentResult(
    testing::Matcher<NotRestoredReasons> not_stored,
    BlockListedFeatures block_listed) {
  return testing::AllOf(
      testing::Property(
          "not_restored_reasons",
          &BackForwardCacheCanStoreDocumentResult::not_restored_reasons,
          not_stored),
      testing::Property(
          "blocklisted_features",
          &BackForwardCacheCanStoreDocumentResult::blocklisted_features,
          block_listed),
      testing::Property(
          "disabled_reasons",
          &BackForwardCacheCanStoreDocumentResult::disabled_reasons,
          BackForwardCacheCanStoreDocumentResult::DisabledReasonsMap()),
      testing::Property(
          "disallow_activation_reasons",
          &BackForwardCacheCanStoreDocumentResult::disallow_activation_reasons,
          std::set<uint64_t>()));
}

// Check the contents of the BackForwardCacheCanStoreTreeResult of a page.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, TreeResultFeatureUsage) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(a, b, c)"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to a(a, b, c).
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh(current_frame_host());

  // 2) Add a blocking feature to the main frame A and the sub frame B.
  current_frame_host()
      ->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
  current_frame_host()
      ->child_at(1)
      ->current_frame_host()
      ->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();

  GURL url_subframe_a = ChildFrame(rfh.get(), 0)->GetLastCommittedURL();
  GURL url_subframe_b = ChildFrame(rfh.get(), 1)->GetLastCommittedURL();
  GURL url_subframe_c = ChildFrame(rfh.get(), 2)->GetLastCommittedURL();

  // 3) Initialize the reasons tree and navigate away to ensure that everything
  // from the old frame has been destroyed.
  BackForwardCacheCanStoreDocumentResultWithTree can_store_result =
      web_contents()
          ->GetController()
          .GetBackForwardCache()
          .GetCurrentBackForwardCacheEligibility(rfh.get());
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());

  // 4) Check IsSameOrigin() and GetUrl().
  // a
  EXPECT_THAT(*can_store_result.tree_reasons,
              MatchesTreeResult(/*same_origin=*/true,
                                /*url=*/url_a));
  // a->a
  EXPECT_THAT(*can_store_result.tree_reasons->GetChildren().at(0),
              MatchesTreeResult(/*same_origin=*/true,
                                /*url=*/url_subframe_a));
  // a->b
  EXPECT_THAT(*can_store_result.tree_reasons->GetChildren().at(1),
              MatchesTreeResult(/*same_origin=*/false,
                                /*url=*/url_subframe_b));
  // a->c
  EXPECT_THAT(*can_store_result.tree_reasons->GetChildren().at(2),
              MatchesTreeResult(/*same_origin=*/false,
                                /*url=*/url_subframe_c));

  // 5) Check that the blocking reasons match.
  // a
  EXPECT_THAT(can_store_result.tree_reasons->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
                  BlockListedFeatures(
                      {blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
  // a->a
  EXPECT_THAT(
      can_store_result.tree_reasons->GetChildren().at(0)->GetDocumentResult(),
      MatchesDocumentResult(NotRestoredReasons(),
                            BlockListedFeatures(BlockListedFeatures())));
  // a->b
  EXPECT_THAT(
      can_store_result.tree_reasons->GetChildren().at(1)->GetDocumentResult(),
      MatchesDocumentResult(
          NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
          BlockListedFeatures(
              {blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
  // a->c
  EXPECT_THAT(
      can_store_result.tree_reasons->GetChildren().at(2)->GetDocumentResult(),
      MatchesDocumentResult(NotRestoredReasons(),
                            BlockListedFeatures(BlockListedFeatures())));
}

// Check the contents of the BackForwardCacheCanStoreTreeResult of a page when
// it is evicted.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       TreeResultEvictionMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to a.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate to B and evict A by JavaScript execution.
  ASSERT_TRUE(NavigateToURL(shell(), url_b));
  EvictByJavaScript(rfh_a.get());
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

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

// Check the contents of the BackForwardCacheCanStoreTreeResult of a page when
// its subframe is evicted.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       TreeResultEvictionSubFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  RenderFrameHostImplWrapper rfh_b(
      current_frame_host()->child_at(0)->current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate to C and evict A's subframe B by JavaScript execution.
  ASSERT_TRUE(NavigateToURL(shell(), url_c));
  EvictByJavaScript(rfh_b.get());
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {},
                    FROM_HERE);
  // Main frame result in the tree is empty.
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures()));
  // Subframe result in the tree contains the reason.
  EXPECT_THAT(GetTreeResult()->GetChildren().at(0)->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
                  BlockListedFeatures()));
}

// Check the contents of the BackForwardCacheCanStoreTreeResult of a page when
// its subframe's subframe is evicted.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                       TreeResultEvictionSubFramesSubframe) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b(c))"));
  GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html"));

  // 1) Navigate to A.
  ASSERT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImplWrapper rfh_a(current_frame_host());
  RenderFrameHostImplWrapper rfh_c(current_frame_host()
                                       ->child_at(0)
                                       ->current_frame_host()
                                       ->child_at(0)
                                       ->current_frame_host());
  rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);

  // 2) Navigate to D and evict C by JavaScript execution.
  ASSERT_TRUE(NavigateToURL(shell(), url_d));
  EvictByJavaScript(rfh_c.get());
  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());

  // 3) Go back to A.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {},
                    FROM_HERE);
  // Main frame result in the tree is empty.
  EXPECT_THAT(
      GetTreeResult()->GetDocumentResult(),
      MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures()));
  // The first level subframe result in the tree is empty.
  EXPECT_THAT(
      GetTreeResult()->GetChildren().at(0)->GetDocumentResult(),
      MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures()));
  // The second level subframe result in the tree contains the reason.
  EXPECT_THAT(GetTreeResult()
                  ->GetChildren()
                  .at(0)
                  ->GetChildren()
                  .at(0)
                  ->GetDocumentResult(),
              MatchesDocumentResult(
                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
                  BlockListedFeatures()));
}

void BackForwardCacheBrowserTest::InstallUnloadHandlerOnMainFrame() {
  EXPECT_TRUE(ExecJs(current_frame_host(), R"(
      localStorage["unload_run_count"] = 0;
      window.onunload = () => {
        localStorage["unload_run_count"] =
            1 + parseInt(localStorage["unload_run_count"]);
      };
    )"));
  EXPECT_EQ("0", GetUnloadRunCount());
}

void BackForwardCacheBrowserTest::InstallUnloadHandlerOnSubFrame() {
  TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
  EXPECT_TRUE(ExecJs(current_frame_host(), R"(
      const iframeElement = document.createElement("iframe");
      iframeElement.src = "%s";
      document.body.appendChild(iframeElement);
    )"));
  navigation_observer.Wait();
  RenderFrameHostImpl* subframe_render_frame_host =
      current_frame_host()->child_at(0)->current_frame_host();
  EXPECT_TRUE(ExecJs(subframe_render_frame_host, R"(
      localStorage["unload_run_count"] = 0;
      window.onunload = () => {
        localStorage["unload_run_count"] =
            1 + parseInt(localStorage["unload_run_count"]);
      };
    )"));
  EXPECT_EQ("0", GetUnloadRunCount());
}

EvalJsResult BackForwardCacheBrowserTest::GetUnloadRunCount() {
  return GetLocalStorage(current_frame_host(), "unload_run_count");
}

bool BackForwardCacheBrowserTest::AddBlocklistedFeature(RenderFrameHost* rfh) {
  // Add kDummy as blocking feature.
  RenderFrameHostImplWrapper rfh_a(rfh);
  rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
  return true;
}

void BackForwardCacheBrowserTest::ExpectNotRestoredDueToBlocklistedFeature(
    base::Location location) {
  ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                    {blink::scheduler::WebSchedulerTrackedFeature::kDummy}, {},
                    {}, {}, location);
}

const ukm::TestAutoSetUkmRecorder& BackForwardCacheBrowserTest::ukm_recorder() {
  return *ukm_recorder_;
}

const base::HistogramTester& BackForwardCacheBrowserTest::histogram_tester() {
  return *histogram_tester_;
}

// Ensure that psges with unload are only allowed to enter back/forward cache by
// default on Android.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, UnloadAllowedFlag) {
#if BUILDFLAG(IS_ANDROID)
  ASSERT_TRUE(BackForwardCacheImpl::IsUnloadAllowed());
#else
  ASSERT_FALSE(BackForwardCacheImpl::IsUnloadAllowed());
#endif
}

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

  // Navigate to a page that contains a blocklisted feature.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));

  RenderFrameHostWrapper rfh(current_frame_host());

  ASSERT_TRUE(AddBlocklistedFeature(rfh.get()));

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

  // The page with the unsupported feature should be deleted (not cached).
  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());

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

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

  // Navigate to a page with an iframe that contains a blocklisted feature.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL(
                   "a.com", "/cross_site_iframe_factory.html?a(b)")));

  RenderFrameHostWrapper rfh(
      current_frame_host()->child_at(0)->current_frame_host());

  ASSERT_TRUE(AddBlocklistedFeature(rfh.get()));

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

  // The page with the unsupported feature should be deleted (not cached).
  ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());

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

class BackForwardCacheBrowserUnloadHandlerTest
    : public BackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<
          std::tuple<bool, bool, bool, TestFrameType>> {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    if (IsUnloadAllowed()) {
      EnableFeatureAndSetParams(kBackForwardCacheUnloadAllowed, "", "");
    } else {
      DisableFeature(kBackForwardCacheUnloadAllowed);
    }
    if (IsUnloadBlocklisted()) {
      EnableFeatureAndSetParams(blink::features::kUnloadBlocklisted, "", "");
    } else {
      DisableFeature(blink::features::kUnloadBlocklisted);
    }
    if (IsUnloadDeprecationOptedOut()) {
      EnableFeatureAndSetParams(blink::features::kDeprecateUnloadOptOut, "",
                                "");
    } else {
      DisableFeature(blink::features::kDeprecateUnloadOptOut);
    }

    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

  bool IsUnloadAllowed() { return std::get<0>(GetParam()); }
  bool IsUnloadBlocklisted() { return std::get<1>(GetParam()); }
  bool IsUnloadDeprecationOptedOut() { return std::get<2>(GetParam()); }

  TestFrameType GetTestFrameType() { return std::get<3>(GetParam()); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Ensure that unload handlers in main frames and subframes block caching,
// depending on unload deprecation status and OS.
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserUnloadHandlerTest,
                       UnloadHandlerPresent) {
  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) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  BackForwardCacheCanStoreDocumentResult::NotRestoredReasons
      expected_blocking_reasons;
  std::vector<blink::scheduler::WebSchedulerTrackedFeature>
      expected_blocklisted_reason;
  if (IsUnloadBlocklisted()) {
    expected_blocking_reasons.Put(
        BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures);
    expected_blocklisted_reason.push_back(
        blink::scheduler::WebSchedulerTrackedFeature::kUnloadHandler);
  }
  switch (GetTestFrameType()) {
    case content::TestFrameType::kMainFrame:
      InstallUnloadHandlerOnMainFrame();
      expected_blocking_reasons.Put(BackForwardCacheMetrics::NotRestoredReason::
                                        kUnloadHandlerExistsInMainFrame);
      break;
    case content::TestFrameType::kSubFrame:
      InstallUnloadHandlerOnSubFrame();
      expected_blocking_reasons.Put(BackForwardCacheMetrics::NotRestoredReason::
                                        kUnloadHandlerExistsInSubFrame);
      break;
    default:
      NOTREACHED();
  }

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

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

  bool unload_never_blocks = IsUnloadAllowed();
  bool unload_deprecated_and_not_opted_out =
      (base::FeatureList::IsEnabled(network::features::kDeprecateUnload) &&
       !IsUnloadDeprecationOptedOut());
  if (unload_never_blocks || unload_deprecated_and_not_opted_out) {
    // Pages with unload handlers are eligible for bfcache only if it is
    // specifically allowed (happens on Android). Also, when unload is
    // deprecated and `kDeprecateUnloadOptOut` doesn't override it, unload
    // handlers cannot be installed so there should be no blocker for BFCache.
    ExpectRestored(FROM_HERE);
    EXPECT_EQ("0", GetUnloadRunCount());
  } else {
    ExpectNotRestored(expected_blocking_reasons, expected_blocklisted_reason,
                      {}, {}, {}, FROM_HERE);
    EXPECT_EQ("1", GetUnloadRunCount());
  }
}

// First param: whether unload is allowed or not.
// Second one: whether unload is blocklisted or not.
// Third one: whether it's opted out from unload deprecation or not.
INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheBrowserUnloadHandlerTest,
    ::testing::Combine(::testing::Bool(),
                       ::testing::Bool(),
                       ::testing::Bool(),
                       ::testing::Values(TestFrameType::kMainFrame,
                                         TestFrameType::kSubFrame)));

IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DisableForRenderFrameHost) {
  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) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostWrapper rfh_wrapper_a(current_frame_host());

  // 2) Navigate to B.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  RenderFrameHostWrapper rfh_wrapper_b(current_frame_host());

  // Regardless of whether the source Id is set or not, it shouldn't affect the
  // result of the BFCache eviction.
  BackForwardCache::DisabledReason test_reason =
      BackForwardCacheDisable::DisabledReason(
          BackForwardCacheDisable::DisabledReasonId::kUnknown);

  // 3) Disable BFCache for A with UKM source Id and go back.
  BackForwardCache::DisableForRenderFrameHost(
      rfh_wrapper_a.get(), test_reason, ukm::UkmRecorder::GetNewSourceID());
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ASSERT_TRUE(rfh_wrapper_a.WaitUntilRenderFrameDeleted());
  // Page A should be evicted properly.
  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
                         kDisableForRenderFrameHostCalled},
                    {}, {}, {test_reason}, {}, FROM_HERE);

  // 4) Disable BFCache for B without UKM source Id and go forward.
  BackForwardCache::DisableForRenderFrameHost(rfh_wrapper_b.get(), test_reason);
  ASSERT_TRUE(HistoryGoForward(web_contents()));
  ASSERT_TRUE(rfh_wrapper_b.WaitUntilRenderFrameDeleted());
  // Page B should be evicted properly.
  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
                         kDisableForRenderFrameHostCalled},
                    {}, {}, {test_reason}, {}, FROM_HERE);
}

namespace {
enum class SubframeType { SameSite, CrossSite };
}

class BackForwardCacheEvictionDueToSubframeNavigationBrowserTest
    : public BackForwardCacheBrowserTest,
      public ::testing::WithParamInterface<SubframeType> {
 public:
  // Provides meaningful param names instead of /0 and /1.
  static std::string DescribeParams(
      const ::testing::TestParamInfo<ParamType>& info) {
    switch (info.param) {
      case SubframeType::SameSite:
        return "SameSite";
      case SubframeType::CrossSite:
        return "CrossSite";
    }
  }

 protected:
  bool UseCrossOriginSubframe() const {
    return GetParam() == SubframeType::CrossSite;
  }
};

IN_PROC_BROWSER_TEST_P(
    BackForwardCacheEvictionDueToSubframeNavigationBrowserTest,
    SubframePendingCommitShouldPreventCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL subframe_url = embedded_test_server()->GetURL(
      UseCrossOriginSubframe() ? "b.com" : "a.com", "/title1.html");

  IsolateOriginsForTesting(embedded_test_server(), web_contents(),
                           std::vector<std::string>{"a.com", "b.com"});

  // 1) Navigate to a.com.
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  RenderFrameHostImpl* main_frame = current_frame_host();

  // 2) Add subframe and wait for empty document to commit.
  CreateSubframe(web_contents(), "child", GURL(""), true);

  CommitMessageDelayer commit_message_delayer(
      web_contents(), subframe_url,
      base::BindLambdaForTesting([&](RenderFrameHost*) {
        // 5) Test that page cannot be stored in bfcache when subframe is
        // pending commit.
        BackForwardCacheCanStoreDocumentResultWithTree can_store_result =
            web_contents()
                ->GetController()
                .GetBackForwardCache()
                .GetCurrentBackForwardCacheEligibility(
                    static_cast<RenderFrameHostImpl*>(main_frame));
        EXPECT_TRUE(can_store_result.flattened_reasons.HasNotRestoredReason(
            BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating));
      }));

  // 3) Start navigation in subframe to |subframe_url|.
  ExecuteScriptAsync(
      main_frame,
      JsReplace("document.querySelector('#child').src = $1;", subframe_url));
  // 4) Wait until subframe navigation is pending commit.
  commit_message_delayer.Wait();
}

// Check that when the main frame gets BFCached while the subframe navigation
// deferring NavigationThrottles has already ran, the issue that the subframe
// navigation escapes the throttle deferral is addressed.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheEvictionDueToSubframeNavigationBrowserTest,
    MainFrameCommitFirstAndSubframePendingCommitShouldBeEvicted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Prepare the main frame and the sub frame, where both of them are same-site.
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(a)"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  FrameTreeNode* child = root->child_at(0);
  RenderFrameHostImplWrapper child_rfh(child->current_frame_host());

  // Navigate both frames simultaneously.
  const std::string subframe_origin =
      UseCrossOriginSubframe() ? "b.com" : "a.com";
  GURL new_url_1(
      embedded_test_server()->GetURL(subframe_origin, "/title1.html"));
  GURL new_url_2(
      embedded_test_server()->GetURL(subframe_origin, "/title2.html"));
  TestNavigationManager manager1(web_contents(), new_url_1);
  TestNavigationManager manager2(web_contents(), new_url_2);
  auto script = JsReplace("location = $1; frames[0].location = $2;", new_url_1,
                          new_url_2);
  EXPECT_TRUE(ExecJs(web_contents(), script));

  // Wait for main frame request, but don't commit it yet. This should create
  // a speculative RenderFrameHost.
  ASSERT_TRUE(manager1.WaitForRequestStart());

  // Wait for subframe request, but don't commit it yet.
  ASSERT_TRUE(manager2.WaitForRequestStart());

  // Now let the main frame commit.
  ASSERT_TRUE(manager1.WaitForNavigationFinished());
  // Make sure the main frame is at the new URL.
  ASSERT_TRUE(root->current_frame_host()->IsRenderFrameLive());
  ASSERT_EQ(new_url_1, root->current_frame_host()->GetLastCommittedURL());

  // The subframe should be gone now: it should have been evicted from BFCache
  // because the subframe is still navigating; otherwise, this will cause a
  // crash.
  ASSERT_TRUE(manager2.WaitForNavigationFinished());
  EXPECT_TRUE(child_rfh.IsDestroyed());
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored(
      {BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating}, {},
      {}, {}, {}, FROM_HERE);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheEvictionDueToSubframeNavigationBrowserTest,
    ::testing::Values(SubframeType::SameSite, SubframeType::CrossSite),
    &BackForwardCacheEvictionDueToSubframeNavigationBrowserTest::
        DescribeParams);

namespace {
enum class SubframeNavigationType { WithoutURLLoader, WithURLLoader };
}

// Test for pages which has subframe(s) with ongoing navigation(s).
class BackForwardCacheWithSubframeNavigationBrowserTest
    : public BackForwardCacheBrowserTest {
 protected:
  void SetUpOnMainThread() override {
    BackForwardCacheBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
                              base::NumberToString(2));
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
  }

  // Start a subframe navigation and pause it when we get the confirmation
  // dialog triggered by beforeunload event, which is before
  // WillCommitWithoutUrlLoader or WillStartRequest.
  void NavigateSubframeAndPauseAtBeforeUnload(
      BeforeUnloadBlockingDelegate& beforeunload_pauser,
      RenderFrameHostImpl* sub_rfh,
      const GURL& subframe_navigate_url,
      std::string_view iframe_id) {
    ASSERT_TRUE(ExecJs(sub_rfh, R"(
      window.addEventListener('beforeunload', e =>
        e.returnValue='blocked'
      );)"));

    // Start a subframe navigation which will trigger the beforeunload dialog
    // that pauses that navigation. Using `BeginNavigateIframeToURL` is
    // necessary here, since we pause this navigation on beforeunload event. So,
    // we don't want to wait for the navigation to finish.
    BeginNavigateIframeToURL(web_contents(), iframe_id, subframe_navigate_url);
    beforeunload_pauser.Wait();
  }

  // Start a subframe navigation and pause it before `DidCommitNavigation`.
  void NavigateSubframeAndPauseAtDidCommit(FrameTreeNode* ftn,
                                           const GURL& subframe_navigate_url) {
    // Enforce the creation of speculative RFH to correctly wait for
    // the commit event.
    SpeculativeRenderFrameHostObserver observer(web_contents(),
                                                subframe_navigate_url);
    // We have to pause a navigation before `DidCommitNavigation`, so we don't
    // want to wait for the navigation to finish.
    ASSERT_TRUE(BeginNavigateToURLFromRenderer(ftn, subframe_navigate_url));
    // Navigation without a URL loader shall always create the speculative RFH
    // immediately or never create one.
    // The same-site navigation to the subframe will not create a new
    // speculative RFH if render document is not enabled for subframes.
    if (subframe_navigate_url.SchemeIsHTTPOrHTTPS() &&
        ShouldCreateNewRenderFrameHostOnSameSiteNavigation(
            /*is_main_frame=*/false, /*is_local_root=*/true)) {
      observer.Wait();
    }

    // Wait until the navigation is pending commit. Note that the navigation
    // might use a speculative RenderFrameHost, so use that if necessary.
    RenderFrameHostImpl* speculative_rfh =
        ftn->render_manager()->speculative_frame_host();
    CommitNavigationPauser commit_pauser(
        speculative_rfh ? speculative_rfh : ftn->current_frame_host());
    commit_pauser.WaitForCommitAndPause();
  }

  // Put a page which has a subframe with a navigation which hasn't reached the
  // "pending commit" stage nor sent a network request into BackForwardCache and
  // confirm the subframe navigation has been deferred.
  void BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
      const GURL& main_frame_navigate_url,
      const GURL& subframe_navigate_url,
      RenderFrameHostImplWrapper& sub_rfh,
      TestNavigationManager& subframe_navigation_manager,
      std::string_view iframe_id) {
    FrameTreeNode* child_ftn =
        web_contents()->GetPrimaryFrameTree().root()->child_at(0);
    {
      BeforeUnloadBlockingDelegate beforeunload_pauser(web_contents());
      NavigateSubframeAndPauseAtBeforeUnload(beforeunload_pauser, sub_rfh.get(),
                                             subframe_navigate_url, iframe_id);

      // Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
      // this function waits for all frames including subframe to finish
      // loading.
      ASSERT_TRUE(NavigateToURLFromRenderer(sub_rfh->GetMainFrame(),
                                            main_frame_navigate_url));

      // The subframe navigation hasn't reached the "pending commit" stage nor
      // sent a network request, so the page is eligible for BackForwardCache.
      EXPECT_TRUE(sub_rfh->GetMainFrame()->IsInBackForwardCache());
      EXPECT_TRUE(sub_rfh->IsInBackForwardCache());
    }
    web_contents()->SetDelegate(shell());

    // Wait until the subframe navigation is deferred.
    ASSERT_TRUE(
        subframe_navigation_manager.WaitForFirstYieldAfterDidStartNavigation());
    NavigationRequest* child_navigation = child_ftn->navigation_request();
    ASSERT_NE(child_navigation, nullptr);
    EXPECT_TRUE(child_navigation->IsDeferredForTesting());
  }
};

class BackForwardCacheWithSubframeNavigationWithParamBrowserTest
    : public BackForwardCacheWithSubframeNavigationBrowserTest,
      public ::testing::WithParamInterface<SubframeNavigationType> {
 public:
  // Provides meaningful param names instead of /0 and /1.
  static std::string DescribeParams(
      const ::testing::TestParamInfo<ParamType>& info) {
    switch (info.param) {
      case SubframeNavigationType::WithoutURLLoader:
        return "WithoutURLLoader";
      case SubframeNavigationType::WithURLLoader:
        return "WithURLLoader";
    }
  }
};

// Confirm that BackForwardCache is blocked when there is only 1 navigation and
// it's pending commit.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
    SubframeNavigationWithPendingCommitShouldPreventCache) {
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  const GURL subframe_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b()");
  const GURL navigate_url(
      embedded_test_server()->GetURL("c.com", "/title1.html"));
  const GURL subframe_navigate_url =
      GetParam() == SubframeNavigationType::WithURLLoader
          ? embedded_test_server()->GetURL("b.com", "/title1.html")
          : GURL("about:blank");

  // Navigate to a page with a cross site iframe.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  FrameTreeNode* child_node = main_rfh.get()->child_at(0);
  RenderFrameHostImplWrapper sub_rfh(child_node->current_frame_host());

  // Pause subframe's navigation before `DidCommitNavigation`.
  NavigateSubframeAndPauseAtDidCommit(child_node, subframe_navigate_url);

  // Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
  // this function waits for all frames including subframe to finish loading.
  ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));

  // Subframe navigation has reached the "pending commit" stage, so the page is
  // not eligible for BackForwardCache.
  EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
  EXPECT_TRUE(sub_rfh.WaitUntilRenderFrameDeleted());

  // Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
                    FROM_HERE);

  // Confirm that subframe's url didn't change.
  EXPECT_EQ(subframe_url, current_frame_host()->child_at(0)->current_url());
}

// Confirm that BackForwardCache is blocked when there are 2 navigations, 1 not
// pending commit yet, and 1 pending commit.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheWithSubframeNavigationBrowserTest,
    MultipleSubframeNavigationWithBeforeAndPendingCommitShouldPreventCache) {
  // This test relies on the main frame and the iframe to live in different
  // processes. This allows one renderer process to proceed a navigation while
  // the other renderer process is busy executing its beforeunload handler.
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is not enabled!";
  }
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
  const GURL subframe_b_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b()");
  const GURL subframe_c_url = embedded_test_server()->GetURL(
      "c.com", "/cross_site_iframe_factory.html?c()");
  const GURL navigate_url(
      embedded_test_server()->GetURL("d.com", "/title1.html"));
  const GURL subframe_navigate_url = GURL("about:blank");

  // Navigate to a page with two cross site iframes.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh_b(
      main_rfh.get()->child_at(0)->current_frame_host());
  RenderFrameHostImplWrapper sub_rfh_c(
      main_rfh.get()->child_at(1)->current_frame_host());

  {
    // The subframe_b itself does have a dialog-showing beforeunload handler.
    // Pause subframe_b's navigation when we get the confirmation dialog
    // triggered by beforeunload event.
    BeforeUnloadBlockingDelegate beforeunload_pauser(web_contents());
    NavigateSubframeAndPauseAtBeforeUnload(beforeunload_pauser, sub_rfh_b.get(),
                                           subframe_navigate_url,
                                           /*iframe_id=*/"child-0");

    // Pause subframe_c's navigation before `DidCommitNavigation`.
    NavigateSubframeAndPauseAtDidCommit(main_rfh.get()->child_at(1),
                                        subframe_navigate_url);

    // Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
    // this function waits for all frames including subframe to finish loading.
    ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));

    // The subframe_c's navigation already started committing, so the page is
    // not eligible for BackForwardCache.
    EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
    EXPECT_TRUE(sub_rfh_b.WaitUntilRenderFrameDeleted());
    EXPECT_TRUE(sub_rfh_c.WaitUntilRenderFrameDeleted());
  }
  web_contents()->SetDelegate(shell());

  // Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
                    FROM_HERE);

  // Confirm that subframe's url didn't change.
  EXPECT_EQ(subframe_b_url, current_frame_host()->child_at(0)->current_url());
  EXPECT_EQ(subframe_c_url, current_frame_host()->child_at(1)->current_url());
}

// Confirm that BackForwardCache is blocked when there are 2 navigations, 1 has
// not sent a network request yet, and 1 has already sent request.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheWithSubframeNavigationBrowserTest,
    MultipleSubframeNavigationWithBeforeAndAfterSendingRequestShouldPreventCache) {
  // This test relies on the main frame and the iframe to live in different
  // processes. This allows one renderer process to proceed a navigation while
  // the other renderer process is busy executing its beforeunload handler.
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is not enabled!";
  }
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
  const GURL subframe_b_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b()");
  const GURL subframe_c_url = embedded_test_server()->GetURL(
      "c.com", "/cross_site_iframe_factory.html?c()");
  const GURL navigate_url(
      embedded_test_server()->GetURL("d.com", "/title1.html"));
  const GURL subframe_b_navigate_url(
      embedded_test_server()->GetURL("b.com", "/title1.html"));
  const GURL subframe_c_navigate_url(
      embedded_test_server()->GetURL("c.com", "/title1.html"));

  // Navigate to a page with two cross site iframes.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh_b(
      main_rfh.get()->child_at(0)->current_frame_host());
  RenderFrameHostImplWrapper sub_rfh_c(
      main_rfh.get()->child_at(1)->current_frame_host());

  // Pause a subframe_b navigation on `WillStartRequest` before sending a
  // network request.
  TestNavigationManager subframe_b_navigation_manager(web_contents(),
                                                      subframe_b_navigate_url);
  ASSERT_TRUE(
      BeginNavigateToURLFromRenderer(sub_rfh_b.get(), subframe_b_navigate_url));
  ASSERT_TRUE(subframe_b_navigation_manager.WaitForRequestStart());

  // Pause a subframe_c navigation on `WillProcessResponse` after sending a
  // network request.
  TestNavigationManager subframe_c_navigation_manager(web_contents(),
                                                      subframe_c_navigate_url);
  ASSERT_TRUE(
      BeginNavigateToURLFromRenderer(sub_rfh_c.get(), subframe_c_navigate_url));
  ASSERT_TRUE(subframe_c_navigation_manager.WaitForResponse());

  // Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
  // this function waits for all frames including subframe to finish loading.
  ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));

  // The subframe_c's navigation has already sent a network request, so the page
  // is not eligible for BackForwardCache.
  EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
  EXPECT_TRUE(sub_rfh_b.WaitUntilRenderFrameDeleted());
  EXPECT_TRUE(sub_rfh_c.WaitUntilRenderFrameDeleted());
  EXPECT_TRUE(subframe_b_navigation_manager.WaitForNavigationFinished());
  EXPECT_TRUE(subframe_c_navigation_manager.WaitForNavigationFinished());
  EXPECT_FALSE(subframe_b_navigation_manager.was_committed());
  EXPECT_FALSE(subframe_c_navigation_manager.was_committed());

  // Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
                    FROM_HERE);

  // Confirm that subframe's url didn't change.
  EXPECT_EQ(subframe_b_url, current_frame_host()->child_at(0)->current_url());
  EXPECT_EQ(subframe_c_url, current_frame_host()->child_at(1)->current_url());
}

// Confirm that subframe navigation which needs url loader that has already sent
// a network request should block BackForwardCache.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheWithSubframeNavigationBrowserTest,
    SubframeNavigationWithUrlLoaderAfterSendingRequestShouldPreventCache) {
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  const GURL subframe_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b()");
  const GURL navigate_url(
      embedded_test_server()->GetURL("c.com", "/title1.html"));
  const GURL subframe_navigate_url(
      embedded_test_server()->GetURL("b.com", "/title2.html"));

  // Navigate to a page with a cross site iframe.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh(
      main_rfh.get()->child_at(0)->current_frame_host());
  TestNavigationManager subframe_navigation_manager(web_contents(),
                                                    subframe_navigate_url);
  ASSERT_TRUE(
      BeginNavigateToURLFromRenderer(sub_rfh.get(), subframe_navigate_url));

  // Pause the subframe navigation on `WillProcessResponse`.
  ASSERT_TRUE(subframe_navigation_manager.WaitForResponse());

  // Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
  // this function waits for all frames including subframe to finish loading.
  ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));

  // Subframe navigation has already sent a network request, so the page is not
  // eligible for BackForwardCache.
  EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
  EXPECT_TRUE(sub_rfh.WaitUntilRenderFrameDeleted());
  EXPECT_FALSE(subframe_navigation_manager.was_committed());

  // Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
                    FROM_HERE);

  // Confirm that subframe's url didn't change.
  EXPECT_EQ(subframe_url, current_frame_host()->child_at(0)->current_url());
}

// Confirm that subframe navigation which needs url loader that hasn't sent a
// network request should not block BackForwardCache.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheWithSubframeNavigationBrowserTest,
    SubframeNavigationWithUrlLoaderBeforeSendingRequestShouldNotPreventCache) {
  // This test relies on the main frame and the iframe to live in different
  // processes. This allows one renderer process to proceed a navigation while
  // the other renderer process is busy executing its beforeunload handler.
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is not enabled!";
  }
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  const GURL subframe_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b()");
  const GURL navigate_url(
      embedded_test_server()->GetURL("c.com", "/title1.html"));
  const GURL subframe_navigate_url(
      embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Navigate to a page with a cross site iframe.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh(
      main_rfh.get()->child_at(0)->current_frame_host());

  // Put a page which has a subframe with a URLLoader navigation which hasn't
  // sent a network request into BackForwardCache. The iframe itself
  // does have a dialog-showing beforeunload handler.
  TestNavigationManager subframe_navigation_manager(web_contents(),
                                                    subframe_navigate_url);
  BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
      navigate_url, subframe_navigate_url, sub_rfh, subframe_navigation_manager,
      /*iframe_id=*/"child-0");

  // Navigate back.
  TestNavigationObserver back_load_observer(shell()->web_contents());
  web_contents()->GetController().GoBack();
  back_load_observer.WaitForNavigationFinished();
  ASSERT_FALSE(main_rfh->IsInBackForwardCache());

  // Wait until the resumed subframe navigation finishes.
  EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
  EXPECT_TRUE(subframe_navigation_manager.was_successful());
  EXPECT_EQ(subframe_navigate_url,
            current_frame_host()->child_at(0)->current_url());
}

// Confirm that subframe no-url loader navigation (e.g., about:blank) in
// bfcached page is deferred and then resumed when the page is navigated back.
IN_PROC_BROWSER_TEST_F(
    BackForwardCacheWithSubframeNavigationBrowserTest,
    SubframeNavigationWithoutUrlLoaderBeforeCommitShouldNotPreventCache) {
  // This test relies on the main frame and the iframe to live in different
  // processes. This allows one renderer process to proceed a navigation while
  // the other renderer process is busy executing its beforeunload handler.
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is not enabled!";
  }
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  const GURL navigate_url(
      embedded_test_server()->GetURL("c.com", "/title1.html"));
  const GURL subframe_navigate_url = GURL("about:blank");

  // Navigate to a page with a cross site iframe.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh(
      main_rfh.get()->child_at(0)->current_frame_host());

  // Put a page which has a subframe with a no-URLLoader navigation which hasn't
  // reached the "pending commit" stage into BackForwardCache. The iframe itself
  // does have a dialog-showing beforeunload handler.
  TestNavigationManager subframe_navigation_manager(web_contents(),
                                                    subframe_navigate_url);
  BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
      navigate_url, subframe_navigate_url, sub_rfh, subframe_navigation_manager,
      /*iframe_id=*/"child-0");

  // Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ASSERT_FALSE(main_rfh->IsInBackForwardCache());

  // Confirm the deferred navigation was resumed and subframe's url changed.
  EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
  EXPECT_TRUE(subframe_navigation_manager.was_successful());
  EXPECT_EQ(subframe_navigate_url,
            current_frame_host()->child_at(0)->current_url());
}

// Confirm that we don't resume a subframe navigation when an unrelated BFCached
// page gets restored.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
    SubframeNavigationShouldNotBeResumedWhenUnrelatedPageRestored) {
  // This test relies on the main frame and the iframe to live in different
  // processes. This allows one renderer process to proceed a navigation while
  // the other renderer process is busy executing its beforeunload handler.
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is not enabled!";
  }
  const GURL main_url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  const GURL navigate_url_c(
      embedded_test_server()->GetURL("c.com", "/title1.html"));
  const GURL navigate_url_d(
      embedded_test_server()->GetURL("d.com", "/title1.html"));
  const GURL subframe_navigate_url =
      GetParam() == SubframeNavigationType::WithURLLoader
          ? embedded_test_server()->GetURL("b.com", "/title1.html")
          : GURL("about:blank");

  // Navigate to a page with a cross site iframe.
  ASSERT_TRUE(NavigateToURL(shell(), main_url_a));
  RenderFrameHostImplWrapper main_rfh_a(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh_b(
      main_rfh_a.get()->child_at(0)->current_frame_host());

  // Put a page which has a subframe with a navigation which hasn't reached the
  // "pending commit" stage or sent a network request into BackForwardCache.
  TestNavigationManager subframe_navigation_manager(web_contents(),
                                                    subframe_navigate_url);
  BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
      navigate_url_c, subframe_navigate_url, sub_rfh_b,
      subframe_navigation_manager,
      /*iframe_id=*/"child-0");

  // Navigate away.
  // Currently, `main_rfh_a` is in BFCache and we are on `navigate_url_c`. Then,
  // we will navigate to `navigate_url_d` which will put `main_rfh_c` in
  // BFCache.
  RenderFrameHostImplWrapper main_rfh_c(current_frame_host());
  ASSERT_TRUE(NavigateToURL(shell(), navigate_url_d));
  ASSERT_TRUE(main_rfh_c->IsInBackForwardCache());

  // Navigate back to `main_rfh_c` and restore that from BFCache, while
  // `main_rfh_a` is still in BFCache.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  ASSERT_EQ(main_rfh_c.get(), current_frame_host());
  ASSERT_TRUE(main_rfh_a->IsInBackForwardCache());

  // Confirm the subframe's deferred navigation is not committed.
  EXPECT_FALSE(subframe_navigation_manager.was_committed());

  // Navigate back to `main_rfh_a`.
  TestNavigationObserver back_load_observer(shell()->web_contents());
  web_contents()->GetController().GoBack();
  back_load_observer.WaitForNavigationFinished();
  ASSERT_FALSE(main_rfh_a->IsInBackForwardCache());

  // Confirm the deferred navigation was resumed and subframe's url changed.
  EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
  EXPECT_TRUE(subframe_navigation_manager.was_successful());
  EXPECT_EQ(subframe_navigate_url,
            current_frame_host()->child_at(0)->current_url());
}

// Evict the bfcached page which has a subframe with a deferred navigation and
// confirm the subframe'url didn't change when the page is navigated back.
IN_PROC_BROWSER_TEST_P(
    BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
    EvictBFCachedPageWithDeferredSubframeNavigationBeforeCommit) {
  // This test relies on the main frame and the iframe to live in different
  // processes. This allows one renderer process to proceed a navigation while
  // the other renderer process is busy executing its beforeunload handler.
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "Site isolation is not enabled!";
  }
  const GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  const GURL subframe_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b()");
  const GURL navigate_url(
      embedded_test_server()->GetURL("c.com", "/title1.html"));
  const GURL subframe_navigate_url =
      GetParam() == SubframeNavigationType::WithURLLoader
          ? embedded_test_server()->GetURL("b.com", "/title1.html")
          : GURL("about:blank");

  // Navigate to a page with a cross site iframe.
  ASSERT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHostImplWrapper main_rfh(current_frame_host());
  RenderFrameHostImplWrapper sub_rfh(
      main_rfh.get()->child_at(0)->current_frame_host());

  // Put a page which has a subframe with a navigation which hasn't reached the
  // "pending commit" stage or sent a network request into BackForwardCache. The
  // iframe itself does have a dialog-showing beforeunload handler.
  TestNavigationManager subframe_navigation_manager(web_contents(),
                                                    subframe_navigate_url);
  BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
      navigate_url, subframe_navigate_url, sub_rfh, subframe_navigation_manager,
      /*iframe_id=*/"child-0");

  // Flush the cache and evict the previously BFCached page.
  web_contents()->GetController().GetBackForwardCache().Flush();
  ASSERT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
  ASSERT_TRUE(sub_rfh.WaitUntilRenderFrameDeleted());

  // Confirm the subframe's deferred navigation has finished and was not
  // committed.
  EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
  EXPECT_FALSE(subframe_navigation_manager.was_committed());

  // Navigate back.
  ASSERT_TRUE(HistoryGoBack(web_contents()));

  // Confirm that subframe's url didn't change.
  EXPECT_EQ(subframe_url, current_frame_host()->child_at(0)->current_url());
}

INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
    ::testing::Values(SubframeNavigationType::WithoutURLLoader,
                      SubframeNavigationType::WithURLLoader),
    &BackForwardCacheWithSubframeNavigationWithParamBrowserTest::
        DescribeParams);

class BackForwardCacheFencedFrameBrowserTest
    : public BackForwardCacheBrowserTest {
 public:
  BackForwardCacheFencedFrameBrowserTest() = default;
  ~BackForwardCacheFencedFrameBrowserTest() override = default;
  BackForwardCacheFencedFrameBrowserTest(
      const BackForwardCacheFencedFrameBrowserTest&) = delete;

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

  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
    fenced_frame_helper_ = std::make_unique<test::FencedFrameTestHelper>();
  }

  test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return *fenced_frame_helper_;
  }

 private:
  std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(BackForwardCacheFencedFrameBrowserTest,
                       FencedFramePageNotStoredInBackForwardCache) {
  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", "/fenced_frames/title1.html"));
  GURL url_c(
      embedded_test_server()->GetURL("c.com", "/fenced_frames/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  // 2) Create a fenced frame.
  content::RenderFrameHostImpl* fenced_frame_host =
      static_cast<content::RenderFrameHostImpl*>(
          fenced_frame_test_helper().CreateFencedFrame(
              web_contents()->GetPrimaryMainFrame(), url_b));
  RenderFrameHostWrapper fenced_frame_host_wrapper(fenced_frame_host);

  // 3) Navigate to C on the fenced frame host.
  fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
                                                            url_c);
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  if (!fenced_frame_host_wrapper.IsRenderFrameDeleted())
    EXPECT_FALSE(fenced_frame_host->IsInBackForwardCache());
}

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

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

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

  // 3) Navigate to B again, renderer initiated.
  ASSERT_TRUE(NavigateToURLFromRenderer(rfh_b.get(), url_b));
  RenderFrameHostImplWrapper rfh_b2(current_frame_host());

  // This is treated as replacement, and the previous B page did not get into
  // back/forward cache.
  if (ShouldCreateNewHostForAllFrames()) {
    EXPECT_TRUE(rfh_b.WaitUntilRenderFrameDeleted());
  } else {
    EXPECT_FALSE(rfh_b->IsInBackForwardCache());
    EXPECT_EQ(rfh_b.get(), rfh_b2.get());
  }

  // 4) Go back. Make sure we go back to A instead of B and restore from
  // bfcache.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
  EXPECT_EQ(current_frame_host(), rfh_a.get());
  EXPECT_TRUE(rfh_b2.get()->IsInBackForwardCache());
  ExpectRestored(FROM_HERE);

  // 5) Go forward and restore from bfcache.
  ASSERT_TRUE(HistoryGoForward(shell()->web_contents()));
  EXPECT_EQ(current_frame_host(), rfh_b2.get());
  ExpectRestored(FROM_HERE);
}

// BEFORE ADDING A NEW TEST HERE
// Read the note at the top about the other files you could add it to.
}  // namespace content